diff --git a/.env b/.env deleted file mode 100644 index 0a433c5..0000000 --- a/.env +++ /dev/null @@ -1,12 +0,0 @@ -# Database Config -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_USER=reactpress -DB_PASSWD=reactpress -DB_DATABASE=reactpress - -# Client Config -CLIENT_SITE_URL=http://localhost:3001 - -# Server Config -SERVER_SITE_URL=http://localhost:3002 \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ad166aa --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# ReactPress — copy to .env and run `pnpm init` to sync from .reactpress/config.json +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_USER=reactpress +DB_PASSWD=reactpress +DB_DATABASE=reactpress + +CLIENT_SITE_URL=http://localhost:3001 +SERVER_SITE_URL=http://localhost:3002 +SERVER_PORT=3002 +SERVER_API_PREFIX=/api diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fe6f513 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI + +on: + push: + branches: [main, master, develop] + pull_request: + branches: [main, master, develop] + +jobs: + build-and-smoke: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: reactpress + MYSQL_DATABASE: reactpress + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping -h localhost" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build toolkit + run: pnpm run build:toolkit + + - name: Build server + run: pnpm run build:server + + - name: Create test .env + run: | + cat > .env <<'EOF' + DB_HOST=127.0.0.1 + DB_PORT=3306 + DB_USER=root + DB_PASSWD=reactpress + DB_DATABASE=reactpress + SERVER_PORT=3002 + SERVER_SITE_URL=http://127.0.0.1:3002 + SERVER_API_PREFIX=/api + CLIENT_SITE_URL=http://127.0.0.1:3001 + EOF + + - name: Start API and smoke health + env: + DB_HOST: 127.0.0.1 + DB_PORT: 3306 + DB_USER: root + DB_PASSWD: reactpress + DB_DATABASE: reactpress + SERVER_PORT: 3002 + SERVER_SITE_URL: http://127.0.0.1:3002 + SERVER_API_PREFIX: /api + run: | + node server/dist/main.js & + echo $! > /tmp/reactpress-api.pid + for i in $(seq 1 45); do + if node scripts/smoke-api-health.mjs; then + kill "$(cat /tmp/reactpress-api.pid)" 2>/dev/null || true + exit 0 + fi + sleep 2 + done + kill "$(cat /tmp/reactpress-api.pid)" 2>/dev/null || true + echo "API health smoke failed" + exit 1 + + - name: CLI doctor (offline checks) + run: node cli/bin/reactpress.js doctor || true diff --git a/.gitignore b/.gitignore index 4f96993..a46667c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ node_modules .idea .next +.env .env.prod +.reactpress/ *.local *.cache *error.log @@ -12,6 +14,7 @@ node_modules sitemap.xml lib +!cli/lib dist dist-ssr coverage diff --git a/3.0.md b/3.0.md new file mode 100644 index 0000000..ab33c2e --- /dev/null +++ b/3.0.md @@ -0,0 +1,357 @@ +# ReactPress 3.0 发布方案(平台版) + +> **版本定位**:零配置、一分钟起站、一个 npm 包走天下 +> **发布代号**:Platform +> **不包含**:Next 14 / React 18 大升级(归入 3.1「现代栈版」) +> **文档更新**:2026-05-17 + +--- + +## 三大重点(3.0 要解决什么) + +| # | 重点 | 用户感知 | 3.0 交付标准 | +|---|------|----------|--------------| +| **1** | **零配置** | 不用手写 `.env`、不用先装六个包、不用懂 monorepo | **最快 1 分钟**内看到前台 + 管理后台 + API | +| **2** | **唯一入口** | 只记一个包名、一个命令 `reactpress` | **`npm i -g @fecommunity/reactpress`** 覆盖 init / dev / build / deploy 全流程 | +| **3** | **极致开发体验** | 少查文档、少踩坑、状态一眼可见 | 交互菜单、`doctor`、热更新全栈、`status` 一站式诊断 | + +### 产品一句话 + +> **装一个包,敲一条命令,一分钟拥有自己的 CMS。** + +### 一分钟启动(定义与验收) + +「1 分钟」指 **首次在本机完成全局安装之后**,在空项目目录内: + +```bash +mkdir my-blog && cd my-blog +reactpress init # 自动生成 .reactpress + .env + Docker MySQL(或检测已有 DB) +reactpress dev # 自动:环境检查 → toolkit → API + 前台 +``` + +| 阶段 | 目标耗时 | 说明 | +|------|----------|------| +| `reactpress init` | ≤ 30s | 无交互默认项;Docker 拉镜像可首次略超,文档说明 | +| `reactpress dev` 到可访问 | ≤ 30s | 前台 `http://localhost:3001`、API `http://localhost:3002/api`、健康检查通过 | +| **合计** | **≤ 60s** | CI 用脚本计时;超时则优化 init/dev 并行与缓存 | + +**零配置含义**(默认即可跑,无需提前准备): + +- 不要求预先创建 MySQL 账号(`embedded-docker` 默认) +- 不要求手写 `.env`(由 `config.json` 同步) +- 不要求 `pnpm install` 整个 monorepo(**仅全局安装** `@fecommunity/reactpress` 即可在任意目录建站) +- 不要求分别安装 server / client npm 包 + +--- + +## 一、唯一入口:`@fecommunity/reactpress` + +### 1.1 包模型(3.0 定稿) + +| npm 包 | 3.0 角色 | +|--------|----------| +| **`@fecommunity/reactpress`** | **唯一对外主包**:全局命令 `reactpress`,内含 CLI + bundled API + 模板 | +| `@fecommunity/reactpress-client` | 高级场景:仅部署前台、连接远程 API(**非**新用户第一步) | +| `@fecommunity/reactpress-toolkit` | Headless / 自建前台用的 TS SDK | +| `@fecommunity/reactpress-template-*` | `reactpress new --template` 可选 | +| `@fecommunity/reactpress-cli` | **Deprecated 别名**:3.0 起文档与 npm 描述指向 `@fecommunity/reactpress`;可保留一个版本 re-export 后停更 | +| `@fecommunity/reactpress-server` | **Deprecated**:API 由主包内置,不再作为主路径 | + +**全局安装(唯一推荐写法):** + +```bash +npm i -g @fecommunity/reactpress@3 +reactpress --version # 3.0.0 +``` + +**不再作为新用户入口的写法**(仅迁移文档保留对照): + +```bash +# ❌ 3.0 文档不推荐 +npm i -g @fecommunity/reactpress-cli +npx @fecommunity/reactpress-server +npx @fecommunity/reactpress-client +``` + +### 1.2 Monorepo 贡献者 + +克隆仓库开发时仍用 `pnpm install` + `pnpm dev`,底层调用同一套 `cli/bin/reactpress.js`;对外叙事不强调 monorepo,避免与「唯一入口」冲突。 + +| 场景 | 命令 | +|------|------| +| 本仓开发 | `pnpm dev` → 等同 `reactpress dev` | +| 发版 | `reactpress publish` | + +--- + +## 二、零配置:从安装到可访问 + +### 2.1 默认路径(新用户) + +```bash +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev +# → http://localhost:3001 前台 +# → http://localhost:3001/admin 管理端 +# → http://localhost:3002/api API +``` + +无子命令时进入 **交互式菜单**(与 Claude Code 类似),适合不想记命令的用户: + +```bash +reactpress +# 零配置开发 / 初始化 / 状态 / Docker / 发布 … +``` + +### 2.2 init 自动完成的事 + +| 产出 | 说明 | +|------|------| +| `.reactpress/config.json` | 端口、数据库模式、URL | +| `.reactpress/docker-compose.yml` | 默认 embedded-docker MySQL | +| `.env` | 由 CLI 从 config 同步,用户无需编辑即可 dev | +| 数据库 | 自动等待就绪 + 迁移/同步 | + +### 2.3 可选配置(仍算「零配置」) + +仅在需要时介入,**不是**第一步: + +- 外部 MySQL:改 `database.mode` + `reactpress config` +- 仅 API(Headless):`reactpress dev --api-only` +- 生产:`reactpress start` 或 `docker-compose.prod.yml` + +--- + +## 三、极致开发体验(DX) + +### 3.1 设计原则 + +| 原则 | 落地 | +|------|------| +| **能用一个词就不用两个** | 全用 `reactpress `,无 `reactpress-cli` / `reactpress-server` 混用 | +| **失败可诊断** | `reactpress doctor`:Node、Docker、端口、`.env`、DB、API `/api/health` | +| **状态可感知** | `reactpress status`:API / DB / 前台 / Docker 一页汇总 | +| **常用操作不打断心流** | `dev` 一次起全栈;`build` 按依赖顺序;热更新 API + 前端 | +| **可发现** | 无参数 `reactpress` → 交互菜单;`reactpress --help` 分组清晰 | + +### 3.2 命令面(目标态) + +| 命令 | 作用 | +|------|------| +| `reactpress` | 交互式菜单 | +| `reactpress init` | 零配置初始化 | +| `reactpress dev` | 全栈开发(默认) | +| `reactpress dev --api-only` | Headless:仅 API | +| `reactpress dev --client-only` | 仅前台(已有 API 时) | +| `reactpress doctor` | 环境诊断 | +| `reactpress status` | 运行状态 | +| `reactpress config` | 查看/修改配置并可选 `--apply` 重启 | +| `reactpress start` / `stop` / `restart` | 生产生命周期 | +| `reactpress docker *` | Docker 开发环境 | +| `reactpress build` / `publish` | 构建与发版(贡献者/维护者) | +| `reactpress db backup` | 数据库备份 | + +### 3.3 DX 相关交付(3.0 必做) + +- [x] `reactpress doctor` +- [x] 交互式菜单(`reactpress` 无参数) +- [x] `dev` 串联:检查 → toolkit → API + client +- [x] `status` 含 DB / health / 端口 +- [ ] **启动耗时优化**:init/dev 并行、Docker 健康等待上限、二次启动复用容器(冲刺 1 分钟指标) +- [x] **首屏提示**:dev 成功后打印前台 / admin / API / Swagger 链接(`cli/lib/dev-banner.js`) +- [x] **错误信息可操作**:`doctor` / `dev` 失败时给出 Docker 安装链接与 `reactpress docker up` 建议 + +--- + +## 四、成功标准(发布闸门) + +围绕三大重点重新定义: + +| # | 标准 | 验证方式 | +|---|------|----------| +| 1 | **1 分钟** | 脚本:`init` + `dev` → curl 前台与 `/api/health`,总耗时 ≤ 60s(二次运行) | +| 2 | **唯一入口** | 文档与 README 仅出现 `npm i -g @fecommunity/reactpress`;无其他包作为主路径 | +| 3 | **零配置** | 空目录仅执行 `init` + `dev`,无需手改 `.env` 即可登录管理端 | +| 4 | **DX** | `doctor` 能识别常见问题;`status` 输出完整;交互菜单覆盖 80% 日常操作 | +| 5 | **迁移** | `docs/migration-2-to-3.md` 将旧命令映射到 `reactpress *` | +| 6 | **质量** | CI:`pnpm build` + API 冒烟 | + +Headless(API Key、Webhook、toolkit)作为 **3.0 平台能力延伸**,不抢三大重点的叙事,但保留实现与验收。 + +--- + +## 五、范围定义 + +### 5.1 必做(支撑三大重点) + +| 类别 | 项 | +|------|-----| +| **入口** | npm 发布 `@fecommunity/reactpress@3`,bin 仅 `reactpress`;废弃文档中的多包安装流 | +| **零配置** | `init` 默认 embedded-docker;`.env` 自动生成;`dev` 一键全栈 | +| **1 分钟** | 启动链路 profiling + 优化(见 3.3) | +| **DX** | doctor、status、交互菜单、dev 成功后的链接提示 | +| **迁移** | 2.x 命令 → `reactpress` 对照表 | +| **冒烟** | CI 计时脚本 + health 检查 | + +### 5.2 平台能力(已完成或延续,非主叙事) + +- API Key、Webhook MVP、定时发布、文章修订历史 +- `docker-compose.prod.yml`、`reactpress db backup` +- OpenAPI / toolkit 与 3.0 版本对齐 +- `@fecommunity/reactpress-server` deprecated + +### 5.3 不做(3.0 Out of Scope) + +| 项 | 归属 | +|----|------| +| Next 14 / React 18 | 3.1 现代栈版 | +| GraphQL、插件市场、多租户 | 更远版本 | +| 要求用户先 `pnpm install` 才能建站 | 与「唯一全局包」冲突 | + +--- + +## 六、Breaking Changes(草案) + +| 变更 | 迁移 | +|------|------| +| 主包改为 **`@fecommunity/reactpress`** | `npm i -g @fecommunity/reactpress@3`;命令统一 `reactpress` | +| `@fecommunity/reactpress-cli` 不再作为主包名 | 旧全局包用户:卸载 cli 包,改装 `reactpress` | +| `@fecommunity/reactpress-server` deprecated | `reactpress start` / `reactpress dev --api-only` | +| 配置以 `.reactpress/config.json` 为准 | `reactpress init` 或 `reactpress config --apply` | +| 根 monorepo 包 `private` | 对外只发 `@fecommunity/reactpress`,不发裸 `reactpress` 名 | + +--- + +## 七、实施阶段 + +```mermaid +gantt + title ReactPress 3.0(三大重点) + dateFormat YYYY-MM-DD + section 入口与零配置 + npm包名reactpress与bin统一 :a1, 2026-05-19, 4d + init dev 一分钟验收脚本 :a2, 2026-05-19, 5d + section 极致DX + 启动优化与成功提示 :b1, 2026-05-24, 5d + 错误信息与文档唯一入口 :b2, 2026-05-24, 4d + section 发布 + 迁移指南与npm publish :c1, 2026-06-01, 5d + v3.0.0 :milestone, 2026-06-06, 1d +``` + +### Phase 1 — 唯一入口 + 零配置(约 1 周) + +- [x] npm 发布物:`cli/package.json` 已更名为 `@fecommunity/reactpress@3.0.0`(待 npm publish) +- [x] 文档/README/迁移指南:主推 `npm i -g @fecommunity/reactpress` +- [x] `reactpress init` + `reactpress dev` 零配置链路(monorepo + 独立项目) +- [x] 一分钟计时脚本:`scripts/benchmark-cold-start.mjs`(`pnpm test:benchmark`) + +### Phase 2 — 极致 DX(约 1 周) + +- [ ] dev 启动耗时优化(并行、Docker 等待策略) +- [x] dev 成功输出:前台 / admin / API / Swagger URL +- [x] `doctor` / `status` / `dev` 失败文案含可操作建议 +- [x] `reactpress-cli` bin 打 deprecated 警告并映射到 `reactpress`(3.1 移除) + +### Phase 3 — 发布(约 3~5 天) + +- [x] Headless 与生产能力(API Key、Webhook、compose、backup 等) +- [ ] GitHub Release + npm 主推 `@fecommunity/reactpress` +- [ ] 公告强调:1 分钟、一个包、极致 DX + +--- + +## 八、验收清单 + +### 重点 1:零配置 · 1 分钟 + +- [x] 空目录:`reactpress init` → `reactpress dev`,无需手改 `.env` +- [x] 自动化脚本:`pnpm test:benchmark`(二次冷启动;首次 Docker 拉镜像可例外) +- [x] 可访问:前台、`/admin`、`GET /api/health` + +### 重点 2:唯一入口 + +- [x] 包名 `@fecommunity/reactpress`,bin `reactpress`(`reactpress-cli` 仅 deprecated 别名) +- [x] README / 迁移指南无「先装 server 再装 client」主路径 +- [x] `migration-2-to-3.md` 旧命令映射到 `reactpress` + +### 重点 3:极致 DX + +- [x] `reactpress` 无参数进入交互菜单 +- [x] `reactpress doctor` 覆盖 Node / Docker / 端口 / DB / API +- [x] `reactpress status` 一页看清服务状态 +- [x] `dev` 失败时错误含下一步建议 + +### 发布 + +- [ ] `@fecommunity/reactpress@3.0.0` 已发布 +- [ ] CI 绿灯 + +--- + +## 九、待决策项 + +| # | 问题 | 建议 | +|---|------|------| +| 1 | `@fecommunity/reactpress` 与现 `cli` 包关系? | **已定稿**:`cli/` 目录即 `@fecommunity/reactpress` 发布物 | +| 2 | 是否保留 `reactpress-cli` bin 别名? | **已定稿**:`reactpress-cli-shim.js` 打 deprecated warning,3.1 移除 | +| 3 | 首次 Docker 拉镜像超过 60s 如何宣传? | 文案写「二次启动 1 分钟内」;或 init 预拉镜像 | +| 4 | Monorepo 用户是否仍写 `pnpm dev`? | 可以,对内文档说明;对外只推全局 `reactpress` | + +--- + +## 十、发布物 + +| 产物 | 说明 | +|------|------| +| **`@fecommunity/reactpress@3.0.0`** | 唯一主包,全局 `reactpress` | +| Git tag `v3.0.0` | monorepo 标签 | +| `docs/migration-2-to-3.md` | 旧多包 → 单包 | +| GitHub Release | 标题突出:1 分钟 · 一个包 · 极致 DX | + +--- + +## 十一、参考命令(3.0 目标态) + +```bash +# ── 新用户:唯一入口 ── +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev +# 约 1 分钟内打开 http://localhost:3001 + +# ── 不想记命令 ── +reactpress + +# ── 出问题 ── +reactpress doctor +reactpress status + +# ── Headless(进阶)── +reactpress dev --api-only +# 自建前台使用 @fecommunity/reactpress-toolkit@3 + +# ── 生产 ── +reactpress start +# 或 docker-compose.prod.yml + +# ── 本仓库贡献者 ── +pnpm install && pnpm dev +``` + +--- + +## 十二、3.0 之后 + +| 版本 | 主题 | +|------|------| +| **3.0.x** | 继续压 dev 启动时间;完善 Webhook UI;移除 deprecated 包名 | +| **3.1** | 现代栈版:Next 14 + React 18 | +| **3.2+** | 插件扩展、可选 PostgreSQL | + +--- + +*3.0 平台版以 **零配置 1 分钟启动**、**`@fecommunity/reactpress` 唯一入口**、**极致开发体验** 为最高优先级;其余能力为实现与差异化服务,不稀释主叙事。* diff --git a/CHANGELOG.md b/CHANGELOG.md index f00ae8f..e53abe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# [3.0.0](https://github.com/fecommunity/reactpress/compare/v2.0.2...v3.0.0) (2026-05-17) + +### Platform 3.0 — CLI-first Headless + +* **CLI**: 主包更名为 `@fecommunity/reactpress`;`reactpress dev` 就绪后输出前台/admin/API/Swagger 链接;`doctor`/`dev` 失败含可操作建议;CLI 多语言(`REACTPRESS_LANG` / `--lang`);`reactpress-cli` bin deprecated +* **Headless**: `GET /api/health`;API Key(`X-API-Key` + `/api/article/headless/list`);Webhook(`article.published`、`comment.created`,HMAC 签名 + 3 次重试) +* **内容**: 文章定时发布(`scheduledPublishAt`);文章修订历史与回滚 +* **配置**: 根目录 `.env.example`;移除废弃 `.reactpress` 配置路径 +* **运维**: `cli/templates/docker-compose.prod.yml` 生产示例;`scripts/benchmark-cold-start.mjs` 二次冷启动验收 +* **Breaking**: `@fecommunity/reactpress-server` deprecated;主推 `@fecommunity/reactpress@3`;迁移见 [docs/migration-2-to-3.md](./docs/migration-2-to-3.md) + # [2.0.0-beta-4-beta.1](https://github.com/fecommunity/reactpress/compare/v2.0.1...v2.0.0-beta-4-beta.1) (2025-11-16) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf13fde..033f800 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,122 +1,79 @@ # Contributing to ReactPress -Thank you for your interest in contributing to ReactPress! We welcome contributions from the community to help improve the project. - -## Code of Conduct - -Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md) to ensure a welcoming and inclusive environment for all contributors. - -## Getting Started - -1. Fork the repository -2. Clone your fork: `git clone https://github.com/your-username/reactpress.git` -3. Create a new branch: `git checkout -b feature/your-feature-name` -4. Make your changes -5. Commit your changes: `git commit -m "Add your feature description"` -6. Push to your fork: `git push origin feature/your-feature-name` -7. Create a pull request +Thank you for your interest in contributing to ReactPress! ## Development Setup ### Prerequisites -- Node.js >= 16.5.0 -- pnpm >= 7.0.0 -- MySQL 5.7 or higher +- Node.js >= 18.0.0 +- pnpm >= 8.0.0 +- MySQL 5.7+ (or Docker via `pnpm run init` / `pnpm docker:dev`) -### Installation +### First run ```bash -# Clone the repository git clone https://github.com/fecommunity/reactpress.git cd reactpress - -# Install dependencies pnpm install - -# Start development servers -pnpm run dev +pnpm run init # .reactpress/config.json + .env +pnpm run dev # toolkit → API (3002) → client (3001) ``` -## Project Structure - -ReactPress follows a monorepo structure: +## Project structure ``` reactpress/ -├── client/ # Next.js frontend application -├── server/ # NestJS backend API -├── toolkit/ # Auto-generated API client SDK -├── templates/ # Project templates -├── scripts/ # Build and deployment scripts -└── docs/ # Documentation +├── server/ # NestJS API (primary backend in this repo) +├── client/ # Next.js frontend +├── toolkit/ # OpenAPI-generated API SDK +├── templates/ # Starter templates +├── scripts/ # dev, deploy, lifecycle +├── docs/ # Docusaurus site +└── .reactpress/ # Local CLI config ``` -## Development Workflow +## Development workflow -### Running Services +| Task | Command | +|------|---------| +| Full stack dev | `pnpm dev` | +| API only (watch) | `pnpm dev:api` or `pnpm dev:server` | +| Client only | `pnpm dev:client` | +| Docker MySQL + proxy | `pnpm docker:dev` | +| Regenerate API types | `pnpm run build:toolkit` | +| API lifecycle | `pnpm run start:api` / `stop` / `restart` / `status` | -```bash -# Start both client and server in development mode -pnpm run dev +`pnpm dev` builds toolkit first, waits for API health, then starts the client. -# Start only the server -pnpm run dev:server +## Building -# Start only the client -pnpm run dev:client +```bash +pnpm run build # toolkit + server + client +pnpm run build:server # Nest only +pnpm run build:client # Next.js only +pnpm run generate:swagger # swagger.json from server ``` -### Building Packages +## Production ```bash -# Build all packages pnpm run build - -# Build specific packages -pnpm run build:client -pnpm run build:server -pnpm run build:toolkit +pnpm run pm2 # PM2 for API + client +# or +sh scripts/deploy.sh ``` -### Testing +## Publishing ```bash -# Run all tests -pnpm test - -# Run tests for specific package -pnpm test --dir client -pnpm test --dir server +pnpm login +pnpm run release ``` -## Code Style - -- Follow the existing code style in the project -- Use TypeScript for type safety -- Write clear, concise commit messages -- Update documentation as needed - -## Pull Request Process - -1. Ensure your changes are well-tested -2. Update the README.md if you've changed functionality -3. Create a pull request with a clear title and description -4. Link any related issues in your pull request description -5. Be responsive to feedback during the review process - -## Reporting Issues - -If you find a bug or have a feature request, please [create an issue](https://github.com/fecommunity/reactpress/issues/new) on GitHub. Include as much detail as possible to help us understand and reproduce the problem. - -## Publishing Packages - -To publish packages to npm: - -1. Ensure you're logged into npm: `pnpm login` -2. Run the publish script: `pnpm run publish` -3. Follow the interactive prompts to select packages and version increments +Packages: root meta, **server**, client, toolkit, templates. +`@fecommunity/reactpress-cli` is used for `init` and optional Docker database only. -## License +## Architecture -By contributing to ReactPress, you agree that your contributions will be licensed under the MIT License. \ No newline at end of file +See [DESIGN.md](./DESIGN.md) and [TODO.md](./TODO.md). diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..d3c2edf --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,399 @@ +# ReactPress 系统架构设计 + +> 目标态架构说明:原则、系统视图、模块职责与目录结构。 +> 实施步骤见 [TODO.md](./TODO.md)。 + +--- + +## 1. 设计原则 + +| 原则 | 含义 | +|------|------| +| **边界清晰** | 平台运行时(API、DB、生命周期)归 `reactpress-cli`;本仓只做 Web 产品与契约 SDK。 | +| **契约优先** | 后端能力以 OpenAPI 为准;`toolkit` 生成类型与客户端;业务代码不手写 REST 路径。 | +| **目录守恒** | 保持现有顶层与 `client/src` 布局;优化模块内涵,不大规模搬迁文件夹。 | +| **单向依赖** | 依赖只能从上层指向下层(页面 → 门面 → SDK → API),禁止反向与环依赖。 | +| **渐进演进** | 允许删除已无价值的 `server/` 薄封装;其余变更以「改实现、不改路径」为主。 | + +--- + +## 2. 系统架构 + +### 2.1 上下文(系统与外部角色) + +```mermaid +flowchart TB + subgraph actors [角色] + V[访客] + A[管理员] + D[主题开发者] + end + + subgraph system [easy-blog-publish + reactpress-cli] + RP[ReactPress 系统] + end + + DB[(MySQL)] + + V -->|浏览内容| RP + A -->|管理内容| RP + D -->|开发主题| RP + RP --> DB +``` + +### 2.2 容器(主要运行时组件) + +```mermaid +flowchart TB + subgraph browser [浏览器] + WEB[client Next.js] + end + + subgraph node [Node 进程] + CLI[@fecommunity/reactpress-cli] + API[Nest API 内置] + end + + DB[(MySQL)] + + WEB -->|HTTPS /api| API + CLI -->|start/stop/config| API + CLI -->|Docker / 连接串| DB + API --> DB +``` + +| 容器 | 部署形态 | 默认端口 | +|------|----------|----------| +| **client** | Next.js(`server.js` 启动) | 3001 | +| **API** | CLI 内置 Nest | 3002(前缀 `/api`) | +| **MySQL** | CLI `init` 提供的 compose 或外部实例 | 3306 | + +### 2.3 仓库与平台(逻辑模块全景) + +```mermaid +flowchart TB + subgraph repo [本仓库 easy-blog-publish] + CLIENT[client 产品应用] + TK[toolkit 契约 SDK] + TMPL[templates 主题] + DOCS[docs 文档] + SCR[scripts 开发/发布脚本] + end + + subgraph platform [外部 reactpress-cli] + CLI[CLI] + API[Nest API] + end + + CFG[.reactpress/config.json] + ENV[.env] + + CLIENT --> TK + TMPL --> TK + SCR --> CLI + CLI --> CFG + CLI --> ENV + CLI --> API + TK -->|REST| API + ENV --> CLIENT + ENV --> TK +``` + +### 2.4 请求与配置(两条主链路) + +**业务请求链路:** + +```mermaid +sequenceDiagram + participant P as pages 页面 + participant C as components + participant R as providers 门面 + participant T as toolkit + participant A as Nest API + + P->>C: 渲染 + C->>R: 领域方法 + R->>T: Api 类 / HttpClient + T->>A: HTTP + A-->>T: JSON + T-->>R: DTO + R-->>C: 数据 + C-->>P: UI +``` + +**配置链路:** + +```mermaid +flowchart LR + JSON[.reactpress/config.json] + CLI[reactpress-cli] + ENV[.env] + TK[toolkit config] + NX[client next.config / server.js] + + JSON --> CLI + CLI -->|syncEnvFromConfig| ENV + ENV --> TK + ENV --> NX +``` + +--- + +## 3. 模块设计 + +### 3.1 模块一览 + +| 模块 | 位置 | 职责 | 依赖 | +|------|------|------|------| +| **reactpress-cli** | 外部 npm | 项目 `init`、API 启停、DB、生成 `.reactpress/*` 与 `.env` | — | +| **toolkit** | `toolkit/` | OpenAPI → TS API/类型;`config`;i18n;无 UI | CLI(swagger 源)、`.env` | +| **client** | `client/` | 前台、Admin、路由、组件、领域门面 | toolkit | +| **templates** | `templates/*` | 可安装主题(前台页面与样式) | toolkit(可选 client 对齐) | +| **docs** | `docs/` | 产品/开发文档 | 无运行时依赖 | +| **scripts** | `scripts/` | 本地 `dev`、发布等编排 | CLI | + +**终态移除**:`server/`(薄封装,职责并入 CLI,不再作为本仓模块)。 + +### 3.2 模块关系(依赖规则) + +```mermaid +flowchart TD + CLI[reactpress-cli] + API[Nest API] + TK[toolkit] + PR[client providers] + PG[client pages/components] + TMPL[templates] + + CLI --> API + TK -.->|契约来源| API + TMPL --> TK + PR --> TK + PG --> PR + PG --> TK + PG -.->|禁止| API + TK -.->|禁止| PG + CLI -.->|禁止 浏览器侧| PG +``` + +| 关系 | 允许 | 禁止 | +|------|------|------| +| client → toolkit | ✓ 唯一后端访问路径 | — | +| client → API 直连 | — | ✓ 绕过 toolkit/providers | +| toolkit → client | — | ✓ SDK 不依赖 UI | +| templates → client 核心 Admin | — | ✓ 主题不 fork 后台 | +| 本仓 → Nest 源码 | — | ✓ API 只在 CLI 演进 | + +### 3.3 toolkit 模块 + +```mermaid +flowchart LR + subgraph toolkit [toolkit] + API[src/api 生成] + TYP[src/types] + CFG[src/config] + UTL[src/utils] + LOC[src/locales] + GEN[scripts/generate] + end + + SW[swagger.json] --> GEN + GEN --> API + GEN --> TYP + ENV[.env] --> CFG + API --> HC[HttpClient / instance] +``` + +| 子模块 | 说明 | +|--------|------| +| `src/api/*` | 由 Swagger 生成,**不手改**;按资源分文件(Article、User…) | +| `src/config` | 读取 env,导出站点 URL、API 前缀等 | +| `src/types` | 对外 DTO | +| `scripts/` | 从 CLI 内置 server 拉取 swagger 并 regenerate | + +### 3.4 client 模块(内部分层) + +```mermaid +flowchart TB + subgraph presentation [表现层] + PAGES[pages/ 路由] + COMP[components/ UI] + LAY[layout/ 布局] + end + + subgraph application [应用层] + PROV[providers/ 领域门面] + HOOKS[hooks/] + CTX[context/] + end + + subgraph cross [横切] + HTTP[providers/http.ts] + UTIL[utils constants theme] + end + + TK[toolkit] + + PAGES --> COMP + PAGES --> LAY + PAGES --> PROV + COMP --> PROV + COMP --> HOOKS + COMP --> CTX + PROV --> HTTP + PROV --> TK + HTTP --> TK +``` + +| 子模块 | 职责 | 与其它模块关系 | +|--------|------|----------------| +| **pages/** | URL 与页面组装;保持薄 | 调用 `components`、`providers`、`layout` | +| **components/** | 展示与交互(Editor、Comment、Setting…) | 不直接请求 API;经 `providers` 或 `hooks` | +| **providers/** | 按领域封装 API(Article、User…) | 对外稳定入口;内部委托 `toolkit` | +| **providers/http.ts** | axios 实例、Token 拦截、baseURL | 与 toolkit `HttpClient` 对齐或包装 | +| **layout/** | 前台/后台布局、Admin 菜单 | 与 `pages/admin` 路由对应 | +| **hooks/** | 分页、设置等复用逻辑 | 可调用 `providers` | +| **context/** | 全局 UI 状态 | 不替代 `providers` 做数据请求 | + +**providers 与 toolkit 的分工**:`toolkit` 提供通用、生成的 HTTP 与类型;`providers` 提供业务语义方法名与参数组合,供页面/组件 import 路径稳定。 + +### 3.5 templates 模块 + +```mermaid +flowchart LR + TMPL[templates 主题包] + TK[toolkit] + CORE[client 核心] + + TMPL -->|前台页面/样式| TMPL + TMPL --> TK + CORE -->|Admin 不拆分| CORE +``` + +- 主题只覆盖 **前台** 展示;**Admin 始终留在 client**。 +- 与 client 共享同一 API 契约(经 toolkit)。 + +### 3.6 reactpress-cli 模块(外部,本仓不实现) + +| 能力 | 说明 | +|------|------| +| `init` | 生成 `.reactpress/config.json`、`.env`、compose 等 | +| `start` / `stop` / `status` | 管理内置 API 进程 | +| 内置 API | Nest 业务实现唯一源码仓 | +| 内置 swagger | toolkit `generate` 的契约源 | + +--- + +## 4. 目录设计 + +### 4.1 仓库顶层(终态) + +在**现有结构**上仅删除 `server/`: + +``` +easy-blog-publish/ +├── .reactpress/ # CLI 写入:config.json、compose、pid 等 +├── client/ # 主应用 → 见 4.2 +├── toolkit/ # 契约 SDK → 见 4.3 +├── templates/ # 主题包(如 hello-world、twentytwentyfive) +├── docs/ # Docusaurus 文档 +├── scripts/ # reactpress-dev、publish 等 +├── pnpm-workspace.yaml +└── package.json +``` + +### 4.2 client 目录 + +``` +client/ +├── pages/ # 路由(Pages Router,保持不变) +│ ├── index.tsx # 首页 +│ ├── article/ # 文章 +│ ├── knowledge/ # 知识库 +│ ├── admin/ # 管理后台 +│ └── … +├── public/ +├── server.js # 开发/生产启动 +├── next.config.js +└── src/ + ├── components/ # UI,按业务分子目录 + ├── providers/ # 领域 API 门面(article.ts、user.ts…) + ├── layout/ # AppLayout、AdminLayout + ├── hooks/ + ├── context/ + ├── utils/ + ├── constants/ + └── theme/ +``` + +| 路径 | 变更策略 | +|------|----------| +| `pages/`、`src/*` 一级目录 | **不更名、不迁到 `app/` 或 `features/`** | +| `src/providers/*.ts` | 仅改文件**内部**实现(委托 toolkit) | +| `src/components/*` | 新业务组件放入已有业务子目录 | + +### 4.3 toolkit 目录 + +``` +toolkit/ +├── src/ +│ ├── api/ # 生成:Article.ts、User.ts、HttpClient.ts… +│ ├── types/ +│ ├── config/ # env、global、i18n +│ ├── utils/ +│ └── locales/ +├── scripts/ # generate-swagger、resolve-swagger-input +└── dist/ # 构建产物(npm 入口) +``` + +### 4.4 配置相关路径(跨模块) + +| 路径 | 写入方 | 读取方 | +|------|--------|--------| +| `.reactpress/config.json` | CLI | CLI、人 | +| `.env` | CLI(由 config 同步) | toolkit、client 启动脚本 | +| `.env.example` | 仓库模板 | 开发者 | + +--- + +## 5. 模块协作示例 + +**管理员发布文章(简化):** + +```mermaid +flowchart LR + A[pages/admin/article/editor] --> B[components/ArticleEditor] + B --> C[providers/article] + C --> D[toolkit ArticleApi] + D --> E[Nest API] +``` + +**访客阅读文章:** + +```mermaid +flowchart LR + A[pages/article/id] --> B[components/MarkdownReader] + B --> C[providers/article] + C --> D[toolkit] + D --> E[Nest API] +``` + +**主题开发者定制首页:** + +```mermaid +flowchart LR + A[templates/.../pages] --> B[toolkit] + B --> C[Nest API] + D[client Admin] --> B +``` + +--- + +## 6. 参考 + +| 文档 | 内容 | +|------|------| +| [TODO.md](./TODO.md) | 重构任务与阶段 | +| [reactpress-cli](https://github.com/fecommunity/reactpress-cli) | 平台 CLI 与 API 源码 | + +默认地址:Web `http://localhost:3001` · API `http://localhost:3002/api` diff --git a/README-zh_CN.md b/README-zh_CN.md index b84810d..4bdbe36 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -3,7 +3,7 @@ ReactPress 标志 -

ReactPress 2.0

+

ReactPress 3.0

基于 React、Next.js 和 NestJS 构建的现代化全栈发布平台 @@ -29,14 +29,24 @@ ## 🌟 现代化发布平台 -**ReactPress 2.0** 是一个现代化的全栈发布平台,使开发者和内容创作者能够轻松构建专业博客、网站和内容管理系统。 +**ReactPress 3.0** 是一个现代化的全栈发布平台:装一个包、敲一条命令,一分钟拥有自己的 CMS。 [![ReactPress 海报](./public/poster.png)](https://gaoredu.com) +## ✨ 3.0 新特性 + +| 重点 | 说明 | +|------|------| +| **零配置** | `init` + `dev`,默认 Docker MySQL,无需手写 `.env` | +| **唯一入口** | `npm i -g @fecommunity/reactpress@3`,命令统一 `reactpress` | +| **极致 DX** | 交互菜单、`doctor`、`status`、dev 成功链接提示 | + +[3.0 文档](./docs/tutorial/tutorial-extras/reactpress-3-0.md) · [2.x 迁移](./docs/migration-2-to-3.md) + ## ✨ 主要特性 -### ⚡ 5分钟快速安装 -- **零配置设置**,具有智能默认值 +### ⚡ 一分钟零配置起站 +- **`reactpress init` + `reactpress dev`**,自动生成 `.reactpress/config.json` 与 `.env` - **WordPress 式安装向导**,提供直观的设置体验 - **自动数据库配置**,具有自动模式迁移 @@ -70,74 +80,59 @@ ## 🚀 快速开始 ### 📋 前置要求 -- Node.js >= 16.5.0 -- MySQL 数据库 -- pnpm 包管理器 +- Node.js >= 18.0.0 +- Docker(默认嵌入式 MySQL)或外部 MySQL +- pnpm(仅本仓库贡献者需要) -### 🏁 安装选项 +### 🏁 终端用户(唯一入口) -#### 选项 1:统一 CLI(推荐) ```bash -# 全局安装 ReactPress -npm install -g @fecommunity/reactpress - -# 启动服务 -reactpress server start -reactpress client start +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev +# 前台 http://localhost:3001 · 管理端 /admin · API /api/health ``` -#### 选项 2:独立服务 -```bash -# 安装并启动 ReactPress 服务器 -npx @fecommunity/reactpress-server - -# 独立安装并运行客户端 -npx @fecommunity/reactpress-client -``` - -## 📟 命令行界面 (CLI) - -ReactPress 提供了一个统一的命令行界面来管理服务器和客户端组件。 +无参数运行 `reactpress` 进入交互菜单。排错:`reactpress doctor`、`reactpress status`。 -### 统一 CLI 命令 +从 2.x 升级见 [迁移指南](./docs/migration-2-to-3.md)。 -全局安装 ReactPress 后,您可以使用 `reactpress` 命令: +### 🏁 本仓库开发(Monorepo,含 server/) ```bash -# 显示帮助 -reactpress --help - -# 启动服务器 -reactpress server start - -# 启动客户端 -reactpress client start - -# 使用 PM2 启动服务器 -reactpress server start --pm2 - -# 使用 PM2 启动客户端 -reactpress client start --pm2 +pnpm install +pnpm run dev # 零配置:自动 init + Docker MySQL + toolkit + API (3002) + 前端 (3001) ``` +可选:`pnpm run init` 仅准备环境不启动服务;`pnpm run docker:dev` 使用独立 compose 起 MySQL + nginx。 -### 独立包命令 +| 命令 | 说明 | +|------|------| +| `pnpm dev` | 一键本地全栈(推荐) | +| `pnpm dev:api` | 仅 API(`server/` nest watch) | +| `pnpm dev:client` | 仅 Next.js 前端 | +| `pnpm build` | 生产构建:toolkit → server → client | +| `pnpm start` | 生产模式同时起 API + 前端 | +| `pnpm run status` | 查看 API 进程与 HTTP 健康 | -您也可以直接使用各个包的命令: +## 📟 命令行界面 (CLI) ```bash -# 启动服务器 -npx @fecommunity/reactpress-server - -# 启动客户端 -npx @fecommunity/reactpress-client +npm i -g @fecommunity/reactpress@3 +``` -# 使用 PM2 启动服务器 -npx @fecommunity/reactpress-server --pm2 +| 命令 | 说明 | +|------|------| +| `reactpress` | 交互式菜单 | +| `reactpress init` | 零配置初始化 | +| `reactpress dev` | 全栈开发 | +| `reactpress dev --api-only` | 仅 API(Headless) | +| `reactpress doctor` | 环境诊断 | +| `reactpress status` | 运行状态 | +| `reactpress start` | 生产启动 | -# 使用 PM2 启动客户端 -npx @fecommunity/reactpress-client --pm2 -``` +本仓贡献者:`pnpm dev` 等同 `reactpress dev`。 ## 📦 包与组件 @@ -147,10 +142,11 @@ ReactPress 组织为**具有模块化包的 monorepo**: | 包 | 描述 | 版本 | |---------|-------------|---------| -| [`@fecommunity/reactpress`](.) | 主 CLI 和统一入口点 | 2.0.0 | -| [`@fecommunity/reactpress-client`](./client) | Next.js 12 前端应用 | 1.0.0 | -| [`@fecommunity/reactpress-server`](./server) | NestJS 6 后端 API | 1.0.0 | -| [`@fecommunity/reactpress-toolkit`](./toolkit) | 自动生成的 API 客户端 SDK | 1.0.0 | +| [`@fecommunity/reactpress`](./cli) | **3.0 主包** — 全局 `reactpress`,内置 API | 3.0.0 | +| [`@fecommunity/reactpress-client`](./client) | 进阶:仅部署前台 | 3.0.0 | +| [`@fecommunity/reactpress-server`](./server) | **Deprecated** — 请用主包内置 API | 3.0.0 | +| [`@fecommunity/reactpress-toolkit`](./toolkit) | OpenAPI 生成的 API SDK | 3.0.0 | +| [`@fecommunity/reactpress-cli`](./cli) | **Deprecated 别名** | 3.0.0 | ### 模板 @@ -159,53 +155,77 @@ ReactPress 组织为**具有模块化包的 monorepo**: | [`hello-world`](./templates/hello-world) | 用于快速原型设计的最小模板 | `@fecommunity/reactpress-template-hello-world` | | [`twentytwentyfive`](./templates/twentytwentyfive) | 功能丰富的博客模板 | `@fecommunity/reactpress-template-twentytwentyfive` | -## 🔧 配置 +## 🛠 研发流程 + +```mermaid +flowchart LR + subgraph init [首次] + A[pnpm install] --> B[pnpm init] + B --> C[.env + .reactpress/config.json] + end + subgraph dev [日常开发] + D[pnpm dev] --> E[toolkit build] + E --> F[server nest watch :3002] + F --> G[client next dev :3001] + end + subgraph ship [上线] + H[pnpm build] --> I[pnpm deploy 或 pm2] + end + init --> dev + dev --> ship +``` -在根目录中创建 `.env` 文件用于本地开发: +**改 API 契约后同步前端类型:** -```env -# 数据库配置 -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_USER=reactpress -DB_PASSWD=reactpress -DB_DATABASE=reactpress +```bash +pnpm run generate:swagger # 从 server 生成 swagger.json +pnpm run build:toolkit # 重新生成 toolkit 的 api/types +``` -# 客户端配置 -CLIENT_SITE_URL=http://localhost:3001 +**Docker 本地(MySQL + 反向代理):** -# 服务器配置 -SERVER_SITE_URL=http://localhost:3002 +```bash +pnpm docker:dev:start # MySQL :3306,入口 http://localhost:8080 +pnpm docker:dev:stop ``` +## 🔧 配置 + +3.0 以 **`.reactpress/config.json`** 为准,`.env` 由 CLI 同步。执行 `reactpress init` 后一般无需手改。详见 [配置说明](./docs/tutorial/tutorial-extras/config-intro.md)。 + ## 🚀 部署选项 ### 使用 Vercel 部署(推荐) [![使用 Vercel 部署](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/fecommunity/reactpress) -### PM2 部署(推荐) -```bash -# 全局安装 PM2 -npm install -g pm2 +### PM2 部署(推荐,自托管) -# 使用 PM2 启动 ReactPress 服务器 -npx @fecommunity/reactpress-server --pm2 +```bash +pnpm install +pnpm run build +pnpm run pm2 # API + 前端 +pm2 save +``` -# 使用 PM2 启动 ReactPress 客户端 -npx @fecommunity/reactpress-client --pm2 +或使用一键脚本(在服务器仓库根目录): -# 或者使用统一 CLI -reactpress server start --pm2 -reactpress client start --pm2 +```bash +sh scripts/deploy.sh ``` -### 传统部署(自托管) +### 传统部署(前台进程) + ```bash -# 构建生产版本 pnpm run build +pnpm run start # concurrently: API + client +``` + +### Docker 生产(DB + 前端容器,API 在宿主机) -# 启动生产服务器 -pnpm run start +```bash +pnpm run build +pnpm run start:api # 或 pm2:api,监听 3002 +docker compose -f docker-compose.prod.yml up -d ``` ## 🤝 贡献 diff --git a/README.md b/README.md index 2ab6a07..57add2a 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,16 @@ -

ReactPress Logo

- Modern Full-Stack Publishing Platform
- Built with React, Next.js, and NestJS + Your publishing platform — live in about a minute
+ One install. One command. Site, admin, and API ready to go.

[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fecommunity/reactpress/blob/master/LICENSE) [![NPM Version](https://img.shields.io/npm/v/@fecommunity/reactpress.svg?style=flat-square)](https://www.npmjs.com/package/@fecommunity/reactpress) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/fecommunity/reactpress/pulls) - [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg?style=flat-square)](http://www.typescriptlang.org/) - [![Next.js](https://img.shields.io/badge/Next.js-12-black?style=flat-square)](https://nextjs.org/) - [![NestJS](https://img.shields.io/badge/NestJS-6-red?style=flat-square)](https://nestjs.com/) - [![Deploy](https://img.shields.io/badge/Deploy-Vercel-blue?style=flat-square)](https://vercel.com/new/clone?repository-url=https://github.com/fecommunity/reactpress)

Report Bug @@ -23,285 +18,163 @@ Request Feature · 中文文档 + · + Live Demo

--- -## ✨ Philosophy: One Backend, All Your Fronts. +## What is ReactPress? -**ReactPress** is a modern full‑stack publishing platform built on the principle of **“One Backend, All Your Fronts.”** It enables developers to focus on frontend development while rapidly building professional‑grade blogs, websites, and content management systems. +**ReactPress** is a modern publishing platform for blogs, company sites, and content-driven products. Install once, run `init` and `dev`, and you get a public site, a full admin console, and an API — without wrestling with setup files or manual database wiring. -> A solution that empowers developers to easily build full‑stack applications. +> **One backend, many fronts.** Publish from one place; present your content on the web, in admin, or through your own apps. [![ReactPress Poster](./public/poster.png)](https://blog.gaoredu.com) --- -## 🎯 Why Choose ReactPress? +## What you can do -ReactPress is engineered for developers who need the publishing power of a traditional CMS combined with the component‑driven workflow of a modern full‑stack JavaScript framework. - -| Dimension | VuePress | WordPress | **ReactPress** | -| :--- | :--- | :--- | :--- | -| **Core Paradigm** | Static Site Generator (SSG) for content‑centric sites | Monolithic, server‑rendered CMS with a coupled frontend | **Decoupled, API‑first publishing platform** | -| **Primary Tech Stack** | Vue, Vite, Markdown | PHP, jQuery, Classic Themes | **React, Next.js (SSG/SSR), NestJS, TypeORM** | -| **Architectural Model** | Build‑time static generation; each site is a separate build | Tightly integrated theme/plugin system on a shared runtime | **Headless backend with fully independent, deployable frontends** | -| **State & Data Flow** | Pre‑rendered static data, content committed as code | Dynamic runtime state, database‑driven with admin UI | **Centralized API (REST/GraphQL) consumed by one or many clients** | -| **Deployment Target** | Static file hosts (CDN, Vercel, Netlify) | PHP‑compatible web servers (Apache/Nginx) | **Anywhere: backend on Node/PM2/Docker, frontends on any static or Node host** | -| **Styling & UI** | Scoped CSS, theme‑level overrides | PHP templates, theme stylesheets, inline styles | **Component‑scoped CSS‑in‑JS, design token system, fully themeable** | -| **Extensibility Model** | Custom themes and plugins (Vue components) | PHP hooks, actions, filters, and plugins | **Modular NestJS services, React component libraries, and build plugins** | -| **Development Experience** | Vue‑focused, markdown‑driven, simple CLI | File editors, browser‑based customizer, legacy codebase | **Type‑safe full‑stack IDE support, CLI toolchain, and hot‑reload** | -| **Ideal Use Case** | Documentation, technical blogs, marketing sites | Blogs, business websites, e‑commerce (with WooCommerce) | **Scalable content platforms, multi‑brand sites, custom publisher workflows** | +| Capability | What it means for you | +| :--- | :--- | +| **Go live in minutes** | Guided setup, automatic database, local URLs printed when ready | +| **Publish & manage content** | Posts, pages, media, and site settings from a familiar admin | +| **Brand your site** | Themes, light/dark mode, templates from minimal to full blog | +| **Speak your audience’s language** | Built-in Chinese and English interfaces | +| **Run your way** | All-in-one local dev, API-only headless mode, or production deploy | +| **Stay confident** | Built-in health checks, status, and clear error guidance | --- -## ✨ Core Features - -### ⚡ Rapid Deployment -- **Zero‑Configuration Setup** – based on intelligent defaults -- **WordPress‑Style Installation Wizard** – intuitive initialization process -- **Auto‑Database Configuration** – automatic database migrations - -### 🎨 Deep Customization -- **Dynamic Theme Switching** – support for light/dark modes -- **Component‑Level Customization** – modular architecture enables fine‑grained control -- **Internationalization Support** – Chinese and English interfaces - -### 🔧 Unified Development Experience -- **Monorepo Architecture** – modular package management -- **Full‑Stack TypeScript** – type safety across frontend and backend -- **PM2 Process Management** – production‑ready deployment solution +## Why ReactPress? -### 🚀 Modern Technology Stack -- **Frontend** – React 17 + Next.js 12 (Pages Router) -- **Backend** – NestJS 6 + modular architecture -- **Database** – MySQL + TypeORM -- **UI Components** – Ant Design v5 +| | Traditional CMS | Static site tools | **ReactPress** | +| :--- | :--- | :--- | :--- | +| **Getting started** | Server, plugins, manual config | Repo + build per site | **One CLI, ~1 minute to a working CMS** | +| **Content workflow** | Admin UI, coupled themes | Markdown in git | **Admin UI + optional code-first workflows** | +| **Flexibility** | Theme/plugin ecosystem | Fixed at build time | **Decoupled: one content hub, your choice of frontends** | +| **Fit** | General blogs & business sites | Docs & marketing pages | **Blogs, multi-site content, custom publisher flows** | --- -## 📸 Feature Preview - -### Installation Wizard -[![Installation Wizard](./public/install.png)](https://blog.gaoredu.com) +## ReactPress 3.0 at a glance -### Admin Dashboard -[![Admin Dashboard](./public/admin.png)](https://blog.gaoredu.com) +| | | +| :--- | :--- | +| **Zero hassle setup** | `reactpress init` then `reactpress dev` — environment prepared for you | +| **Single entry point** | One global package, one `reactpress` command for the full lifecycle | +| **Thoughtful DX** | Interactive menu, `doctor`, `status`, and actionable messages when something’s wrong | -### Demo Site -[![Demo Site](./public/demo.png)](https://blog.gaoredu.com) +[3.0 overview](./docs/tutorial/tutorial-extras/reactpress-3-0.md) · [Upgrade from 2.x](./docs/migration-2-to-3.md) --- -## 🚀 Quick Start +## Core capabilities -### 📋 Prerequisites -- Node.js >= 16.5.0 -- MySQL database -- `pnpm` package manager - -### 🏁 Installation Options - -#### Option 1: Unified CLI (Recommended) -```bash -# Install ReactPress globally -npm install -g @fecommunity/reactpress +### Launch fast +- **Guided first run** — installation wizard walks you through site basics +- **One command dev** — site, admin, and API up together +- **Database handled** — no manual schema or connection juggling for a typical start -# Start services -reactpress server start -reactpress client start -``` +### Publish with confidence +- **Rich content management** — create and organize what readers see +- **Media library** — upload and reuse assets from the admin +- **Roles & workflow** — manage the site without touching low-level config -#### Option 2: Independent Services -```bash -# Install and start ReactPress server -npx @fecommunity/reactpress-server +### Make it yours +- **Themes & appearance** — switch look and feel, including light/dark +- **Starter templates** — from minimal hello-world to a full blog layout +- **Extend when you need to** — headless API mode for custom frontends -# Install and run client independently -npx @fecommunity/reactpress-client -``` +### Operate in production +- **Build & start** — production lifecycle from the same CLI +- **Diagnostics** — `doctor` and `status` when you need to see what’s running +- **Deploy options** — cloud button, process manager, or your own hosting --- -## 📟 Command Line Interface (CLI) - -ReactPress provides a unified CLI for managing both server and client components. - -### Unified CLI Commands - -```bash -# Show help -reactpress --help +## See it in action -# Start the server -reactpress server start +### Admin dashboard +[![Admin Dashboard](./public/admin.png)](https://blog.gaoredu.com) -# Start the client -reactpress client start +### Demo site +[![Demo Site](./public/demo.png)](https://blog.gaoredu.com) -# Start server with PM2 -reactpress server start --pm2 +--- -# Start client with PM2 -reactpress client start --pm2 -``` +## Quick start -### Individual Package Commands +**You need:** Node.js 18+ and Docker (for the default bundled database), or your own MySQL. ```bash -# Start server -npx @fecommunity/reactpress-server - -# Start client -npx @fecommunity/reactpress-client - -# Start server with PM2 -npx @fecommunity/reactpress-server --pm2 - -# Start client with PM2 -npx @fecommunity/reactpress-client --pm2 +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev ``` ---- - -## 📦 Package Structure & Components - -ReactPress uses a **Modular Monorepo Architecture**: - -### Core Packages - -| Package | Description | Version | -|---------|-------------|---------| -| [`@fecommunity/reactpress`](.) | Main CLI and unified entry point | 2.0.0 | -| [`@fecommunity/reactpress-client`](./client) | Next.js 12 frontend application | 1.0.0 | -| [`@fecommunity/reactpress-server`](./server) | NestJS 6 backend API | 1.0.0 | -| [`@fecommunity/reactpress-toolkit`](./toolkit) | Auto‑generated API client SDK | 1.0.0 | - -### Templates - -| Template | Description | Package Name | -|----------|-------------|---------------| -| [`hello-world`](./templates/hello-world) | Minimal template for rapid prototyping | `@fecommunity/reactpress-template-hello-world` | -| [`twentytwentyfive`](./templates/twentytwentyfive) | Feature‑rich blog template | `@fecommunity/reactpress-template-twentytwentyfive` | +When dev is ready, open the URLs shown in the terminal (site, `/admin`, and API health). ---- - -## 🔧 Configuration +- Run `reactpress` with no args for the interactive menu +- Run `reactpress doctor` or `reactpress status` if something doesn’t look right -Create a `.env` file in the root directory for local development: +**Contributing to this repo?** See [README-zh_CN.md](./README-zh_CN.md) for the full local dev and deploy workflow. -```env -# Database Configuration -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_USER=reactpress -DB_PASSWD=reactpress -DB_DATABASE=reactpress - -# Client Configuration -CLIENT_SITE_URL=http://localhost:3001 - -# Server Configuration -SERVER_SITE_URL=http://localhost:3002 -``` +**Upgrading from 2.x?** [Migration guide](./docs/migration-2-to-3.md) --- -## 🚀 Development Workflow - -### Docker Development Environment - -```bash -# Start the development environment -pnpm docker:dev - -# Or use enhanced commands -pnpm docker:dev:start # Start services -pnpm docker:dev:stop # Stop services -pnpm docker:dev:restart # Restart services -pnpm docker:dev:status # Check service status -pnpm docker:dev:logs # View service logs -``` +## CLI essentials -The development environment includes: -- MySQL Database (port 3306) -- Nginx Reverse Proxy (port 8080) -- Client Development Server (port 3001) -- Server Development Server (port 3002) +| Command | What it does | +| :--- | :--- | +| `reactpress` | Interactive menu | +| `reactpress init` | Set up a new project | +| `reactpress dev` | Run site + admin + API locally | +| `reactpress dev --api-only` | API only (for custom frontends) | +| `reactpress doctor` | Check your environment | +| `reactpress status` | See what’s running | +| `reactpress build` / `reactpress start` | Production build and run | -Access your application at: `http://localhost:8080` +Full reference: [documentation](https://blog.gaoredu.com) · [Configuration](./docs/tutorial/tutorial-extras/config-intro.md) --- -## 🚀 Deployment Options +## Deploy -### Deploy with Vercel (Recommended for Startups) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/fecommunity/reactpress) -### PM2 Deployment (Recommended for Production) ```bash -# Install PM2 globally -npm install -g pm2 - -# Start ReactPress server with PM2 -npx @fecommunity/reactpress-server --pm2 - -# Start ReactPress client with PM2 -npx @fecommunity/reactpress-client --pm2 - -# Or use the unified CLI -reactpress server start --pm2 -reactpress client start --pm2 +npm i -g @fecommunity/reactpress@3 +reactpress build +reactpress start ``` -### Traditional Deployment (Self‑Managed) -```bash -# Build for production -pnpm run build - -# Start production servers -pnpm run start -``` +More deployment patterns are covered in [README-zh_CN.md](./README-zh_CN.md) and the [docs](./docs/tutorial/intro.md). --- -## 🤝 Contributing - -We welcome contributions of all kinds! Bug fixes, new features, documentation improvements, and translations are all appreciated. - -### 📋 Development Setup - -1. Fork the repository -2. Clone your fork: `git clone https://github.com/your-username/reactpress.git` -3. Install dependencies: `pnpm install` -4. Start development servers: `pnpm run dev` +## Contributing -### 📦 Publishing Packages +We welcome bug fixes, features, docs, and translations. -To publish packages to npm: +1. Fork and clone the repo +2. `pnpm install` +3. `pnpm run dev` -1. Ensure you're logged in: `pnpm login` -2. Run the publish script: `pnpm run release` -3. Follow the interactive prompts to select packages and version increments - -Please read our [Contributing Guide](https://github.com/fecommunity/reactpress/blob/master/CONTRIBUTING.md) for details on our code of conduct and development process. +See [Contributing Guide](https://github.com/fecommunity/reactpress/blob/master/CONTRIBUTING.md) for details. --- -## ❤️ Acknowledgments - -ReactPress is inspired by and built upon the work of many amazing open‑source projects: - -- [Next.js](https://github.com/vercel/next.js) – React Framework -- [NestJS](https://github.com/nestjs/nest) – Progressive Node.js Framework -- [Ant Design](https://github.com/ant-design/ant-design) – UI Design Language -- [TypeORM](https://github.com/typeorm/typeorm) – ORM for TypeScript and JavaScript +## Acknowledgments -We're grateful to the authors and contributors of these projects for their excellent work. +ReactPress stands on the shoulders of many excellent open-source communities. Thank you to everyone who builds and maintains the tools we rely on. --- -## 📈 Star History +## Star history -[![Star History Chart](https://api.star-history.com/svg?repos=fecommunity/reactpress&type=Date)](https://star-history.com/#fecommunity/reactpress&Date) \ No newline at end of file +[![Star History Chart](https://api.star-history.com/svg?repos=fecommunity/reactpress&type=Date)](https://star-history.com/#fecommunity/reactpress&Date) diff --git a/cli/LICENSE b/cli/LICENSE new file mode 100644 index 0000000..269cb6b --- /dev/null +++ b/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 FECommunity + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..fdaf2ac --- /dev/null +++ b/cli/README.md @@ -0,0 +1,47 @@ +# @fecommunity/reactpress-cli + +零配置一键初始化与管理 ReactPress CMS & 博客服务器。内置 NestJS 服务端,无需单独克隆 [fecommunity/reactpress](https://github.com/fecommunity/reactpress)。 + +完整文档与贡献指南见:[github.com/fecommunity/reactpress-cli](https://github.com/fecommunity/reactpress-cli) + +## 安装 + +```bash +npm install -g @fecommunity/reactpress-cli +``` + +全局命令为 `reactpress-cli`(与 npm 包名无关)。 + +> npm 上的无作用域包名 `reactpress-cli` 已被占用,本包发布为 `@fecommunity/reactpress-cli`。 + +## 快速开始 + +```bash +mkdir my-blog && cd my-blog +reactpress-cli init +reactpress-cli start +``` + +浏览器访问 `http://localhost:3002`(API 文档:`/api`)。 + +## 常用命令 + +| 命令 | 说明 | +|------|------| +| `reactpress-cli init [dir]` | 初始化项目 | +| `reactpress-cli start` | 启动服务(自动准备数据库) | +| `reactpress-cli stop` | 停止服务 | +| `reactpress-cli restart` | 重启服务 | +| `reactpress-cli status` | 查看状态 | +| `reactpress-cli config [key] [value]` | 查看/修改配置 | +| `reactpress-cli config server.port 3003 --apply` | 改端口并重启 | + +## 要求 + +- Node.js 18+ +- macOS / Linux / Windows +- 默认使用 Docker 运行嵌入式 MySQL;也可在 `.reactpress/config.json` 中配置外部数据库 + +## 许可证 + +MIT © FECommunity diff --git a/cli/bin/reactpress-cli-shim.js b/cli/bin/reactpress-cli-shim.js new file mode 100755 index 0000000..bf2dc2b --- /dev/null +++ b/cli/bin/reactpress-cli-shim.js @@ -0,0 +1,28 @@ +#!/usr/bin/env node + +/** + * @deprecated 3.0 起请使用 `reactpress`(@fecommunity/reactpress)。3.1 将移除此 bin。 + */ +const chalk = require('chalk'); +const { t } = require('../lib/i18n'); + +function mapLegacyArgv(argv) { + const [cmd, ...rest] = argv; + if (cmd === 'start') return ['server', 'start', ...rest]; + if (cmd === 'stop') return ['server', 'stop', ...rest]; + if (cmd === 'restart') return ['server', 'restart', ...rest]; + if (cmd === 'status') return ['server', 'status', ...rest]; + return argv; +} + +if (!process.env.REACTPRESS_SUPPRESS_DEPRECATION) { + console.warn( + chalk.yellow( + t('shim.deprecated') + ) + ); +} + +const mapped = mapLegacyArgv(process.argv.slice(2)); +process.argv = [process.argv[0], process.argv[1], ...mapped]; +require('./reactpress.js'); diff --git a/cli/bin/reactpress.js b/cli/bin/reactpress.js new file mode 100755 index 0000000..b18b28f --- /dev/null +++ b/cli/bin/reactpress.js @@ -0,0 +1,274 @@ +#!/usr/bin/env node + +/** + * ReactPress unified CLI — init, dev, build, server, docker, publish. + * Run without arguments for an interactive menu (Claude Code–style). + */ + +const { Command } = require('commander'); +const path = require('path'); +const chalk = require('chalk'); +const { ensureOriginalCwd, getMonorepoRoot } = require('../lib/root'); +const { ensureProjectEnvironment, initMonorepoProject } = require('../lib/bootstrap'); +const { runDev } = require('../lib/dev'); +const { runApiDev } = require('../lib/api-dev'); +const { runLifecycleCommand } = require('../lib/lifecycle'); +const { runDockerCommand } = require('../lib/docker'); +const { printUnifiedStatus } = require('../lib/status'); +const { runDoctor } = require('../lib/doctor'); +const { runDbBackup } = require('../lib/db-backup'); +const { runBuild } = require('../lib/build'); +const { startApiWithPm2 } = require('../lib/pm2'); +const { runNodeScript, runReactpressCli } = require('../lib/spawn'); +const { getClientBin } = require('../lib/paths'); +const { runInteractiveLoop } = require('../ui/interactive'); +const { t } = require('../lib/i18n'); + +const rootPkg = require(path.join(getMonorepoRoot(), 'package.json')); + +const program = new Command(); + +program + .name('reactpress') + .description(t('cli.description')) + .version(rootPkg.version); + +program + .command('init') + .description(t('cli.init.description')) + .argument('[directory]', t('cli.init.directory'), '.') + .option('-f, --force', t('cli.init.force')) + .action(async (directory, options) => { + const projectRoot = path.resolve(directory); + process.env.REACTPRESS_ORIGINAL_CWD = projectRoot; + const { isMonorepoCheckout } = require('../lib/bootstrap'); + if (isMonorepoCheckout(projectRoot)) { + const result = await initMonorepoProject(projectRoot, { force: !!options.force }); + console.log(`[reactpress] ${result.message}`); + process.exit(result.ok ? 0 : 1); + return; + } + const args = ['init', directory]; + if (options.force) args.push('--force'); + runReactpressCli(args, { cwd: projectRoot }); + }); + +program + .command('dev') + .description(t('cli.dev.description')) + .option('--api-only', t('cli.dev.apiOnly')) + .option('--client-only', t('cli.dev.clientOnly')) + .action(async (options) => { + const projectRoot = ensureOriginalCwd(); + try { + if (options.clientOnly) { + await runNodeScript(getClientBin(), [], { cwd: projectRoot }); + return; + } + if (options.apiOnly) { + await runApiDev(projectRoot); + return; + } + await runDev(projectRoot); + } catch (err) { + console.error(chalk.red('[reactpress]'), err.message || err); + process.exit(err.exitCode ?? 1); + } + }); + +const serverCmd = program.command('server').description(t('cli.server.description')); + +serverCmd + .command('start') + .description(t('cli.server.start.description')) + .option('--pm2', t('cli.server.start.pm2')) + .option('--bg', t('cli.server.start.bg')) + .action(async (options) => { + const projectRoot = ensureOriginalCwd(); + try { + if (options.pm2) { + await startApiWithPm2(projectRoot); + return; + } + const cmd = options.bg ? 'start:bg' : 'start'; + const code = await runLifecycleCommand(cmd, projectRoot); + process.exit(code ?? 0); + } catch (err) { + console.error(chalk.red('[reactpress]'), err.message || err); + process.exit(1); + } + }); + +serverCmd.command('stop').description(t('cli.server.stop')).action(async () => { + const code = await runLifecycleCommand('stop', ensureOriginalCwd()); + process.exit(code ?? 0); +}); + +serverCmd.command('restart').description(t('cli.server.restart')).action(async () => { + const code = await runLifecycleCommand('restart', ensureOriginalCwd()); + process.exit(code ?? 0); +}); + +serverCmd.command('status').description(t('cli.server.status')).action(async () => { + await runLifecycleCommand('status', ensureOriginalCwd()); +}); + +const clientCmd = program.command('client').description(t('cli.client.description')); + +clientCmd + .command('start') + .description(t('cli.client.start')) + .option('--pm2', t('cli.client.start.pm2')) + .action(async (options) => { + const args = options.pm2 ? ['--pm2'] : []; + await runNodeScript(getClientBin(), args, { cwd: ensureOriginalCwd() }); + }); + +program + .command('build') + .description(t('cli.build.description')) + .option('-t, --target ', t('cli.build.target'), 'all') + .action(async (options) => { + try { + await runBuild(options.target, ensureOriginalCwd()); + } catch (err) { + console.error(chalk.red('[reactpress]'), err.message || err); + process.exit(1); + } + }); + +const dockerCmd = program.command('docker').description(t('cli.docker.description')); + +dockerCmd + .command('up') + .description(t('cli.docker.up')) + .action(async () => { + await runDockerCommand('up', ensureOriginalCwd()); + }); + +dockerCmd + .command('down') + .alias('stop') + .description(t('cli.docker.down')) + .action(async () => { + await runDockerCommand('down', ensureOriginalCwd()); + }); + +dockerCmd + .command('start') + .description(t('cli.docker.start')) + .action(async () => { + await runDockerCommand('start', ensureOriginalCwd()); + }); + +dockerCmd.command('restart').description(t('cli.docker.restart')).action(async () => { + await runDockerCommand('restart', ensureOriginalCwd()); +}); + +dockerCmd.command('status').description(t('cli.docker.status')).action(async () => { + await runDockerCommand('status', ensureOriginalCwd()); +}); + +dockerCmd + .command('logs [service]') + .description(t('cli.docker.logs')) + .action(async (service) => { + await runDockerCommand('logs', ensureOriginalCwd(), service ? [service] : []); + }); + +program + .command('status') + .description(t('cli.status.description')) + .action(async () => { + await printUnifiedStatus(ensureOriginalCwd()); + }); + +program + .command('doctor') + .description(t('cli.doctor.description')) + .action(async () => { + const code = await runDoctor(ensureOriginalCwd()); + process.exit(code); + }); + +const dbCmd = program.command('db').description(t('cli.db.description')); + +dbCmd + .command('backup') + .description(t('cli.db.backup')) + .option('-o, --output ', t('cli.db.backup.output')) + .action(async (options) => { + try { + await runDbBackup(ensureOriginalCwd(), options.output); + } catch (err) { + console.error(chalk.red('[reactpress]'), err.message || err); + process.exit(1); + } + }); + +program + .command('publish') + .description(t('cli.publish.description')) + .option('--build', t('cli.publish.build')) + .option('--publish', t('cli.publish.publish')) + .action(async (options) => { + const prev = process.argv.slice(); + const args = [process.argv[0], process.argv[1]]; + if (options.build) args.push('--build'); + else if (options.publish) args.push('--publish'); + else args.push('--publish'); + process.argv = args; + try { + await require('../lib/publish').main(); + } catch (err) { + console.error(chalk.red('[reactpress]'), err.message || err); + process.exit(1); + } finally { + process.argv = prev; + } + }); + +program + .command('start') + .description(t('cli.start.description')) + .action(async () => { + const projectRoot = ensureOriginalCwd(); + const { spawn } = require('child_process'); + const code = await runLifecycleCommand('start', projectRoot); + if (code !== 0) process.exit(code); + const child = spawn('pnpm', ['run', '--dir', './client', 'start'], { + stdio: 'inherit', + shell: true, + cwd: projectRoot, + }); + child.on('close', (c) => process.exit(c ?? 0)); + }); + +program.on('--help', () => { + console.log(''); + console.log(chalk.gray(t('cli.help.examples'))); + console.log(t('cli.help.interactive')); + console.log(t('cli.help.dev')); + console.log(t('cli.help.init')); + console.log(t('cli.help.server')); + console.log(t('cli.help.status')); + console.log(t('cli.help.doctor')); + console.log(t('cli.help.docker')); + console.log(t('cli.help.build')); + console.log(t('cli.help.publish')); + console.log(''); +}); + +async function main() { + const argv = process.argv.slice(2); + if (argv.length === 0) { + await runInteractiveLoop(); + return; + } + program.parse(process.argv); +} + +main().catch((err) => { + console.error(chalk.red('[reactpress]'), err.message || err); + process.exit(1); +}); diff --git a/cli/lib/api-dev-runner.js b/cli/lib/api-dev-runner.js new file mode 100644 index 0000000..4bbde61 --- /dev/null +++ b/cli/lib/api-dev-runner.js @@ -0,0 +1,6 @@ +#!/usr/bin/env node +const { runApiDev } = require('./api-dev'); +const { ensureOriginalCwd } = require('./root'); + +ensureOriginalCwd(); +runApiDev(); diff --git a/cli/lib/api-dev.js b/cli/lib/api-dev.js new file mode 100644 index 0000000..d6434ad --- /dev/null +++ b/cli/lib/api-dev.js @@ -0,0 +1,85 @@ +const { spawn, spawnSync } = require('child_process'); +const path = require('path'); +const { ensureProjectEnvironment } = require('./bootstrap'); +const { isUsingMonorepoServer } = require('./paths'); +const { getMonorepoRoot } = require('./root'); +const { t } = require('./i18n'); + +let apiChild; + +function stopApiDev(projectRoot) { + if (apiChild && !apiChild.killed) { + apiChild.kill('SIGTERM'); + } + if (!isUsingMonorepoServer()) { + spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], { + cwd: projectRoot, + stdio: 'inherit', + }); + } +} + +function startApiDev(projectRoot) { + if (isUsingMonorepoServer()) { + console.log(t('apiDev.modeServer')); + apiChild = spawn('pnpm', ['run', '--dir', './server', 'dev'], { + cwd: projectRoot, + stdio: 'inherit', + shell: true, + env: { + ...process.env, + REACTPRESS_ORIGINAL_CWD: projectRoot, + }, + }); + } else { + console.log(t('apiDev.modeCli')); + const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], { + cwd: projectRoot, + stdio: 'inherit', + }); + if (start.status !== 0) { + process.exit(start.status ?? 1); + } + console.log(t('apiDev.startedByCli')); + process.stdin.resume(); + return; + } + + if (apiChild) { + apiChild.on('close', (code) => { + process.exit(code ?? 0); + }); + console.log(t('apiDev.ctrlCHint')); + console.log(t('apiDev.stopHint')); + } +} + +async function runApiDev(projectRoot = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) { + try { + await ensureProjectEnvironment(projectRoot); + } catch (err) { + console.error(t('dev.envFailed'), err.message || err); + process.exit(1); + } + + process.on('SIGINT', () => { + stopApiDev(projectRoot); + process.exit(0); + }); + process.on('SIGTERM', () => { + stopApiDev(projectRoot); + process.exit(0); + }); + + startApiDev(projectRoot); +} + +function getApiDevScriptPath() { + return path.join(__dirname, 'api-dev-runner.js'); +} + +module.exports = { + runApiDev, + stopApiDev, + getApiDevScriptPath, +}; diff --git a/cli/lib/bootstrap.js b/cli/lib/bootstrap.js new file mode 100644 index 0000000..909a2c9 --- /dev/null +++ b/cli/lib/bootstrap.js @@ -0,0 +1,114 @@ +const fs = require('fs'); +const path = require('path'); +const { pathToFileURL } = require('url'); +const { getMonorepoRoot, isMonorepoCheckout } = require('./root'); +const { getCliPackageRoot } = require('./paths'); +const { t } = require('./i18n'); + +async function importCliModule(relativePath) { + const modulePath = path.join(getCliPackageRoot(), 'dist', relativePath); + return import(pathToFileURL(modulePath).href); +} + +async function copyTemplateFile(src, dest) { + await fs.promises.mkdir(path.dirname(dest), { recursive: true }); + await fs.promises.copyFile(src, dest); +} + +async function initMonorepoProject(projectRoot, { force = false } = {}) { + const { getProjectPaths, getTemplatesDir } = await importCliModule('utils/paths.js'); + const { saveConfig, syncEnvFromConfig } = await importCliModule('services/config.js'); + const { ensureDatabase, ensureDatabaseHostPort } = await importCliModule('services/database.js'); + + const paths = getProjectPaths(projectRoot); + const templatesDir = getTemplatesDir(); + + if (fs.existsSync(paths.configPath) && !force) { + const config = await (await importCliModule('services/config.js')).loadConfig(projectRoot); + await ensureDatabaseHostPort(projectRoot, undefined, config); + const dbResult = await ensureDatabase(projectRoot, config); + if (!dbResult.ok) { + return { ok: false, projectRoot, message: dbResult.message }; + } + return { ok: true, projectRoot, message: t('bootstrap.configReady') }; + } + + await fs.promises.mkdir(paths.reactpressDir, { recursive: true }); + await copyTemplateFile( + path.join(templatesDir, 'docker-compose.yml'), + paths.dockerComposePath + ); + + const config = JSON.parse( + await fs.promises.readFile(path.join(templatesDir, 'config.default.json'), 'utf8') + ); + await saveConfig(projectRoot, config); + await syncEnvFromConfig(projectRoot, config); + + if (!fs.existsSync(paths.envPath) || force) { + await copyTemplateFile(path.join(templatesDir, 'env.default'), paths.envPath); + await syncEnvFromConfig(projectRoot, config); + } + + await ensureDatabaseHostPort(projectRoot, undefined, config); + const dbResult = await ensureDatabase(projectRoot, config); + + if (!dbResult.ok) { + return { + ok: true, + projectRoot, + message: t('bootstrap.projectDbPending', { message: dbResult.message }), + }; + } + + return { + ok: true, + projectRoot, + message: t('bootstrap.ready'), + }; +} + +async function ensureProjectEnvironment(projectRoot = getMonorepoRoot()) { + const root = path.resolve(projectRoot); + const { setProjectCwd } = await importCliModule('utils/cli-context.js'); + setProjectCwd(root); + + const { isReactPressProject, loadConfig } = await importCliModule('services/config.js'); + const { ensureDatabase, ensureDatabaseHostPort } = await importCliModule('services/database.js'); + + if (!(await isReactPressProject(root))) { + if (isMonorepoCheckout(root)) { + const result = await initMonorepoProject(root); + if (!result.ok) { + throw new Error(result.message || t('bootstrap.initFailed')); + } + return result; + } + + const { initProject } = await importCliModule('services/init.js'); + const result = await initProject({ directory: root, force: false }); + if (!result.ok) { + throw new Error(result.message || t('bootstrap.cliInitFailed')); + } + return result; + } + + const config = await loadConfig(root); + await ensureDatabaseHostPort(root, undefined, config); + const dbResult = await ensureDatabase(root, config); + if (!dbResult.ok) { + throw new Error( + t('bootstrap.dbNotReady', { + message: dbResult.message || t('bootstrap.dbPendingShort'), + }) + ); + } + + return { ok: true, projectRoot: root, message: t('bootstrap.dbReady') }; +} + +module.exports = { + ensureProjectEnvironment, + initMonorepoProject, + isMonorepoCheckout, +}; diff --git a/cli/lib/build.js b/cli/lib/build.js new file mode 100644 index 0000000..6036c42 --- /dev/null +++ b/cli/lib/build.js @@ -0,0 +1,75 @@ +const chalk = require('chalk'); +const { runSync } = require('./spawn'); +const { ensureOriginalCwd } = require('./root'); +const { t } = require('./i18n'); + +const FORBIDDEN_SCRIPTS = new Set(['build']); + +/** @type {Record} */ +const BUILD_STEPS = { + toolkit: [{ script: 'build:toolkit', labelKey: 'build.label.toolkit' }], + server: [{ script: 'build:server', labelKey: 'build.label.server' }], + client: [{ script: 'build:client', labelKey: 'build.label.client' }], + docs: [{ script: 'build:docs', labelKey: 'build.label.docs' }], + all: [ + { script: 'build:toolkit', labelKey: 'build.label.toolkit' }, + { script: 'build:server', labelKey: 'build.label.server' }, + { script: 'build:client', labelKey: 'build.label.client' }, + ], +}; + +const TARGETS = Object.keys(BUILD_STEPS); + +const buildChildEnv = { REACTPRESS_BUILD_ACTIVE: '1' }; + +async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) { + if (process.env.REACTPRESS_BUILD_ACTIVE === '1') { + throw new Error(t('build.recursive')); + } + + const steps = BUILD_STEPS[target]; + if (!steps) { + throw new Error( + t('build.unknownTarget', { + target, + available: TARGETS.join(', '), + }) + ); + } + + const total = steps.length; + const buildStarted = Date.now(); + + if (total > 1) { + console.log(t('build.plan', { total })); + } + + for (let i = 0; i < steps.length; i++) { + const { script, labelKey } = steps[i]; + if (FORBIDDEN_SCRIPTS.has(script)) { + throw new Error(t('build.forbiddenScript', { script })); + } + + const current = i + 1; + const label = t(labelKey); + const stepStarted = Date.now(); + + console.log(t('build.step', { current, total, label })); + try { + runSync('pnpm', ['run', script], { cwd: projectRoot, env: buildChildEnv }); + } catch (err) { + console.error(chalk.red(t('build.stepFailed', { current, total, label }))); + throw err; + } + + const seconds = ((Date.now() - stepStarted) / 1000).toFixed(1); + console.log(chalk.green(t('build.stepDone', { current, total, label, seconds }))); + } + + if (total > 1) { + const totalSeconds = ((Date.now() - buildStarted) / 1000).toFixed(1); + console.log(chalk.green(t('build.done', { seconds: totalSeconds }))); + } +} + +module.exports = { runBuild, TARGETS, BUILD_STEPS }; diff --git a/cli/lib/db-backup.js b/cli/lib/db-backup.js new file mode 100644 index 0000000..23152f0 --- /dev/null +++ b/cli/lib/db-backup.js @@ -0,0 +1,46 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); +const chalk = require('chalk'); +const { t } = require('./i18n'); + +function parseEnv(projectRoot) { + const envPath = path.join(projectRoot, '.env'); + const out = {}; + try { + const content = fs.readFileSync(envPath, 'utf8'); + for (const line of content.split('\n')) { + const m = line.match(/^([A-Z_]+)=(.*)$/); + if (m) out[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, ''); + } + } catch { + // ignore + } + return out; +} + +async function runDbBackup(projectRoot, outputPath) { + const env = parseEnv(projectRoot); + const host = env.DB_HOST || '127.0.0.1'; + const port = env.DB_PORT || '3306'; + const user = env.DB_USER || 'root'; + const password = env.DB_PASSWD || env.DB_PASSWORD || 'root'; + const database = env.DB_DATABASE || 'reactpress'; + const out = + outputPath || + path.join(projectRoot, `reactpress-backup-${new Date().toISOString().replace(/[:.]/g, '-')}.sql`); + + const cmd = `mysqldump -h ${host} -P ${port} -u ${user} -p${password} ${database}`; + console.log(chalk.cyan('[reactpress]'), t('db.backup.to', { path: out })); + try { + const dump = execSync(cmd, { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 }); + fs.writeFileSync(out, dump, 'utf8'); + console.log(chalk.green('[reactpress]'), t('db.backup.done')); + return out; + } catch (err) { + console.error(chalk.red('[reactpress]'), t('db.backup.fail')); + throw err; + } +} + +module.exports = { runDbBackup }; diff --git a/cli/lib/dev-banner.js b/cli/lib/dev-banner.js new file mode 100644 index 0000000..a683f02 --- /dev/null +++ b/cli/lib/dev-banner.js @@ -0,0 +1,40 @@ +const chalk = require('chalk'); +const { + loadClientSiteUrl, + loadServerSiteUrl, + getApiPrefix, + getHealthUrl, +} = require('./http'); +const { t } = require('./i18n'); + +function getDevUrls(projectRoot) { + const client = loadClientSiteUrl(projectRoot).replace(/\/$/, ''); + const server = loadServerSiteUrl(projectRoot).replace(/\/$/, ''); + const prefix = getApiPrefix(projectRoot).replace(/\/$/, '') || '/api'; + return { + site: client, + admin: `${client}/admin`, + api: `${server}${prefix}`, + swagger: `${server}${prefix}`, + health: getHealthUrl(projectRoot), + }; +} + +function printDevReadyBanner(projectRoot, { apiOnly = false } = {}) { + const urls = getDevUrls(projectRoot); + console.log(''); + console.log(chalk.bold.green(t('devBanner.ready'))); + console.log(chalk.gray(' ─────────────────────────────────────────')); + if (!apiOnly) { + console.log(` ${chalk.cyan(t('devBanner.site'))} ${chalk.underline(urls.site)}`); + console.log(` ${chalk.cyan(t('devBanner.admin'))} ${chalk.underline(urls.admin)}`); + } + console.log(` ${chalk.cyan('API')} ${chalk.underline(urls.api)}`); + console.log(` ${chalk.cyan('Swagger')} ${chalk.underline(urls.swagger)}`); + console.log(` ${chalk.cyan(t('devBanner.health'))} ${urls.health}`); + console.log(chalk.gray(' ─────────────────────────────────────────')); + console.log(chalk.gray(t('devBanner.hint'))); + console.log(''); +} + +module.exports = { getDevUrls, printDevReadyBanner }; diff --git a/cli/lib/dev.js b/cli/lib/dev.js new file mode 100644 index 0000000..c8b701b --- /dev/null +++ b/cli/lib/dev.js @@ -0,0 +1,122 @@ +const { spawn } = require('child_process'); +const path = require('path'); +const { runBuild } = require('./build'); +const { ensureProjectEnvironment } = require('./bootstrap'); +const { loadServerSiteUrl, loadClientSiteUrl, waitForHttp } = require('./http'); +const { printDevReadyBanner } = require('./dev-banner'); +const { ensureOriginalCwd } = require('./root'); +const { t } = require('./i18n'); + +const CLIENT_READY_TIMEOUT_MS = 120_000; + +const API_READY_TIMEOUT_MS = 180_000; + +function formatDevFailureHint() { + return [t('dev.nextSteps'), t('dev.nextDoctor'), t('dev.nextDocker'), t('dev.nextEnv')].join('\n'); +} + +let apiChild; +let webChild; +let shuttingDown = false; + +function shutdown(signal = 'SIGINT') { + if (shuttingDown) return; + shuttingDown = true; + if (webChild && !webChild.killed) { + webChild.kill(signal); + } + if (apiChild && !apiChild.killed) { + apiChild.kill(signal); + } +} + +async function buildToolkit(projectRoot) { + await runBuild('toolkit', projectRoot); +} + +async function startDevStack(projectRoot) { + const serverUrl = loadServerSiteUrl(projectRoot); + const apiDevRunner = path.join(__dirname, 'api-dev-runner.js'); + + console.log(t('dev.startingApi')); + apiChild = spawn(process.execPath, [apiDevRunner], { + stdio: 'inherit', + cwd: projectRoot, + env: { + ...process.env, + REACTPRESS_ORIGINAL_CWD: projectRoot, + }, + }); + + apiChild.on('close', (code) => { + if (shuttingDown) { + process.exit(code ?? 0); + return; + } + if (webChild && !webChild.killed) { + webChild.kill('SIGINT'); + } + process.exit(code ?? 1); + }); + + console.log(t('dev.waitingApi', { url: serverUrl })); + const ready = await waitForHttp(serverUrl, API_READY_TIMEOUT_MS); + if (!ready) { + console.error( + t('dev.apiTimeout', { seconds: API_READY_TIMEOUT_MS / 1000 }), + ); + shutdown('SIGINT'); + process.exit(1); + } + + printDevReadyBanner(projectRoot, { apiOnly: true }); + + console.log(t('dev.apiReady')); + webChild = spawn('pnpm', ['run', '--dir', './client', 'dev'], { + stdio: 'inherit', + shell: true, + cwd: projectRoot, + }); + + const clientUrl = loadClientSiteUrl(projectRoot); + waitForHttp(clientUrl, CLIENT_READY_TIMEOUT_MS).then((clientReady) => { + if (clientReady) { + printDevReadyBanner(projectRoot); + } else { + console.warn( + t('dev.clientSlow', { + seconds: CLIENT_READY_TIMEOUT_MS / 1000, + url: clientUrl, + }), + ); + } + }); + + webChild.on('close', (code) => { + if (!shuttingDown) { + shutdown('SIGINT'); + } + process.exit(code ?? 0); + }); +} + +async function runDev(projectRoot = ensureOriginalCwd()) { + process.on('SIGINT', () => shutdown('SIGINT')); + process.on('SIGTERM', () => shutdown('SIGTERM')); + + try { + const result = await ensureProjectEnvironment(projectRoot); + if (result.message) { + console.log(`[reactpress] ${result.message}`); + } + } catch (err) { + console.error(t('dev.envFailed'), err.message || err); + console.error(formatDevFailureHint()); + process.exit(1); + } + + await buildToolkit(projectRoot); + await startDevStack(projectRoot); +} + +module.exports = { runDev, buildToolkit, startDevStack }; diff --git a/cli/lib/docker.js b/cli/lib/docker.js new file mode 100644 index 0000000..ad6ac2d --- /dev/null +++ b/cli/lib/docker.js @@ -0,0 +1,157 @@ +const { spawn, execSync } = require('child_process'); +const path = require('path'); +const { ensureOriginalCwd } = require('./root'); +const { t } = require('./i18n'); + +function isDockerRunning() { + try { + execSync('docker info', { stdio: 'ignore' }); + return true; + } catch { + return false; + } +} + +function stopDockerServices(projectRoot) { + console.log(t('docker.stopping')); + try { + execSync('docker-compose -f docker-compose.dev.yml down', { + stdio: 'inherit', + cwd: projectRoot, + }); + console.log(t('docker.stopped')); + } catch (error) { + console.error(t('docker.stopFailed'), error.message); + throw error; + } +} + +function startDockerServices(projectRoot) { + console.log(t('docker.starting')); + if (!isDockerRunning()) { + throw new Error(t('docker.notRunning')); + } + execSync('docker-compose -f docker-compose.dev.yml up -d', { + stdio: 'inherit', + cwd: projectRoot, + }); + console.log(t('docker.started')); +} + +async function waitForMysql(maxAttempts = 30) { + console.log(t('docker.waitingMysql')); + let attempts = 0; + while (attempts < maxAttempts) { + try { + execSync('docker exec reactpress_db mysql -u reactpress -preactpress -e "SELECT 1"', { + stdio: 'ignore', + }); + console.log(t('docker.mysqlReady')); + return true; + } catch { + attempts += 1; + if (attempts % 5 === 0) { + console.log(t('docker.waitingMysqlProgress', { attempts, max: maxAttempts })); + } + await new Promise((r) => setTimeout(r, 1000)); + } + } + console.error(t('docker.mysqlTimeout')); + return false; +} + +async function dockerStartWithDev(projectRoot) { + startDockerServices(projectRoot); + const ready = await waitForMysql(); + if (!ready) { + throw new Error(t('docker.mysqlNotReady')); + } + + const { buildToolkit } = require('./dev'); + await buildToolkit(projectRoot); + + const apiRunner = path.join(__dirname, 'api-dev-runner.js'); + console.log(t('docker.startDevStack')); + console.log(t('docker.visitUrls')); + + return new Promise((resolve, reject) => { + const child = spawn( + 'npx', + [ + 'concurrently', + '-n', + 'api,web', + '-c', + 'blue,green', + `node "${apiRunner}"`, + 'pnpm run --dir ./client dev', + ], + { + stdio: 'inherit', + shell: true, + cwd: projectRoot, + env: { + ...process.env, + REACTPRESS_ORIGINAL_CWD: projectRoot, + }, + } + ); + + child.on('error', reject); + child.on('close', (code) => { + if (code !== 0) { + reject(Object.assign(new Error(t('docker.devProcessExit', { code })), { exitCode: code })); + return; + } + resolve(); + }); + }); +} + +async function runDockerCommand(command, projectRoot = ensureOriginalCwd(), extraArgs = []) { + switch (command) { + case 'up': + startDockerServices(projectRoot); + await waitForMysql(); + return; + case 'down': + stopDockerServices(projectRoot); + return; + case 'start': + await dockerStartWithDev(projectRoot); + return; + case 'stop': + stopDockerServices(projectRoot); + return; + case 'restart': + stopDockerServices(projectRoot); + await new Promise((r) => setTimeout(r, 2000)); + startDockerServices(projectRoot); + await waitForMysql(); + return; + case 'status': + execSync('docker-compose -f docker-compose.dev.yml ps', { + stdio: 'inherit', + cwd: projectRoot, + }); + return; + case 'logs': { + const service = extraArgs[0] || ''; + execSync(`docker-compose -f docker-compose.dev.yml logs -f ${service}`.trim(), { + stdio: 'inherit', + cwd: projectRoot, + }); + return; + } + default: + throw new Error(t('docker.unknownCommand', { command })); + } +} + +module.exports = { + runDockerCommand, + startDockerServices, + stopDockerServices, + waitForMysql, + isDockerRunning, +}; diff --git a/cli/lib/doctor.js b/cli/lib/doctor.js new file mode 100644 index 0000000..8798fc6 --- /dev/null +++ b/cli/lib/doctor.js @@ -0,0 +1,210 @@ +const fs = require('fs'); +const net = require('net'); +const path = require('path'); +const { execSync } = require('child_process'); +const chalk = require('chalk'); +const { getHealthUrl, checkHealth } = require('./http'); +const { isDockerRunning } = require('./docker'); +const { envFileStatus } = require('./status'); +const { t } = require('./i18n'); + +function checkNodeVersion() { + const major = parseInt(process.versions.node.split('.')[0], 10); + if (major >= 18) { + return { ok: true, message: `Node.js ${process.version}` }; + } + return { + ok: false, + message: t('doctor.nodeBad', { version: process.version }), + fix: t('doctor.nodeFix'), + }; +} + +function checkDocker() { + if (isDockerRunning()) { + return { ok: true, message: t('doctor.dockerOk') }; + } + return { + ok: false, + message: t('doctor.dockerBad'), + fix: t('doctor.dockerFix'), + }; +} + +function parseEnv(projectRoot) { + const envPath = path.join(projectRoot, '.env'); + const out = {}; + try { + const content = fs.readFileSync(envPath, 'utf8'); + for (const line of content.split('\n')) { + const m = line.match(/^([A-Z_]+)=(.*)$/); + if (m) out[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, ''); + } + } catch { + // ignore + } + return out; +} + +function checkPort(port, host = '127.0.0.1') { + return new Promise((resolve) => { + const socket = net.createConnection({ port, host }, () => { + socket.destroy(); + resolve(true); + }); + socket.on('error', () => resolve(false)); + socket.setTimeout(1000, () => { + socket.destroy(); + resolve(false); + }); + }); +} + +async function checkPorts(projectRoot) { + const env = parseEnv(projectRoot); + const apiPort = parseInt(env.SERVER_PORT || '3002', 10); + const clientPort = parseInt(env.CLIENT_PORT || '3001', 10); + const [apiBusy, clientBusy] = await Promise.all([checkPort(apiPort), checkPort(clientPort)]); + const issues = []; + if (apiBusy) issues.push(t('doctor.portApiBusy', { port: apiPort })); + if (clientBusy) issues.push(t('doctor.portClientBusy', { port: clientPort })); + if (issues.length) { + return { + ok: false, + message: issues.join('; '), + fix: t('doctor.portFix'), + }; + } + return { + ok: true, + message: t('doctor.portOk', { apiPort, clientPort }), + }; +} + +async function checkDatabase(projectRoot) { + const env = parseEnv(projectRoot); + const host = env.DB_HOST || '127.0.0.1'; + const port = parseInt(env.DB_PORT || '3306', 10); + const user = env.DB_USER || 'root'; + const password = env.DB_PASSWD || env.DB_PASSWORD || 'root'; + const database = env.DB_DATABASE || 'reactpress'; + + return new Promise((resolve) => { + let mysql; + try { + mysql = require('mysql2/promise'); + } catch { + try { + mysql = require(path.join(projectRoot, 'server/node_modules/mysql2/promise')); + } catch { + resolve({ + ok: false, + message: t('doctor.dbNoMysql2'), + fix: t('doctor.dbMysql2Fix'), + }); + return; + } + } + + mysql + .createConnection({ host, port, user, password, database, connectTimeout: 5000 }) + .then(async (conn) => { + await conn.ping(); + await conn.end(); + resolve({ + ok: true, + message: t('doctor.dbOk', { host, port, database }), + }); + }) + .catch((err) => { + resolve({ + ok: false, + message: t('doctor.dbBad', { error: err.message }), + fix: t('doctor.dbFix'), + }); + }); + }); +} + +async function checkApiHealth(projectRoot) { + const healthUrl = getHealthUrl(projectRoot); + const result = await checkHealth(healthUrl); + if (result.ok) { + return { ok: true, message: t('doctor.apiOk', { url: healthUrl }) }; + } + return { + ok: false, + message: t('doctor.apiBad', { url: healthUrl }), + fix: t('doctor.apiFix'), + }; +} + +function checkPnpm() { + try { + const v = execSync('pnpm -v', { encoding: 'utf8' }).trim(); + return { ok: true, message: `pnpm ${v}` }; + } catch { + return { + ok: false, + message: t('doctor.pnpmBad'), + fix: t('doctor.pnpmFix'), + }; + } +} + +async function runDoctor(projectRoot) { + const env = envFileStatus(projectRoot); + const checks = [ + { name: 'Node.js', run: () => checkNodeVersion() }, + { name: 'pnpm', run: () => checkPnpm() }, + { + name: t('doctor.check.config'), + run: () => ({ + ok: env.config, + message: env.config ? t('doctor.configOk') : t('doctor.configBad'), + fix: t('doctor.configFix'), + }), + }, + { + name: t('doctor.check.env'), + run: () => ({ + ok: env.env, + message: env.env ? t('doctor.envOk') : t('doctor.envBad'), + fix: t('doctor.envFix'), + }), + }, + { name: 'Docker', run: () => checkDocker() }, + { name: t('doctor.check.ports'), run: () => checkPorts(projectRoot) }, + { name: t('doctor.check.database'), run: () => checkDatabase(projectRoot) }, + { name: t('doctor.check.api'), run: () => checkApiHealth(projectRoot) }, + ]; + + console.log(''); + console.log(chalk.bold.cyan(' ReactPress Doctor')); + console.log(chalk.gray(t('doctor.project', { path: projectRoot }))); + console.log(chalk.gray(' ─────────────────────────────────────')); + + let failed = 0; + for (const { name, run } of checks) { + const result = await run(); + const icon = result.ok ? chalk.green('✓') : chalk.red('✗'); + console.log(` ${icon} ${chalk.bold(name)} ${result.message}`); + if (!result.ok && result.fix) { + console.log(chalk.yellow(` → ${result.fix}`)); + failed += 1; + } else if (!result.ok) { + failed += 1; + } + } + + console.log(chalk.gray(' ─────────────────────────────────────')); + if (failed === 0) { + console.log(chalk.green(t('doctor.allPass'))); + } else { + console.log(chalk.yellow(t('doctor.failed', { count: failed }))); + } + console.log(''); + return failed === 0 ? 0 : 1; +} + +module.exports = { runDoctor }; diff --git a/cli/lib/http.js b/cli/lib/http.js new file mode 100644 index 0000000..23b23e1 --- /dev/null +++ b/cli/lib/http.js @@ -0,0 +1,147 @@ +const fs = require('fs'); +const http = require('http'); +const path = require('path'); + +function loadServerSiteUrl(projectRoot) { + const envPath = path.join(projectRoot, '.env'); + try { + const content = fs.readFileSync(envPath, 'utf8'); + const match = content.match(/^SERVER_SITE_URL=(.+)$/m); + if (match) { + return match[1].trim().replace(/^['"]|['"]$/g, ''); + } + } catch { + // ignore + } + return 'http://localhost:3002'; +} + +function loadClientSiteUrl(projectRoot) { + const envPath = path.join(projectRoot, '.env'); + try { + const content = fs.readFileSync(envPath, 'utf8'); + const match = content.match(/^CLIENT_SITE_URL=(.+)$/m); + if (match) { + return match[1].trim().replace(/^['"]|['"]$/g, ''); + } + } catch { + // ignore + } + return 'http://localhost:3001'; +} + +function getApiPrefix(projectRoot) { + const envPath = path.join(projectRoot, '.env'); + try { + const content = fs.readFileSync(envPath, 'utf8'); + const match = content.match(/^SERVER_API_PREFIX=(.+)$/m); + if (match) { + return match[1].trim().replace(/^['"]|['"]$/g, ''); + } + } catch { + // ignore + } + return '/api'; +} + +function getHealthUrl(projectRoot) { + const base = loadServerSiteUrl(projectRoot).replace(/\/$/, ''); + const prefix = getApiPrefix(projectRoot).replace(/\/$/, ''); + return `${base}${prefix}/health`; +} + +function checkHealth(url, timeoutMs = 3000) { + return new Promise((resolve) => { + let parsed; + try { + parsed = new URL(url); + } catch { + resolve({ ok: false }); + return; + } + const port = parsed.port || (parsed.protocol === 'https:' ? 443 : 80); + const req = http.request( + { + hostname: parsed.hostname, + port, + path: parsed.pathname + (parsed.search || ''), + method: 'GET', + timeout: timeoutMs, + }, + (res) => { + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => { + const ok = res.statusCode === 200; + let data = null; + try { + data = JSON.parse(body); + } catch { + // ignore + } + resolve({ ok, statusCode: res.statusCode, data }); + }); + } + ); + req.on('timeout', () => { + req.destroy(); + resolve({ ok: false }); + }); + req.on('error', () => resolve({ ok: false })); + req.end(); + }); +} + +function isHttpResponding(url, timeoutMs = 2000) { + return new Promise((resolve) => { + let parsed; + try { + parsed = new URL(url); + } catch { + resolve(false); + return; + } + + const port = parsed.port || (parsed.protocol === 'https:' ? 443 : 80); + const req = http.request( + { + hostname: parsed.hostname, + port, + path: parsed.pathname || '/', + method: 'GET', + timeout: timeoutMs, + }, + (res) => resolve(res.statusCode > 0) + ); + + req.on('timeout', () => { + req.destroy(); + resolve(false); + }); + req.on('error', () => resolve(false)); + req.end(); + }); +} + +async function waitForHttp(url, timeoutMs = 120_000, intervalMs = 500) { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (await isHttpResponding(url)) { + return true; + } + await new Promise((r) => setTimeout(r, intervalMs)); + } + return false; +} + +module.exports = { + loadServerSiteUrl, + loadClientSiteUrl, + getApiPrefix, + getHealthUrl, + checkHealth, + isHttpResponding, + waitForHttp, +}; diff --git a/cli/lib/i18n/index.js b/cli/lib/i18n/index.js new file mode 100644 index 0000000..afeedd0 --- /dev/null +++ b/cli/lib/i18n/index.js @@ -0,0 +1,32 @@ +const { STRINGS } = require('./strings'); + +function resolveLocale() { + const raw = process.env.REACTPRESS_LANG || process.env.LANG || 'en'; + const code = String(raw).split(/[._-]/)[0].toLowerCase(); + return code === 'zh' ? 'zh' : 'en'; +} + +let locale = resolveLocale(); + +/** + * @param {string} key + * @param {Record} [vars] + */ +function t(key, vars = {}) { + const table = STRINGS[locale] || STRINGS.en; + let text = table[key] ?? STRINGS.en[key] ?? key; + for (const [name, value] of Object.entries(vars)) { + text = text.replace(new RegExp(`\\{${name}\\}`, 'g'), String(value)); + } + return text; +} + +function getLocale() { + return locale; +} + +function setLocale(next) { + locale = next === 'zh' ? 'zh' : 'en'; +} + +module.exports = { t, getLocale, setLocale, resolveLocale }; diff --git a/cli/lib/i18n/strings.js b/cli/lib/i18n/strings.js new file mode 100644 index 0000000..1001fff --- /dev/null +++ b/cli/lib/i18n/strings.js @@ -0,0 +1,587 @@ +/** CLI user-facing strings — English first, Chinese via REACTPRESS_LANG=zh or zh LANG */ +const STRINGS = { + en: { + 'cli.description': 'ReactPress CLI — init, dev, build, deploy, and publish', + 'cli.init.description': 'Initialize project (.reactpress/config.json + .env + Docker MySQL)', + 'cli.init.directory': 'Project directory', + 'cli.init.force': 'Overwrite existing config', + 'cli.dev.description': 'Zero-config dev: env check + toolkit build + API + frontend', + 'cli.dev.apiOnly': 'API only (watch)', + 'cli.dev.clientOnly': 'Frontend only', + 'cli.server.description': 'Manage API service', + 'cli.server.start.description': 'Start API (wait until HTTP ready)', + 'cli.server.start.pm2': 'Start with PM2 (production)', + 'cli.server.start.bg': 'Start in background without waiting for HTTP', + 'cli.server.stop': 'Stop API', + 'cli.server.restart': 'Restart API', + 'cli.server.status': 'API status', + 'cli.client.description': 'Manage frontend', + 'cli.client.start': 'Start Next.js client', + 'cli.client.start.pm2': 'Start with PM2', + 'cli.build.description': 'Build production artifacts', + 'cli.docker.description': 'Docker dev environment (MySQL + nginx)', + 'cli.docker.up': 'Start Docker services and wait for MySQL', + 'cli.docker.down': 'Stop Docker services', + 'cli.docker.start': 'Start Docker + full-stack dev (API + frontend)', + 'cli.docker.restart': 'Restart Docker services', + 'cli.docker.status': 'Docker container status', + 'cli.docker.logs': 'Docker logs (db | nginx)', + 'cli.status.description': 'Project, API, frontend, and Docker status', + 'cli.doctor.description': 'Diagnose Node, Docker, ports, database, and API health', + 'cli.db.description': 'Database operations', + 'cli.db.backup': 'Backup current project database with mysqldump', + 'cli.db.backup.output': 'Output SQL file path', + 'cli.publish.description': 'Build and publish npm packages (interactive)', + 'cli.publish.build': 'Build all packages only', + 'cli.publish.publish': 'Interactive publish', + 'cli.start.description': 'Production: start API + frontend', + 'cli.help.examples': 'Examples:', + 'cli.help.interactive': ' reactpress Interactive menu', + 'cli.help.dev': ' reactpress dev Zero-config full-stack dev', + 'cli.help.init': ' reactpress init --force Re-initialize config', + 'cli.help.server': ' reactpress server start Start API', + 'cli.help.status': ' reactpress status Combined status', + 'cli.help.doctor': ' reactpress doctor Environment diagnostics', + 'cli.help.docker': ' reactpress docker start Docker + full stack', + 'cli.help.build': ' reactpress build -t client Build one target (toolkit|server|client|docs|all)', + 'cli.help.publish': ' reactpress publish Publish npm packages', + 'cli.build.target': 'Build target: toolkit | server | client | docs | all', + 'banner.subtitle': ' · Full-stack publishing CLI ', + 'menu.dev': 'Zero-config dev (env + DB + API + frontend)', + 'menu.init': 'Initialize project (.reactpress + .env + database)', + 'menu.status': 'View project status', + 'menu.doctor': 'Environment diagnostics (doctor)', + 'menu.devApi': 'API only (dev watch)', + 'menu.devClient': 'Frontend only', + 'menu.serverStart': 'Start API (production background)', + 'menu.serverStop': 'Stop API', + 'menu.serverRestart': 'Restart API', + 'menu.build': 'Build (toolkit → server → client)', + 'menu.buildTarget': 'What do you want to build?', + 'menu.buildAll': 'All (toolkit → server → client)', + 'menu.dockerStart': 'Docker dev (DB + nginx + full stack)', + 'menu.dockerUp': 'Docker: database only', + 'menu.dockerStop': 'Stop Docker services', + 'menu.openAdmin': 'Open admin in browser', + 'menu.publish': 'Publish npm packages (interactive)', + 'menu.exit': 'Exit', + 'menu.prompt': 'Choose an action', + 'menu.back': 'Return to main menu?', + 'menu.retry': 'Return to main menu and retry?', + 'menu.startingDev': 'Starting full-stack dev…', + 'menu.initProject': 'Initializing project…', + 'menu.done': 'Done', + 'menu.opening': 'Opening {url}', + 'menu.goodbye': ' Goodbye.', + 'dev.startingApi': '[reactpress] Starting API (first run may install deps)…', + 'dev.waitingApi': '[reactpress] Waiting for API: {url}', + 'dev.apiTimeout': '[reactpress] API not ready within {seconds}s.\n → Run reactpress doctor\n → Embedded MySQL: reactpress docker up\n → Check DB_* and SERVER_SITE_URL in .env', + 'dev.apiReady': '[reactpress] API ready, starting frontend…', + 'dev.clientSlow': '[reactpress] Frontend not responding within {seconds}s; it may still be compiling. Visit {url} later', + 'dev.envFailed': '[reactpress] Environment setup failed:', + 'dev.toolkitFailed': 'toolkit build failed with exit code: {code}', + 'dev.nextSteps': 'Suggested next steps:', + 'dev.nextDoctor': ' → reactpress doctor Diagnostics', + 'dev.nextDocker': ' → reactpress docker up Start embedded MySQL', + 'dev.nextEnv': ' → Check DB_* and SERVER_SITE_URL in .env', + 'devBanner.ready': ' ✓ ReactPress dev environment is ready', + 'devBanner.site': 'Site', + 'devBanner.admin': 'Admin', + 'devBanner.health': 'Health', + 'devBanner.hint': ' Diagnostics: reactpress doctor · Status: reactpress status', + 'doctor.nodeBad': 'Node.js {version} (requires ≥ 18)', + 'doctor.nodeFix': 'Install Node.js 18+: https://nodejs.org/', + 'doctor.dockerOk': 'Docker engine is available', + 'doctor.dockerBad': 'Docker is not running or unavailable', + 'doctor.dockerFix': 'Install and start Docker: https://docs.docker.com/get-docker/ , then run reactpress docker up; or use external MySQL in config.json', + 'doctor.portApiBusy': 'API port {port} is in use', + 'doctor.portClientBusy': 'Frontend port {port} is in use', + 'doctor.portFix': 'Change SERVER_PORT / CLIENT_PORT in .env, or stop the blocking process', + 'doctor.portOk': 'Ports {apiPort} (API) and {clientPort} (frontend) are available', + 'doctor.dbNoMysql2': 'mysql2 not installed; cannot check database', + 'doctor.dbMysql2Fix': 'Run pnpm install at monorepo root', + 'doctor.dbOk': 'MySQL {host}:{port}/{database} connected', + 'doctor.dbBad': 'Database connection failed: {error}', + 'doctor.dbFix': 'Run reactpress docker up or check DB_* in .env', + 'doctor.apiOk': 'API health check passed ({url})', + 'doctor.apiBad': 'API health check failed ({url})', + 'doctor.apiFix': 'Run reactpress server start or reactpress dev', + 'doctor.pnpmBad': 'pnpm not found', + 'doctor.pnpmFix': 'npm i -g pnpm, or enable corepack at monorepo root', + 'doctor.check.config': 'Config file', + 'doctor.check.env': 'Environment', + 'doctor.check.ports': 'Ports', + 'doctor.check.database': 'Database', + 'doctor.check.api': 'API health', + 'doctor.configOk': '.reactpress/config.json exists', + 'doctor.configBad': 'Missing .reactpress/config.json', + 'doctor.configFix': 'Run reactpress init', + 'doctor.envOk': '.env exists', + 'doctor.envBad': 'Missing .env', + 'doctor.envFix': 'Run reactpress init or reactpress config --apply', + 'doctor.project': ' Project: {path}', + 'doctor.allPass': ' All checks passed. You can start developing.', + 'doctor.failed': ' {count} item(s) need attention.', + 'status.title': ' ReactPress project status', + 'status.dir': ' Project {path}', + 'status.apiSource': ' API source {source}', + 'status.apiSource.monorepo': 'monorepo server/', + 'status.apiSource.bundle': 'reactpress-cli', + 'status.configOk': '.reactpress/config.json ✓', + 'status.configBad': 'not initialized', + 'status.envOk': '.env ✓', + 'status.envBad': 'missing .env', + 'status.apiOnline': 'online', + 'status.apiOffline': 'offline', + 'status.apiUnreachable': '{url} (offline or not started)', + 'status.dbUp': 'connected', + 'status.dbDown': 'unavailable', + 'status.pidRunning': '(running)', + 'status.frontend': ' Frontend', + 'status.docker': ' Docker', + 'status.dockerUp': 'available', + 'status.dockerDown': 'not running', + 'bootstrap.configReady': 'Config exists and database is ready.', + 'bootstrap.projectDbPending': 'Project created, but database is not ready: {message}. Start Docker and run reactpress dev again.', + 'bootstrap.ready': 'ReactPress dev environment is ready (config + database).', + 'bootstrap.initFailed': 'Initialization failed', + 'bootstrap.cliInitFailed': 'reactpress-cli init failed', + 'bootstrap.dbPendingShort': 'Database not ready', + 'bootstrap.dbNotReady': '{message}. Tip: start Docker and run reactpress docker up, or run reactpress doctor', + 'bootstrap.dbReady': 'Database is ready', + 'db.backup.to': 'Backing up database to {path}', + 'db.backup.done': 'Backup complete', + 'db.backup.fail': 'mysqldump failed; ensure MySQL client is installed and .env is correct', + 'common.done': 'Done', + 'common.yes': 'yes', + 'common.no': 'no', + 'common.none': '(none)', + 'common.unknownError': 'Unknown error', + 'lifecycle.apiStopped': '[reactpress] API process stopped (pid {pid})', + 'lifecycle.stopPidFailed': '[reactpress] Failed to stop pid {pid}:', + 'lifecycle.apiAlreadyRunning': '[reactpress] API already running (pid {pid})', + 'lifecycle.noServerSrc': '[reactpress] server/src not found, falling back to reactpress-cli start', + 'lifecycle.startingLocalApi': '[reactpress] Starting local API (server/)…', + 'lifecycle.apiStartedBg': '[reactpress] API started in background (pid {pid})', + 'lifecycle.apiTimeout120': '[reactpress] API not ready within 120s: {url}', + 'lifecycle.apiReady': '[reactpress] API ready: {url}', + 'lifecycle.apiStatusTitle': '[reactpress] API status', + 'lifecycle.source': ' Source: {source}', + 'lifecycle.source.monorepo': 'monorepo server/', + 'lifecycle.source.bundle': 'reactpress-cli bundle', + 'lifecycle.pidFile': ' PID file: {path}', + 'lifecycle.recordedPid': ' Recorded PID: {pid}', + 'lifecycle.processAlive': ' Process alive: {alive}', + 'lifecycle.httpStatus': ' HTTP ({url}): {status}', + 'lifecycle.httpReachable': 'reachable', + 'lifecycle.httpUnreachable': 'unreachable', + 'lifecycle.unknownCommand': 'Unknown lifecycle command: {command}', + 'docker.stopping': '[reactpress] Stopping Docker services…', + 'docker.stopped': '[reactpress] Docker services stopped.', + 'docker.stopFailed': '[reactpress] Failed to stop Docker:', + 'docker.starting': '[reactpress] Starting Docker services…', + 'docker.notRunning': 'Docker is not running. Please start Docker Desktop first.', + 'docker.started': '[reactpress] Docker services started.', + 'docker.waitingMysql': '[reactpress] Waiting for MySQL…', + 'docker.mysqlReady': '[reactpress] MySQL is ready.', + 'docker.waitingMysqlProgress': '[reactpress] Waiting for MySQL… ({attempts}/{max})', + 'docker.mysqlTimeout': '[reactpress] MySQL not ready within timeout.', + 'docker.mysqlNotReady': 'MySQL is not ready', + 'docker.startDevStack': '[reactpress] Starting API + frontend (Docker MySQL)…', + 'docker.visitUrls': '[reactpress] Visit: http://localhost:8080 (nginx) / http://localhost:3001 (client)', + 'docker.devProcessExit': 'Dev process exited with code {code}', + 'docker.unknownCommand': 'Unknown docker command: {command}', + 'apiDev.modeServer': '[reactpress] Dev mode: server/ (nest start --watch)', + 'apiDev.modeCli': '[reactpress] Dev mode: reactpress-cli (server/src not found)', + 'apiDev.startedByCli': '[reactpress] API started via reactpress-cli.', + 'apiDev.ctrlCHint': '[reactpress] Press Ctrl+C to stop API.', + 'apiDev.stopHint': '[reactpress] Stop separately: reactpress server stop', + 'build.unknownTarget': 'Unknown build target: {target}. Available: {available}', + 'build.recursive': 'Build recursion detected (pnpm run build must not call itself). Use build:toolkit, build:server, or build:client.', + 'build.forbiddenScript': 'Invalid build script "{script}"; use granular scripts like build:toolkit.', + 'build.stepFailed': '[reactpress] [{current}/{total}] {label} failed', + 'build.plan': '[reactpress] Production build — {total} step(s): toolkit → server → client', + 'build.step': '[reactpress] [{current}/{total}] {label}…', + 'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)', + 'build.done': '[reactpress] Build finished in {seconds}s', + 'build.label.toolkit': 'Toolkit', + 'build.label.server': 'API (server)', + 'build.label.client': 'Frontend (client)', + 'build.label.docs': 'Documentation (docs)', + 'pm2.startFailed': '[reactpress] PM2 failed to start API:', + 'pm2.exitCode': 'PM2 exited with code {code}', + 'spawn.commandFailed': 'Command failed ({command}): exit code {code}', + 'spawn.exitCode': 'Exit code {code}', + 'shim.deprecated': '\n[deprecated] reactpress-cli will be removed in 3.1. Use instead:\n npm i -g @fecommunity/reactpress\n reactpress init · reactpress dev · reactpress doctor\n', + 'server.help.invokedBy': ' (usually invoked by reactpress server start)', + 'publish.pkg.main': 'ReactPress 3.0 main package — single entry (init / dev / doctor / publish)', + 'publish.pkg.server': 'NestJS backend API (deprecated — use bundled API in reactpress-cli)', + 'bundle.cli.description': 'Zero-config init and manage ReactPress CMS & blog server', + 'bundle.cli.cwd': 'ReactPress project directory (default: current working directory)', + 'bundle.cli.init.description': 'One-click init ReactPress CMS & blog (zero config)', + 'bundle.cli.init.directory': 'Project directory', + 'bundle.cli.init.force': 'Overwrite existing config', + 'bundle.cli.start.description': 'Start server (prepares database automatically)', + 'bundle.cli.stop.description': 'Stop server', + 'bundle.cli.stop.database': 'Also stop embedded database container', + 'bundle.cli.restart.description': 'Restart server', + 'bundle.cli.status.description': 'Server and database status', + 'bundle.cli.config.description': 'View or update config (--apply to restart)', + 'bundle.cli.config.key': 'Config key, e.g. server.port', + 'bundle.cli.config.value': 'New value', + 'bundle.cli.config.list': 'List all config keys', + 'bundle.cli.config.apply': 'Restart automatically after update', + 'bundle.cli.unknownCommand': 'Unknown command: {command}', + 'bundle.cmd.init.spinner': 'Initializing ReactPress project…', + 'bundle.cmd.init.succeed': 'Initialization complete', + 'bundle.cmd.init.fail': 'Initialization failed', + 'bundle.cmd.init.projectDir': 'Project directory: {path}', + 'bundle.cmd.init.nextStep': 'Next: reactpress-cli start', + 'bundle.cmd.notProject': 'This directory is not a ReactPress project.', + 'bundle.cmd.notProjectInit': 'This directory is not a ReactPress project. Run reactpress-cli init first.', + 'bundle.cmd.start.spinner': 'Preparing database and server…', + 'bundle.cmd.start.succeed': 'Server started', + 'bundle.cmd.start.fail': 'Start failed', + 'bundle.cmd.stop.spinner': 'Stopping server…', + 'bundle.cmd.stop.succeed': 'Stopped', + 'bundle.cmd.stop.fail': 'Stop failed', + 'bundle.cmd.restart.spinner': 'Restarting server…', + 'bundle.cmd.restart.succeed': 'Restart complete', + 'bundle.cmd.restart.fail': 'Restart failed', + 'bundle.cmd.status.title': 'Service status', + 'bundle.cmd.status.project': 'Project: {path}', + 'bundle.cmd.status.service': 'Service: {status}{pid}', + 'bundle.cmd.status.running': 'running', + 'bundle.cmd.status.stopped': 'stopped', + 'bundle.cmd.status.url': 'URL: {url}', + 'bundle.cmd.status.database': 'Database: {status} ({mode})', + 'bundle.cmd.status.dbReady': 'ready', + 'bundle.cmd.status.dbNotReady': 'not ready', + 'bundle.cmd.config.title': 'Configuration', + 'bundle.cmd.config.keyRequired': 'Specify a config key, e.g.: reactpress-cli config server.port 3003', + 'bundle.cmd.config.listHint': 'Use --list to show all keys', + 'bundle.cmd.config.updated': 'Updated {key} = {value}', + 'bundle.cmd.config.restartSpinner': 'Restarting to apply config…', + 'bundle.cmd.config.applied': 'Config applied', + 'bundle.cmd.config.restartFail': 'Restart failed', + 'bundle.cmd.config.restartManual': 'Run reactpress-cli restart manually', + 'bundle.cmd.config.applyHint': 'Run reactpress-cli restart or config --apply to apply changes', + 'bundle.service.init.alreadyProject': 'Directory is already a ReactPress project. Use --force to overwrite config.', + 'bundle.service.init.dbPending': 'Project created, but database is not ready: {message}. Run reactpress-cli start later.', + 'bundle.service.init.complete': 'ReactPress project initialized. Run reactpress-cli start to launch the server.', + 'bundle.service.init.templateMissing': 'Template file missing: {path}', + 'bundle.service.config.notFound': 'ReactPress project not found. Run reactpress-cli init first.', + 'bundle.service.config.keyMissing': 'Config key does not exist: {key}', + 'bundle.service.server.alreadyRunning': 'Server already running (PID {pid}), visit {url}', + 'bundle.service.server.portBusy': 'Port {port} is in use; ReactPress cannot bind. If you started via Docker Compose, run: docker stop reactpress_server. Or change server.port in .reactpress/config.json.', + 'bundle.service.server.cannotStart': 'Could not start ReactPress server process.', + 'bundle.service.server.noHttp': 'Server process started (PID {pid}) but no HTTP response at {url}. Check port conflicts or DB_* in .env.', + 'bundle.service.server.started': 'ReactPress server started at {url}', + 'bundle.service.server.stopped': 'ReactPress server stopped.', + 'bundle.service.server.cannotStopPid': 'Could not stop process PID {pid}', + 'bundle.service.database.dockerMissing': 'Docker not detected. Install and start Docker, or set database.mode to external with an existing MySQL.', + 'bundle.service.database.portSwitched': 'Host port {previous} was in use; switched to {port} (.env updated)', + 'bundle.service.database.portBindRetry': 'Port {port} bind failed, trying another port…', + 'bundle.service.database.containerStartFailed': 'Failed to start database container: {detail}', + 'bundle.service.database.credsMismatch': 'Database container is on port {port} but user "{user}" cannot connect (volume credentials differ from .env). Run: cd .reactpress && docker compose down -v && cd .. && reactpress-cli start', + 'bundle.service.database.connectionTimeout': 'Database container started but connection timed out. Run: docker logs {container}', + 'bundle.service.database.cannotConnect': 'Cannot connect to database {host}:{port}. Check DB_* in .env.', + 'bundle.serverBundle.missing': 'Bundled server is missing. Reinstall reactpress-cli.', + 'bundle.serverBundle.installFailed': 'Bundled server dependency install failed. In the reactpress-cli install dir run: cd server && npm install --omit=dev --no-bin-links', + 'bundle.serverBundle.notBuilt': 'Bundled server is not built or dependencies are incomplete. Run npm run build:server (dev) or reinstall reactpress-cli.', + 'bundle.port.notFound': 'No available port in range {start}-{end}', + }, + zh: { + 'cli.description': 'ReactPress 全栈 CLI — 初始化、开发、构建、部署、发布', + 'cli.init.description': '初始化项目 (.reactpress/config.json + .env + Docker MySQL)', + 'cli.init.directory': '项目目录', + 'cli.init.force': '覆盖已有配置', + 'cli.dev.description': '零配置开发: 环境检查 + toolkit 构建 + API + 前端', + 'cli.dev.apiOnly': '仅启动 API (watch)', + 'cli.dev.clientOnly': '仅启动前端', + 'cli.server.description': '管理 API 服务', + 'cli.server.start.description': '启动 API(等待 HTTP 就绪)', + 'cli.server.start.pm2': '使用 PM2 启动(生产)', + 'cli.server.start.bg': '后台启动,不等待 HTTP', + 'cli.server.stop': '停止 API', + 'cli.server.restart': '重启 API', + 'cli.server.status': '查看 API 状态', + 'cli.client.description': '管理前端', + 'cli.client.start': '启动 Next.js 客户端', + 'cli.client.start.pm2': '使用 PM2 启动', + 'cli.build.description': '构建生产产物', + 'cli.docker.description': 'Docker 开发环境 (MySQL + nginx)', + 'cli.docker.up': '仅启动 Docker 服务并等待 MySQL', + 'cli.docker.down': '停止 Docker 服务', + 'cli.docker.start': '启动 Docker + 全栈开发 (API + 前端)', + 'cli.docker.restart': '重启 Docker 服务', + 'cli.docker.status': '查看 Docker 容器状态', + 'cli.docker.logs': '查看 Docker 日志 (db | nginx)', + 'cli.status.description': '查看项目、API、前端、Docker 综合状态', + 'cli.doctor.description': '诊断环境:Node、Docker、端口、数据库、API 健康', + 'cli.db.description': '数据库运维', + 'cli.db.backup': '使用 mysqldump 备份当前项目数据库', + 'cli.db.backup.output': '输出 SQL 文件路径', + 'cli.publish.description': '构建并发布 npm 包 (交互式)', + 'cli.publish.build': '仅构建所有包', + 'cli.publish.publish': '交互式发布', + 'cli.start.description': '生产模式: 启动 API + 前端', + 'cli.help.examples': '示例:', + 'cli.help.interactive': ' reactpress 交互式菜单', + 'cli.help.dev': ' reactpress dev 零配置全栈开发', + 'cli.help.init': ' reactpress init --force 重新初始化配置', + 'cli.help.server': ' reactpress server start 启动 API', + 'cli.help.status': ' reactpress status 综合状态', + 'cli.help.doctor': ' reactpress doctor 环境诊断', + 'cli.help.docker': ' reactpress docker start Docker + 全栈', + 'cli.help.build': ' reactpress build -t client 构建指定目标 (toolkit|server|client|docs|all)', + 'cli.help.publish': ' reactpress publish 发布 npm 包', + 'cli.build.target': '构建目标: toolkit | server | client | docs | all', + 'banner.subtitle': ' · 全栈发布平台 CLI ', + 'menu.dev': '零配置开发 (env + DB + API + 前端)', + 'menu.init': '初始化项目 (.reactpress + .env + 数据库)', + 'menu.status': '查看项目状态', + 'menu.doctor': '环境诊断 (doctor)', + 'menu.devApi': '仅启动 API (开发 watch)', + 'menu.devClient': '仅启动前端', + 'menu.serverStart': '启动 API (后台生产)', + 'menu.serverStop': '停止 API', + 'menu.serverRestart': '重启 API', + 'menu.build': '构建 (toolkit → server → client)', + 'menu.buildTarget': '选择要构建的目标', + 'menu.buildAll': '全部 (toolkit → server → client)', + 'menu.dockerStart': 'Docker 开发环境 (DB + nginx + 全栈)', + 'menu.dockerUp': 'Docker 仅启动数据库', + 'menu.dockerStop': '停止 Docker 服务', + 'menu.openAdmin': '在浏览器打开管理后台', + 'menu.publish': '发布 npm 包 (交互式)', + 'menu.exit': '退出', + 'menu.prompt': '选择操作', + 'menu.back': '返回主菜单?', + 'menu.retry': '返回主菜单重试?', + 'menu.startingDev': '启动全栈开发…', + 'menu.initProject': '初始化项目…', + 'menu.done': '完成', + 'menu.opening': '打开 {url}', + 'menu.goodbye': ' 再见。', + 'dev.startingApi': '[reactpress] 正在启动 API(首次可能需安装依赖,请稍候)…', + 'dev.waitingApi': '[reactpress] 等待 API 就绪: {url}', + 'dev.apiTimeout': '[reactpress] API 在 {seconds}s 内未就绪。\n → 运行 reactpress doctor 查看详情\n → 嵌入式 MySQL:reactpress docker up\n → 检查 .env 中 DB_* 与 SERVER_SITE_URL', + 'dev.apiReady': '[reactpress] API 已就绪,正在启动前端…', + 'dev.clientSlow': '[reactpress] 前端在 {seconds}s 内未响应,可能仍在编译。稍后访问 {url}', + 'dev.envFailed': '[reactpress] 环境准备失败:', + 'dev.toolkitFailed': 'toolkit 构建失败,退出码: {code}', + 'dev.nextSteps': '下一步建议:', + 'dev.nextDoctor': ' → reactpress doctor 环境诊断', + 'dev.nextDocker': ' → reactpress docker up 启动嵌入式 MySQL', + 'dev.nextEnv': ' → 检查 .env 中 DB_* 与 SERVER_SITE_URL', + 'devBanner.ready': ' ✓ ReactPress 开发环境已就绪', + 'devBanner.site': '前台', + 'devBanner.admin': '管理端', + 'devBanner.health': '健康检查', + 'devBanner.hint': ' 诊断: reactpress doctor · 状态: reactpress status', + 'doctor.nodeBad': 'Node.js {version}(需要 ≥ 18)', + 'doctor.nodeFix': '请安装 Node.js 18+:https://nodejs.org/', + 'doctor.dockerOk': 'Docker 引擎可用', + 'doctor.dockerBad': 'Docker 未运行或不可用', + 'doctor.dockerFix': '安装并启动 Docker:https://docs.docker.com/get-docker/ ,然后运行 reactpress docker up;或改 config.json 使用外部 MySQL', + 'doctor.portApiBusy': 'API 端口 {port} 已被占用', + 'doctor.portClientBusy': '前端端口 {port} 已被占用', + 'doctor.portFix': '修改 .env 中 SERVER_PORT / CLIENT_PORT,或停止占用进程', + 'doctor.portOk': '端口 {apiPort}(API)、{clientPort}(前端)可用', + 'doctor.dbNoMysql2': '未安装 mysql2,无法检测数据库', + 'doctor.dbMysql2Fix': '在 monorepo 根目录执行 pnpm install', + 'doctor.dbOk': 'MySQL {host}:{port}/{database} 连通', + 'doctor.dbBad': '数据库连接失败: {error}', + 'doctor.dbFix': '运行 reactpress docker up 或检查 .env 中 DB_* 配置', + 'doctor.apiOk': 'API 健康检查通过 ({url})', + 'doctor.apiBad': 'API 未响应健康检查 ({url})', + 'doctor.apiFix': '运行 reactpress server start 或 reactpress dev', + 'doctor.pnpmBad': '未检测到 pnpm', + 'doctor.pnpmFix': 'npm i -g pnpm,或在 monorepo 根目录使用 corepack enable', + 'doctor.check.config': '配置文件', + 'doctor.check.env': '环境变量', + 'doctor.check.ports': '端口', + 'doctor.check.database': '数据库', + 'doctor.check.api': 'API 健康', + 'doctor.configOk': '.reactpress/config.json 存在', + 'doctor.configBad': '缺少 .reactpress/config.json', + 'doctor.configFix': '运行 reactpress init', + 'doctor.envOk': '.env 存在', + 'doctor.envBad': '缺少 .env', + 'doctor.envFix': '运行 reactpress init 或 reactpress config --apply', + 'doctor.project': ' 项目: {path}', + 'doctor.allPass': ' 全部检查通过,可以开始开发。', + 'doctor.failed': ' {count} 项需要处理。', + 'status.title': ' ReactPress 项目状态', + 'status.dir': ' 项目目录 {path}', + 'status.apiSource': ' API 来源 {source}', + 'status.apiSource.monorepo': 'monorepo server/', + 'status.apiSource.bundle': 'reactpress-cli', + 'status.configOk': '.reactpress/config.json ✓', + 'status.configBad': '未初始化', + 'status.envOk': '.env ✓', + 'status.envBad': '缺少 .env', + 'status.apiOnline': '在线', + 'status.apiOffline': '离线', + 'status.apiUnreachable': '{url} (离线或未启动)', + 'status.dbUp': '连通', + 'status.dbDown': '不可用', + 'status.pidRunning': '(运行中)', + 'status.frontend': ' 前端', + 'status.docker': ' Docker', + 'status.dockerUp': '可用', + 'status.dockerDown': '未运行', + 'bootstrap.configReady': '配置已存在,数据库已就绪。', + 'bootstrap.projectDbPending': '项目已创建,但数据库未就绪: {message}。请确认 Docker 已启动后重试 reactpress dev。', + 'bootstrap.ready': 'ReactPress 开发环境已就绪(配置 + 数据库)。', + 'bootstrap.initFailed': '初始化失败', + 'bootstrap.cliInitFailed': 'reactpress-cli init 失败', + 'bootstrap.dbPendingShort': '数据库未就绪', + 'bootstrap.dbNotReady': '{message}。建议:启动 Docker 后运行 reactpress docker up,或执行 reactpress doctor', + 'bootstrap.dbReady': '数据库已就绪', + 'db.backup.to': '备份数据库到 {path}', + 'db.backup.done': '备份完成', + 'db.backup.fail': 'mysqldump 失败,请确认已安装 MySQL 客户端且 .env 正确', + 'common.done': '完成', + 'common.yes': '是', + 'common.no': '否', + 'common.none': '(无)', + 'common.unknownError': '未知错误', + 'lifecycle.apiStopped': '[reactpress] 已停止 API 进程 (pid {pid})', + 'lifecycle.stopPidFailed': '[reactpress] 停止 pid {pid} 失败:', + 'lifecycle.apiAlreadyRunning': '[reactpress] API 已在运行 (pid {pid})', + 'lifecycle.noServerSrc': '[reactpress] 未检测到 server/src,回退到 reactpress-cli start', + 'lifecycle.startingLocalApi': '[reactpress] 正在启动本地 API (server/)…', + 'lifecycle.apiStartedBg': '[reactpress] API 已后台启动 (pid {pid})', + 'lifecycle.apiTimeout120': '[reactpress] API 在 120s 内未就绪: {url}', + 'lifecycle.apiReady': '[reactpress] API 已就绪: {url}', + 'lifecycle.apiStatusTitle': '[reactpress] API 状态', + 'lifecycle.source': ' 来源: {source}', + 'lifecycle.source.monorepo': 'monorepo server/', + 'lifecycle.source.bundle': 'reactpress-cli bundle', + 'lifecycle.pidFile': ' PID 文件: {path}', + 'lifecycle.recordedPid': ' 记录 PID: {pid}', + 'lifecycle.processAlive': ' 进程存活: {alive}', + 'lifecycle.httpStatus': ' HTTP ({url}): {status}', + 'lifecycle.httpReachable': '可访问', + 'lifecycle.httpUnreachable': '不可访问', + 'lifecycle.unknownCommand': '未知 lifecycle 命令: {command}', + 'docker.stopping': '[reactpress] 正在停止 Docker 服务…', + 'docker.stopped': '[reactpress] Docker 服务已停止。', + 'docker.stopFailed': '[reactpress] 停止 Docker 失败:', + 'docker.starting': '[reactpress] 正在启动 Docker 服务…', + 'docker.notRunning': 'Docker 未运行,请先启动 Docker Desktop。', + 'docker.started': '[reactpress] Docker 服务已启动。', + 'docker.waitingMysql': '[reactpress] 等待 MySQL 就绪…', + 'docker.mysqlReady': '[reactpress] MySQL 已就绪。', + 'docker.waitingMysqlProgress': '[reactpress] 等待 MySQL… ({attempts}/{max})', + 'docker.mysqlTimeout': '[reactpress] MySQL 在超时时间内未就绪。', + 'docker.mysqlNotReady': 'MySQL 未就绪', + 'docker.startDevStack': '[reactpress] 启动 API + 前端 (Docker MySQL)…', + 'docker.visitUrls': '[reactpress] 访问: http://localhost:8080 (nginx) / http://localhost:3001 (client)', + 'docker.devProcessExit': '开发进程退出: {code}', + 'docker.unknownCommand': '未知 docker 命令: {command}', + 'apiDev.modeServer': '[reactpress] 开发模式: server/ (nest start --watch)', + 'apiDev.modeCli': '[reactpress] 开发模式: reactpress-cli(未找到 server/src)', + 'apiDev.startedByCli': '[reactpress] API 已由 reactpress-cli 启动。', + 'apiDev.ctrlCHint': '[reactpress] 按 Ctrl+C 停止 API。', + 'apiDev.stopHint': '[reactpress] 单独停止: reactpress server stop', + 'build.unknownTarget': '未知构建目标: {target},可选: {available}', + 'build.recursive': '检测到构建递归(pnpm run build 不能再次调用自身)。请使用 build:toolkit、build:server 或 build:client。', + 'build.forbiddenScript': '无效的构建脚本 "{script}",请使用 build:toolkit 等细分脚本。', + 'build.stepFailed': '[reactpress] [{current}/{total}] {label} 失败', + 'build.plan': '[reactpress] 生产构建 — 共 {total} 步:toolkit → server → client', + 'build.step': '[reactpress] [{current}/{total}] {label}…', + 'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)', + 'build.done': '[reactpress] 构建完成,耗时 {seconds}s', + 'build.label.toolkit': 'Toolkit', + 'build.label.server': 'API (server)', + 'build.label.client': '前端 (client)', + 'build.label.docs': '文档 (docs)', + 'pm2.startFailed': '[reactpress] PM2 启动 API 失败:', + 'pm2.exitCode': 'PM2 退出码 {code}', + 'spawn.commandFailed': '命令失败 ({command}): 退出码 {code}', + 'spawn.exitCode': '退出码 {code}', + 'shim.deprecated': '\n[deprecated] reactpress-cli 将在 3.1 移除。请改用:\n npm i -g @fecommunity/reactpress\n reactpress init · reactpress dev · reactpress doctor\n', + 'server.help.invokedBy': ' (通常由 reactpress server start 调用)', + 'publish.pkg.main': 'ReactPress 3.0 主包 — 唯一入口 (init / dev / doctor / publish)', + 'publish.pkg.server': 'NestJS 后端 API (deprecated — 使用 reactpress-cli 内置 API)', + 'bundle.cli.description': '零配置初始化与管理 ReactPress CMS & 博客服务器', + 'bundle.cli.cwd': 'ReactPress 项目目录(默认:当前工作目录)', + 'bundle.cli.init.description': '一键初始化 ReactPress CMS & 博客服务器(零配置)', + 'bundle.cli.init.directory': '项目目录', + 'bundle.cli.init.force': '覆盖已有配置', + 'bundle.cli.start.description': '启动服务器(自动准备数据库)', + 'bundle.cli.stop.description': '停止服务器', + 'bundle.cli.stop.database': '同时停止嵌入式数据库容器', + 'bundle.cli.restart.description': '重启服务器', + 'bundle.cli.status.description': '查看服务与数据库状态', + 'bundle.cli.config.description': '查看或更新配置(更新后可用 --apply 重启生效)', + 'bundle.cli.config.key': '配置键,如 server.port', + 'bundle.cli.config.value': '新值', + 'bundle.cli.config.list': '列出所有配置', + 'bundle.cli.config.apply': '更新后自动重启服务', + 'bundle.cli.unknownCommand': '未知命令: {command}', + 'bundle.cmd.init.spinner': '正在初始化 ReactPress 项目…', + 'bundle.cmd.init.succeed': '初始化完成', + 'bundle.cmd.init.fail': '初始化失败', + 'bundle.cmd.init.projectDir': '项目目录: {path}', + 'bundle.cmd.init.nextStep': '下一步: reactpress-cli start', + 'bundle.cmd.notProject': '当前目录不是 ReactPress 项目。', + 'bundle.cmd.notProjectInit': '当前目录不是 ReactPress 项目。请先运行 reactpress-cli init。', + 'bundle.cmd.start.spinner': '正在准备数据库与服务…', + 'bundle.cmd.start.succeed': '服务已启动', + 'bundle.cmd.start.fail': '启动失败', + 'bundle.cmd.stop.spinner': '正在停止服务…', + 'bundle.cmd.stop.succeed': '已停止', + 'bundle.cmd.stop.fail': '停止失败', + 'bundle.cmd.restart.spinner': '正在重启服务…', + 'bundle.cmd.restart.succeed': '重启完成', + 'bundle.cmd.restart.fail': '重启失败', + 'bundle.cmd.status.title': '服务状态', + 'bundle.cmd.status.project': '项目: {path}', + 'bundle.cmd.status.service': '服务: {status}{pid}', + 'bundle.cmd.status.running': '运行中', + 'bundle.cmd.status.stopped': '已停止', + 'bundle.cmd.status.url': '地址: {url}', + 'bundle.cmd.status.database': '数据库: {status} ({mode})', + 'bundle.cmd.status.dbReady': '就绪', + 'bundle.cmd.status.dbNotReady': '未就绪', + 'bundle.cmd.config.title': '配置项', + 'bundle.cmd.config.keyRequired': '请指定配置键,例如: reactpress-cli config server.port 3003', + 'bundle.cmd.config.listHint': '使用 --list 查看所有配置项', + 'bundle.cmd.config.updated': '已更新 {key} = {value}', + 'bundle.cmd.config.restartSpinner': '正在重启以使配置生效…', + 'bundle.cmd.config.applied': '配置已应用', + 'bundle.cmd.config.restartFail': '重启失败', + 'bundle.cmd.config.restartManual': '请手动运行 reactpress-cli restart', + 'bundle.cmd.config.applyHint': '运行 reactpress-cli restart 或 config --apply 使配置生效', + 'bundle.service.init.alreadyProject': '目录已是 ReactPress 项目。使用 --force 覆盖配置。', + 'bundle.service.init.dbPending': '项目已创建,但数据库未就绪: {message}。可稍后运行 reactpress-cli start。', + 'bundle.service.init.complete': 'ReactPress 项目初始化完成。运行 reactpress-cli start 启动服务。', + 'bundle.service.init.templateMissing': '模板文件缺失: {path}', + 'bundle.service.config.notFound': '未找到 ReactPress 项目。请先运行 reactpress-cli init 初始化。', + 'bundle.service.config.keyMissing': '配置项不存在: {key}', + 'bundle.service.server.alreadyRunning': '服务已在运行 (PID {pid}),访问 {url}', + 'bundle.service.server.portBusy': '端口 {port} 已被占用,ReactPress 无法绑定。若曾用 Docker Compose 启动过 ReactPress,请执行: docker stop reactpress_server。也可在 .reactpress/config.json 中修改 server.port。', + 'bundle.service.server.cannotStart': '无法启动 ReactPress 服务进程。', + 'bundle.service.server.noHttp': '服务进程已启动 (PID {pid}),但 {url} 无 HTTP 响应。请检查端口占用或 .env 数据库配置。', + 'bundle.service.server.started': 'ReactPress 服务已启动,访问 {url}', + 'bundle.service.server.stopped': 'ReactPress 服务已停止。', + 'bundle.service.server.cannotStopPid': '无法停止进程 PID {pid}', + 'bundle.service.database.dockerMissing': '未检测到 Docker。请安装并启动 Docker,或将 database.mode 设为 external 并使用已有 MySQL。', + 'bundle.service.database.portSwitched': '宿主机端口 {previous} 已被占用,已改用 {port}(已更新 .env)', + 'bundle.service.database.portBindRetry': '端口 {port} 绑定失败,正在尝试其他端口…', + 'bundle.service.database.containerStartFailed': '启动数据库容器失败: {detail}', + 'bundle.service.database.credsMismatch': '数据库容器已在端口 {port} 运行,但账号「{user}」无法连接(数据卷中的凭证与 .env 不一致)。请在项目目录执行: cd .reactpress && docker compose down -v && cd .. && reactpress-cli start', + 'bundle.service.database.connectionTimeout': '数据库容器已启动,但连接超时。请执行 docker logs {container} 查看详情。', + 'bundle.service.database.cannotConnect': '无法连接数据库 {host}:{port},请检查 .env 中的 DB_* 配置。', + 'bundle.serverBundle.missing': '内置服务端缺失,请重新安装 reactpress-cli。', + 'bundle.serverBundle.installFailed': '内置服务端依赖安装失败。请在 reactpress-cli 安装目录下手动执行: cd server && npm install --omit=dev --no-bin-links', + 'bundle.serverBundle.notBuilt': '内置服务端未构建或依赖不完整。请运行 npm run build:server(开发)或重新安装 reactpress-cli。', + 'bundle.port.notFound': '在 {start}-{end} 范围内未找到可用端口', + }, +}; + +module.exports = { STRINGS }; diff --git a/cli/lib/lifecycle.js b/cli/lib/lifecycle.js new file mode 100644 index 0000000..f1de03c --- /dev/null +++ b/cli/lib/lifecycle.js @@ -0,0 +1,160 @@ +const { spawn, spawnSync } = require('child_process'); +const { ensureProjectEnvironment } = require('./bootstrap'); +const { loadServerSiteUrl, waitForHttp } = require('./http'); +const { + getServerBin, + getServerDir, + isUsingMonorepoServer, + getPidFile, +} = require('./paths'); +const { readPid, isProcessRunning, clearPidFile, writePid } = require('./process'); +const { getMonorepoRoot } = require('./root'); +const { t } = require('./i18n'); + +async function ensureConfig(projectRoot) { + try { + await ensureProjectEnvironment(projectRoot); + return true; + } catch (err) { + console.error(t('dev.envFailed'), err.message || err); + return false; + } +} + +function stopApi(projectRoot) { + const pid = readPid(projectRoot); + if (pid && isProcessRunning(pid)) { + try { + process.kill(pid, 'SIGTERM'); + console.log(t('lifecycle.apiStopped', { pid })); + } catch (err) { + console.warn(t('lifecycle.stopPidFailed', { pid }), err.message); + } + } + clearPidFile(projectRoot); +} + +async function startApi(projectRoot, { wait = true } = {}) { + if (!(await ensureConfig(projectRoot))) { + return 1; + } + + const existing = readPid(projectRoot); + if (existing && isProcessRunning(existing)) { + console.log(t('lifecycle.apiAlreadyRunning', { pid: existing })); + return 0; + } + clearPidFile(projectRoot); + + if (!isUsingMonorepoServer()) { + console.warn(t('lifecycle.noServerSrc')); + const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], { + cwd: projectRoot, + stdio: 'inherit', + }); + return start.status ?? 1; + } + + console.log(t('lifecycle.startingLocalApi')); + const child = spawn(process.execPath, [getServerBin()], { + cwd: getServerDir(), + detached: true, + stdio: 'ignore', + env: { + ...process.env, + REACTPRESS_ORIGINAL_CWD: projectRoot, + }, + }); + + child.unref(); + writePid(projectRoot, child.pid); + console.log(t('lifecycle.apiStartedBg', { pid: child.pid })); + + if (!wait) { + return 0; + } + + const serverUrl = loadServerSiteUrl(projectRoot); + const ready = await waitForHttp(serverUrl); + if (!ready) { + console.error(t('lifecycle.apiTimeout120', { url: serverUrl })); + return 1; + } + console.log(t('lifecycle.apiReady', { url: serverUrl })); + return 0; +} + +async function statusApi(projectRoot) { + const pid = readPid(projectRoot); + const serverUrl = loadServerSiteUrl(projectRoot); + const { isHttpResponding } = require('./http'); + const httpOk = await isHttpResponding(serverUrl); + + const source = isUsingMonorepoServer() + ? t('lifecycle.source.monorepo') + : t('lifecycle.source.bundle'); + + console.log(t('lifecycle.apiStatusTitle')); + console.log(t('lifecycle.source', { source })); + console.log(t('lifecycle.pidFile', { path: getPidFile(projectRoot) })); + console.log( + t('lifecycle.recordedPid', { + pid: pid ?? t('common.none'), + }) + ); + console.log( + t('lifecycle.processAlive', { + alive: pid + ? isProcessRunning(pid) + ? t('common.yes') + : t('common.no') + : '—', + }) + ); + console.log( + t('lifecycle.httpStatus', { + url: serverUrl, + status: httpOk ? t('lifecycle.httpReachable') : t('lifecycle.httpUnreachable'), + }) + ); +} + +async function runLifecycleCommand(command, projectRoot = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) { + switch (command) { + case 'start': + return startApi(projectRoot, { wait: true }); + case 'start:bg': + return startApi(projectRoot, { wait: false }); + case 'stop': + stopApi(projectRoot); + if (!isUsingMonorepoServer()) { + spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], { + cwd: projectRoot, + stdio: 'inherit', + }); + } + return 0; + case 'restart': + stopApi(projectRoot); + if (!isUsingMonorepoServer()) { + spawnSync('pnpm', ['exec', 'reactpress-cli', 'restart'], { + cwd: projectRoot, + stdio: 'inherit', + }); + return 0; + } + return startApi(projectRoot, { wait: true }); + case 'status': + await statusApi(projectRoot); + return 0; + default: + throw new Error(t('lifecycle.unknownCommand', { command })); + } +} + +module.exports = { + startApi, + stopApi, + statusApi, + runLifecycleCommand, +}; diff --git a/cli/lib/paths.js b/cli/lib/paths.js new file mode 100644 index 0000000..12846f9 --- /dev/null +++ b/cli/lib/paths.js @@ -0,0 +1,75 @@ +const fs = require('fs'); +const path = require('path'); +const { getMonorepoRoot } = require('./root'); + +function getMonorepoServerDir() { + return path.join(getMonorepoRoot(), 'server'); +} + +function hasMonorepoServerSource() { + return fs.existsSync(path.join(getMonorepoServerDir(), 'src', 'main.ts')); +} + +function getCliPackageRoot() { + const ownRoot = path.join(__dirname, '..'); + if (fs.existsSync(path.join(ownRoot, 'dist', 'index.js'))) { + return ownRoot; + } + try { + return path.dirname( + require.resolve('@fecommunity/reactpress-cli-core/package.json') + ); + } catch { + return path.dirname(require.resolve('@fecommunity/reactpress-cli/package.json')); + } +} + +function getBundledServerDir() { + return path.join(getCliPackageRoot(), 'server'); +} + +function getServerDir() { + if (hasMonorepoServerSource()) { + return getMonorepoServerDir(); + } + return getBundledServerDir(); +} + +function getServerBin() { + return path.join(getServerDir(), 'bin', 'reactpress-server.js'); +} + +function getSwaggerPath() { + return path.join(getServerDir(), 'public', 'swagger.json'); +} + +function getServerMain() { + return path.join(getServerDir(), 'dist', 'main.js'); +} + +function isUsingMonorepoServer() { + return hasMonorepoServerSource(); +} + +function getClientBin() { + return path.join(getMonorepoRoot(), 'client', 'bin', 'reactpress-client.js'); +} + +function getPidFile(projectRoot) { + return path.join(projectRoot, '.reactpress', 'server.pid'); +} + +module.exports = { + getMonorepoRoot, + getMonorepoServerDir, + hasMonorepoServerSource, + isUsingMonorepoServer, + getCliPackageRoot, + getBundledServerDir, + getServerDir, + getServerBin, + getSwaggerPath, + getServerMain, + getClientBin, + getPidFile, +}; diff --git a/cli/lib/pm2.js b/cli/lib/pm2.js new file mode 100644 index 0000000..129f2a9 --- /dev/null +++ b/cli/lib/pm2.js @@ -0,0 +1,32 @@ +const { spawn } = require('child_process'); +const { getServerBin, getServerDir } = require('./paths'); +const { ensureOriginalCwd } = require('./root'); +const { t } = require('./i18n'); + +function startApiWithPm2(projectRoot = ensureOriginalCwd()) { + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [getServerBin(), '--pm2'], { + stdio: 'inherit', + cwd: getServerDir(), + env: { + ...process.env, + REACTPRESS_ORIGINAL_CWD: projectRoot, + }, + }); + + child.on('error', (error) => { + console.error(t('pm2.startFailed'), error); + reject(error); + }); + + child.on('close', (code) => { + if (code !== 0) { + reject(Object.assign(new Error(t('pm2.exitCode', { code })), { exitCode: code })); + return; + } + resolve(); + }); + }); +} + +module.exports = { startApiWithPm2 }; diff --git a/cli/lib/process.js b/cli/lib/process.js new file mode 100644 index 0000000..d0f3ced --- /dev/null +++ b/cli/lib/process.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const path = require('path'); +const { getPidFile } = require('./paths'); + +function readPid(projectRoot) { + const pidFile = getPidFile(projectRoot); + try { + const raw = fs.readFileSync(pidFile, 'utf8').trim(); + const pid = Number.parseInt(raw, 10); + return Number.isFinite(pid) ? pid : null; + } catch { + return null; + } +} + +function isProcessRunning(pid) { + if (!pid) return false; + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +function clearPidFile(projectRoot) { + const pidFile = getPidFile(projectRoot); + if (fs.existsSync(pidFile)) { + fs.unlinkSync(pidFile); + } +} + +function writePid(projectRoot, pid) { + const pidFile = getPidFile(projectRoot); + fs.mkdirSync(path.dirname(pidFile), { recursive: true }); + fs.writeFileSync(pidFile, String(pid)); +} + +module.exports = { + readPid, + isProcessRunning, + clearPidFile, + writePid, + getPidFile, +}; diff --git a/cli/lib/publish.js b/cli/lib/publish.js new file mode 100644 index 0000000..1ae6005 --- /dev/null +++ b/cli/lib/publish.js @@ -0,0 +1,952 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); +const inquirer = require('inquirer'); +const crypto = require('crypto'); +const { t } = require('./i18n'); + +function getPackages() { + return [ + { + name: '@fecommunity/reactpress', + path: 'cli', + description: t('publish.pkg.main') + }, + { + name: '@fecommunity/reactpress-toolkit', + path: 'toolkit', + description: 'API client and utilities toolkit' + }, + { + name: '@fecommunity/reactpress-client', + path: 'client', + description: 'Frontend application package' + }, + { + name: '@fecommunity/reactpress-server', + path: 'server', + description: t('publish.pkg.server'), + deprecated: true + }, + { + name: '@fecommunity/reactpress-template-hello-world', + path: 'templates/hello-world', + description: 'Hello World template for ReactPress' + }, + { + name: '@fecommunity/reactpress-template-twentytwentyfive', + path: 'templates/twentytwentyfive', + description: 'Twenty Twenty Five blog template for ReactPress' + } +]; +} + +const packages = getPackages(); + +// Generate a hash for a file or directory +function generateHash(filePath) { + try { + if (fs.statSync(filePath).isDirectory()) { + const files = fs.readdirSync(filePath); + const hashes = files + .filter(file => !file.startsWith('.') && file !== 'node_modules' && file !== 'dist' && file !== '.next') // Ignore hidden files and build directories + .map(file => generateHash(path.join(filePath, file))) + .sort(); + return crypto.createHash('md5').update(hashes.join('')).digest('hex'); + } else { + const content = fs.readFileSync(filePath); + return crypto.createHash('md5').update(content).digest('hex'); + } + } catch (error) { + return ''; + } +} + +// Get package content hash +function getPackageHash(packagePath) { + const fullPath = path.join(process.cwd(), packagePath); + return generateHash(fullPath); +} + +// Check if package has meaningful changes (for build) +function hasMeaningfulChangesForBuild(packagePath, packageName) { + try { + // Create a hash file path to store previous hash in node_modules + const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`); + + // Generate current hash + const currentHash = getPackageHash(packagePath); + + // Check if we have a previous hash + if (fs.existsSync(hashFilePath)) { + const previousHash = fs.readFileSync(hashFilePath, 'utf8').trim(); + return currentHash !== previousHash; + } + + // If no previous hash, consider it as having changes + return true; + } catch (error) { + console.log(chalk.yellow(`⚠️ Could not check changes for ${packageName}, assuming changes exist`)); + return true; + } +} + +// Check if package has meaningful changes (for publish) +function hasMeaningfulChangesForPublish(packagePath, packageName) { + try { + // Create a hash file path to store previous hash in node_modules + const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`); + + // Generate current hash + const currentHash = getPackageHash(packagePath); + + // Check if we have a previous hash + if (fs.existsSync(hashFilePath)) { + const previousHash = fs.readFileSync(hashFilePath, 'utf8').trim(); + return currentHash !== previousHash; + } + + // If no previous hash, consider it as having changes + return true; + } catch (error) { + console.log(chalk.yellow(`⚠️ Could not check changes for ${packageName}, assuming changes exist`)); + return true; + } +} + +// Save package hash (for build) +function savePackageHashForBuild(packagePath, packageName) { + try { + const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`); + + // Ensure cache directory exists + const cacheDir = path.dirname(hashFilePath); + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + // Generate and save current hash + const currentHash = getPackageHash(packagePath); + fs.writeFileSync(hashFilePath, currentHash); + } catch (error) { + console.log(chalk.yellow(`⚠️ Could not save hash for ${packageName}`)); + } +} + +// Save package hash (for publish) +function savePackageHashForPublish(packagePath, packageName) { + try { + const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`); + + // Ensure cache directory exists + const cacheDir = path.dirname(hashFilePath); + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + + // Generate and save current hash + const currentHash = getPackageHash(packagePath); + fs.writeFileSync(hashFilePath, currentHash); + } catch (error) { + console.log(chalk.yellow(`⚠️ Could not save hash for ${packageName}`)); + } +} + +// Get current versions +function getCurrentVersion(packagePath) { + try { + const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + return pkg.version; + } catch (error) { + return 'unknown'; + } +} + +// Increment version based on type +function incrementVersion(version, type) { + const parts = version.split('-')[0].split('.'); + const major = parseInt(parts[0]); + const minor = parseInt(parts[1]); + const patch = parseInt(parts[2]); + + switch (type) { + case 'major': + return `${major + 1}.0.0`; + case 'minor': + return `${major}.${minor + 1}.0`; + case 'patch': + return `${major}.${minor}.${patch + 1}`; + case 'beta': + // For beta, we increment the beta number or add beta.1 if not present + const match = version.match(/^(.*)-beta\.(\d+)$/); + if (match) { + const baseVersion = match[1]; + const betaNumber = parseInt(match[2]); + return `${baseVersion}-beta.${betaNumber + 1}`; + } else { + // If no beta version exists, add beta.1 + return `${version}-beta.1`; + } + case 'alpha': + // For alpha, we increment the alpha number or add alpha.1 if not present + const alphaMatch = version.match(/^(.*)-alpha\.(\d+)$/); + if (alphaMatch) { + const baseVersion = alphaMatch[1]; + const alphaNumber = parseInt(alphaMatch[2]); + return `${baseVersion}-alpha.${alphaNumber + 1}`; + } else { + // If no alpha version exists, add alpha.1 + return `${version}-alpha.1`; + } + default: + return version; + } +} + +// Get next available version from npm registry +function getNextAvailableVersion(packageName, currentVersion, versionType) { + try { + // First, increment the version locally + let nextVersion = incrementVersion(currentVersion, versionType); + + // Check if this version already exists on npm + let versionExists = true; + let attempts = 0; + const maxAttempts = 100; // Prevent infinite loop + + while (versionExists && attempts < maxAttempts) { + try { + execSync(`npm view ${packageName}@${nextVersion} version`, { stdio: 'ignore' }); + // If we get here, the version exists, so we need to increment again + nextVersion = incrementVersion(nextVersion, versionType); + attempts++; + } catch (error) { + // If we get an error, the version doesn't exist, which is what we want + versionExists = false; + } + } + + if (attempts >= maxAttempts) { + throw new Error('Too many attempts to find available version'); + } + + return nextVersion; + } catch (error) { + // Fallback to simple increment if npm view fails + console.log(chalk.yellow(`⚠️ Could not check npm registry, using local increment for ${packageName}`)); + return incrementVersion(currentVersion, versionType); + } +} + +// Update package version +function updateVersion(packagePath, newVersion) { + console.log(chalk.blue(`\n✏️ Updating version to ${newVersion}...`)); + + const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + + const oldVersion = pkg.version; + pkg.version = newVersion; + + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + console.log(chalk.green(`✅ Version updated from ${oldVersion} to ${newVersion}`)); +} + +// Fix workspace dependencies for build +function fixWorkspaceDependenciesForBuild(packagePath) { + console.log(chalk.blue(`🔧 Fixing workspace dependencies for build: ${packagePath}...`)); + + const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + + // Fix dependencies + const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; + + depTypes.forEach(depType => { + if (pkg[depType]) { + Object.keys(pkg[depType]).forEach(depName => { + // Check if it's a workspace dependency + if (pkg[depType][depName] === 'workspace:*' || pkg[depType][depName].startsWith('workspace:')) { + // For build purposes, we'll use the file: protocol to reference local packages + const depPackage = packages.find(p => p.name === depName); + if (depPackage) { + console.log(chalk.gray(` Replacing ${depName} workspace dependency with file reference`)); + pkg[depType][depName] = `file:../${depPackage.path}`; + } + } + }); + } + }); + + // Write the updated package.json + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + console.log(chalk.green(`✅ Workspace dependencies fixed for build: ${packagePath}`)); +} + +// Restore workspace dependencies after build +function restoreWorkspaceDependenciesAfterBuild(packagePath) { + console.log(chalk.blue(`🔄 Restoring workspace dependencies after build: ${packagePath}...`)); + + const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + + // Restore dependencies + const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; + + depTypes.forEach(depType => { + if (pkg[depType]) { + Object.keys(pkg[depType]).forEach(depName => { + // Check if this is one of our internal packages referenced with file: + const depPackage = packages.find(p => p.name === depName); + if (depPackage && pkg[depType][depName].startsWith('file:')) { + console.log(chalk.gray(` Restoring ${depName} to workspace dependency`)); + pkg[depType][depName] = 'workspace:*'; + } + }); + } + }); + + // Write the updated package.json + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + console.log(chalk.green(`✅ Workspace dependencies restored after build: ${packagePath}`)); +} + +// Fix workspace dependencies for publish +function fixWorkspaceDependenciesForPublish(packagePath, packageVersions) { + console.log(chalk.blue(`🔧 Fixing workspace dependencies for publish: ${packagePath}...`)); + + const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + + // Fix dependencies + const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; + + depTypes.forEach(depType => { + if (pkg[depType]) { + Object.keys(pkg[depType]).forEach(depName => { + // Check if it's a workspace dependency + if (pkg[depType][depName] === 'workspace:*' || pkg[depType][depName].startsWith('workspace:')) { + // Replace with actual version + const depPackage = packages.find(p => p.name === depName); + if (depPackage && packageVersions[depName]) { + console.log(chalk.gray(` Replacing ${depName} workspace dependency with version ${packageVersions[depName]}`)); + pkg[depType][depName] = packageVersions[depName]; + } + } + }); + } + }); + + // Write the updated package.json + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + console.log(chalk.green(`✅ Workspace dependencies fixed for publish: ${packagePath}`)); +} + +// Restore workspace dependencies after publish +function restoreWorkspaceDependenciesAfterPublish(packagePath) { + console.log(chalk.blue(`🔄 Restoring workspace dependencies for ${packagePath}...`)); + + const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + + // Restore dependencies + const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; + + depTypes.forEach(depType => { + if (pkg[depType]) { + Object.keys(pkg[depType]).forEach(depName => { + // Check if this is one of our internal packages + const depPackage = packages.find(p => p.name === depName); + if (depPackage) { + console.log(chalk.gray(` Restoring ${depName} to workspace dependency`)); + pkg[depType][depName] = 'workspace:*'; + } + }); + } + }); + + // Write the updated package.json + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + console.log(chalk.green(`✅ Workspace dependencies restored for ${packagePath}`)); +} + +// Build package +function buildPackage(pkg) { + console.log(chalk.blue(`\n🔨 Building ${pkg.name} (${pkg.description})...`)); + + try { + // Fix workspace dependencies for build + fixWorkspaceDependenciesForBuild(pkg.path); + + try { + if (pkg.path === 'config') { + execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); + } else if (pkg.path === 'client') { + execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); + } else if (pkg.path === 'toolkit') { + execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); + } else if (pkg.path === 'templates/hello-world' || pkg.path === 'templates/twentytwentyfive') { + // Templates don't need to be built, just validate package.json + console.log(chalk.gray(' Templates do not require building, skipping...')); + } + console.log(chalk.green(`✅ ${pkg.name} built successfully`)); + } finally { + // Always restore workspace dependencies + restoreWorkspaceDependenciesAfterBuild(pkg.path); + } + } catch (error) { + console.log(chalk.red(`❌ Failed to build ${pkg.name}`)); + throw error; + } +} + +// Publish package +function publishPackage(packagePath, packageName, tag = 'latest') { + console.log(chalk.blue(`\n🚀 Publishing ${packageName} with tag ${tag}...`)); + + try { + const command = `pnpm publish --access public --tag ${tag} --registry https://registry.npmjs.org --no-git-checks`; + execSync(command, { cwd: path.join(process.cwd(), packagePath), stdio: 'inherit' }); + console.log(chalk.green(`✅ ${packageName} published successfully!`)); + } catch (error) { + console.log(chalk.red(`❌ Failed to publish ${packageName}`)); + throw error; + } +} + +// Create GitHub release +function createGitHubRelease(tagName, releaseNotes) { + console.log(chalk.blue(`\n📝 Creating GitHub release ${tagName}...`)); + + try { + // Create release using GitHub CLI if available + const command = `gh release create ${tagName} --title "${tagName}" --notes "${releaseNotes}"`; + execSync(command, { stdio: 'inherit' }); + console.log(chalk.green(`✅ GitHub release ${tagName} created successfully!`)); + } catch (error) { + console.log(chalk.yellow(`⚠️ Failed to create GitHub release (GitHub CLI may not be installed or configured)`)); + console.log(chalk.gray('You can manually create the release at: https://github.com/fecommunity/reactpress/releases/new')); + } +} + +// Check environment +function checkEnvironment() { + // Check if pnpm is installed + try { + execSync('pnpm --version', { stdio: 'ignore' }); + } catch (error) { + console.log(chalk.red('❌ pnpm is not installed. Please install pnpm first.')); + return false; + } + + // Check if logged in to npm + try { + execSync('pnpm whoami --registry https://registry.npmjs.org', { stdio: 'ignore' }); + } catch (error) { + console.log(chalk.red('❌ Not logged in to npm. Please run "pnpm login --registry https://registry.npmjs.org" first.')); + return false; + } + + return true; +} + +// Build packages function +async function buildPackages() { + console.log(chalk.blue('🏗️ ReactPress Package Builder\n')); + + // Show current versions + console.log(chalk.cyan('📋 Current package versions:')); + packages.forEach(pkg => { + const version = getCurrentVersion(pkg.path); + console.log(chalk.gray(` ${pkg.name}: ${version}`)); + }); + console.log(); + + try { + // Track which packages actually need to be built + const packagesToBuild = []; + + // Check for meaningful changes in each package + for (const pkg of packages) { + if (fs.existsSync(path.join(process.cwd(), pkg.path))) { + if (hasMeaningfulChangesForBuild(pkg.path, pkg.name)) { + packagesToBuild.push(pkg); + console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be built`)); + } else { + console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); + } + } else { + console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); + } + } + + if (packagesToBuild.length === 0) { + console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to build!')); + return; + } + + // Build packages that have changes + for (const pkg of packagesToBuild) { + await buildPackage(pkg); + // Save the hash after successful build + savePackageHashForBuild(pkg.path, pkg.name); + } + + console.log(chalk.green(`\n🎉 ${packagesToBuild.length} package(s) built successfully!`)); + } catch (error) { + console.error(chalk.red('❌ Build failed:'), error); + process.exit(1); + } +} + +// Publish packages function +async function publishPackages() { + // Check if called with --no-build flag + const noBuild = process.argv.includes('--no-build'); + + console.log(chalk.blue('📦 ReactPress Package Publisher\n')); + + // Run environment checks + if (!checkEnvironment()) { + process.exit(1); + } + + // Show current versions + console.log(chalk.cyan('📋 Current package versions:')); + packages.forEach(pkg => { + const version = getCurrentVersion(pkg.path); + console.log(chalk.gray(` ${pkg.name}: ${version}`)); + }); + console.log(); + + // Ask for publishing options + const { action } = await inquirer.prompt([ + { + type: 'list', + name: 'action', + message: 'What would you like to do?', + choices: [ + { name: '🚀 Publish all packages with version bump', value: 'publish-all' }, + { name: '📦 Publish specific package', value: 'publish-one' }, + { name: '🔨 Build all packages only', value: 'build-all' }, + { name: '🏷️ Publish as beta/alpha', value: 'publish-prerelease' }, + { name: '❌ Cancel', value: 'cancel' } + ] + } + ]); + + if (action === 'cancel') { + console.log(chalk.yellow('Operation cancelled.')); + return; + } + + if (action === 'build-all') { + console.log(chalk.blue('🔨 Building all packages...\n')); + + // Track which packages actually need to be built + const packagesToBuild = []; + + // Check for meaningful changes in each package + for (const pkg of packages) { + if (fs.existsSync(path.join(process.cwd(), pkg.path))) { + if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) { + packagesToBuild.push(pkg); + console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be built`)); + } else { + console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); + } + } else { + console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); + } + } + + if (packagesToBuild.length === 0) { + console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to build!')); + return; + } + + // Build packages that have changes + for (const pkg of packagesToBuild) { + buildPackage(pkg); + // Save the hash after successful build + savePackageHashForPublish(pkg.path, pkg.name); + } + + console.log(chalk.green(`\n🎉 ${packagesToBuild.length} package(s) built successfully!`)); + return; + } + + if (action === 'publish-one') { + const { selectedPackage } = await inquirer.prompt([ + { + type: 'list', + name: 'selectedPackage', + message: 'Which package would you like to publish?', + choices: packages.map(pkg => ({ + name: `${pkg.name} (${pkg.description})`, + value: pkg + })) + } + ]); + + // Check if the selected package has meaningful changes + if (!hasMeaningfulChangesForPublish(selectedPackage.path, selectedPackage.name)) { + console.log(chalk.gray(`\n⏭️ ${selectedPackage.name} has no meaningful changes, skipping...`)); + console.log(chalk.green('✅ Nothing to publish!')); + return; + } + + const { versionType } = await inquirer.prompt([ + { + type: 'list', + name: 'versionType', + message: 'Version bump type:', + choices: [ + { name: 'Beta (1.0.0-beta.1 -> 1.0.0-beta.2)', value: 'beta' }, + { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' }, + { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' }, + { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' }, + { name: 'Custom version', value: 'custom' } + ] + } + ]); + + let newVersion; + const currentVersion = getCurrentVersion(selectedPackage.path); + + if (versionType === 'custom') { + const { customVersion } = await inquirer.prompt([ + { + type: 'input', + name: 'customVersion', + message: `Enter new version for ${selectedPackage.name} (current: ${currentVersion}):`, + validate: (input) => { + const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/; + return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)'; + } + } + ]); + newVersion = customVersion; + } else { + newVersion = getNextAvailableVersion(selectedPackage.name, currentVersion, versionType); + } + + // Get all package versions for dependency resolution + const packageVersions = {}; + packages.forEach(pkg => { + packageVersions[pkg.name] = getCurrentVersion(pkg.path); + }); + // Update the selected package version + packageVersions[selectedPackage.name] = newVersion; + + // Fix workspace dependencies before publishing + fixWorkspaceDependenciesForPublish(selectedPackage.path, packageVersions); + + try { + updateVersion(selectedPackage.path, newVersion); + // Only build if not disabled + if (!noBuild) { + buildPackage(selectedPackage); + } + + // Determine tag based on version type + const tag = versionType === 'beta' ? 'beta' : 'latest'; + publishPackage(selectedPackage.path, selectedPackage.name, tag); + + // Save the hash after successful publish + savePackageHashForPublish(selectedPackage.path, selectedPackage.name); + + console.log(chalk.green(`\n🎉 ${selectedPackage.name} v${newVersion} published successfully!`)); + } finally { + // Always restore workspace dependencies + restoreWorkspaceDependenciesAfterPublish(selectedPackage.path); + } + + return; + } + + if (action === 'publish-prerelease') { + const { tag } = await inquirer.prompt([ + { + type: 'list', + name: 'tag', + message: 'Select prerelease tag:', + choices: ['beta', 'alpha', 'rc', 'next'] + } + ]); + + const { versionType } = await inquirer.prompt([ + { + type: 'list', + name: 'versionType', + message: 'Version bump type:', + choices: [ + { name: `Prerelease (${tag})`, value: tag }, + { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' }, + { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' }, + { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' }, + { name: 'Custom version', value: 'custom' } + ] + } + ]); + + // Get all package versions for dependency resolution + const packageVersions = {}; + packages.forEach(pkg => { + const currentVersion = getCurrentVersion(pkg.path); + packageVersions[pkg.name] = currentVersion; + }); + + // Track which packages actually need to be published + const packagesToPublish = []; + + // Check for meaningful changes in each package + for (const pkg of packages) { + if (!fs.existsSync(path.join(process.cwd(), pkg.path))) { + console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); + continue; + } + + if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) { + packagesToPublish.push(pkg); + console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be published`)); + } else { + console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); + } + } + + if (packagesToPublish.length === 0) { + console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to publish!')); + return; + } + + // Process each package that has changes + for (const pkg of packagesToPublish) { + let newVersion; + const currentVersion = getCurrentVersion(pkg.path); + + if (versionType === 'custom') { + const { customVersion } = await inquirer.prompt([ + { + type: 'input', + name: 'customVersion', + message: `Enter version for ${pkg.name} (current: ${currentVersion}):`, + default: currentVersion + } + ]); + newVersion = customVersion; + } else { + newVersion = getNextAvailableVersion(pkg.name, currentVersion, versionType); + } + + // Update package version in our tracking + packageVersions[pkg.name] = newVersion; + + // Fix workspace dependencies before publishing + fixWorkspaceDependenciesForPublish(pkg.path, packageVersions); + + try { + updateVersion(pkg.path, newVersion); + // Only build if not disabled + if (!noBuild) { + buildPackage(pkg); + } + publishPackage(pkg.path, pkg.name, tag); + + // Save the hash after successful publish + savePackageHashForPublish(pkg.path, pkg.name); + } finally { + // Always restore workspace dependencies + restoreWorkspaceDependenciesAfterPublish(pkg.path); + } + } + + console.log(chalk.green(`\n🎉 ${packagesToPublish.length} package(s) published with ${tag} tag!`)); + return; + } + + if (action === 'publish-all') { + // Check if we're on master branch for final release + let isMasterBranch = false; + try { + const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim(); + isMasterBranch = branch === 'master' || branch === 'main'; + } catch (error) { + console.log(chalk.yellow('⚠️ Unable to determine current branch')); + } + + const { versionType } = await inquirer.prompt([ + { + type: 'list', + name: 'versionType', + message: 'Version bump type:', + choices: [ + { name: `Beta ${isMasterBranch ? '(will publish as final)' : '(will publish as beta)'}`, value: 'beta' }, + { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' }, + { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' }, + { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' }, + { name: 'Custom version', value: 'custom' } + ] + } + ]); + + // Get all package versions for dependency resolution + const packageVersions = {}; + const originalVersions = {}; + + packages.forEach(pkg => { + const currentVersion = getCurrentVersion(pkg.path); + originalVersions[pkg.name] = currentVersion; + packageVersions[pkg.name] = currentVersion; + }); + + let baseVersion; + if (versionType === 'custom') { + const { customVersion } = await inquirer.prompt([ + { + type: 'input', + name: 'customVersion', + message: 'Enter new version for all packages:', + validate: (input) => { + const semverRegex = /^\d+\.\d+\.\d+$/; + return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)'; + } + } + ]); + baseVersion = customVersion; + } else { + // Use the highest current version as base and increment + const nextVersion = getNextAvailableVersion(packages[0].name, originalVersions[packages[0].name], versionType); + baseVersion = nextVersion; + } + + console.log(chalk.cyan(`\n📋 Will publish all packages with version: ${baseVersion}\n`)); + + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'Are you sure you want to proceed?', + default: false + } + ]); + + if (!confirm) { + console.log(chalk.yellow('Operation cancelled.')); + return; + } + + // Track which packages actually need to be published + const packagesToPublish = []; + + // Check for meaningful changes in each package + for (const pkg of packages) { + if (!fs.existsSync(path.join(process.cwd(), pkg.path))) { + console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); + continue; + } + + if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) { + packagesToPublish.push(pkg); + console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be published`)); + } else { + console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); + } + } + + if (packagesToPublish.length === 0) { + console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to publish!')); + return; + } + + // Update versions, build and publish only packages with changes + for (const pkg of packagesToPublish) { + console.log(chalk.blue(`\n📦 Processing ${pkg.name}...`)); + + // For publish-all, we use the same version for all packages + const pkgVersion = baseVersion; + packageVersions[pkg.name] = pkgVersion; + + // Fix workspace dependencies before publishing + fixWorkspaceDependenciesForPublish(pkg.path, packageVersions); + + try { + updateVersion(pkg.path, pkgVersion); + // Only build if not disabled + if (!noBuild) { + buildPackage(pkg); + } + + // Determine tag based on version type and branch + const tag = (versionType === 'beta' && !isMasterBranch) ? 'beta' : 'latest'; + publishPackage(pkg.path, pkg.name, tag); + + // Save the hash after successful publish + savePackageHashForPublish(pkg.path, pkg.name); + } finally { + // Always restore workspace dependencies + restoreWorkspaceDependenciesAfterPublish(pkg.path); + } + } + + // Create GitHub release if on master and we actually published something + if (isMasterBranch && packagesToPublish.length > 0) { + const tagName = `v${baseVersion}`; + const releaseNotes = `Release ${baseVersion} + +Packages released: +${Object.entries(packageVersions).map(([name, version]) => `- ${name}@${version}`).join('\n')}`; + createGitHubRelease(tagName, releaseNotes); + } + + console.log(chalk.green(`\n🎉 ${packagesToPublish.length} package(s) published successfully with version ${baseVersion}!`)); + console.log(chalk.cyan('\n📋 Next steps:')); + console.log(chalk.gray('1. Create a git tag: git tag v' + baseVersion)); + console.log(chalk.gray('2. Push changes: git push && git push --tags')); + } +} + +// Main function +async function main() { + // Check command line arguments + const args = process.argv.slice(2); + + if (args.includes('--publish')) { + // When called with --publish, start the publish process + console.log(chalk.blue('🏗️ ReactPress Package Builder\n')); + + // Show current versions + console.log(chalk.cyan('📋 Current package versions:')); + packages.forEach(pkg => { + const version = getCurrentVersion(pkg.path); + console.log(chalk.gray(` ${pkg.name}: ${version}`)); + }); + console.log(); + + // Start the publish process + await publishPackages(); + } else if (args.includes('--build')) { + // When called with --build, just build packages + await buildPackages(); + } else { + // Default behavior - show help + console.log(chalk.blue('🏗️ ReactPress CLI\n')); + console.log('Usage:'); + console.log(' reactpress publish --build Build all packages'); + console.log(' reactpress publish --publish Publish packages (interactive)'); + console.log(''); + } +} + +module.exports = { main, buildPackages, publishPackages }; + +if (require.main === module) { + main().catch((error) => { + console.error(chalk.red('❌ Operation failed:'), error); + process.exit(1); + }); +} \ No newline at end of file diff --git a/cli/lib/root.js b/cli/lib/root.js new file mode 100644 index 0000000..553d0a8 --- /dev/null +++ b/cli/lib/root.js @@ -0,0 +1,31 @@ +const fs = require('fs'); +const path = require('path'); + +/** Monorepo root (parent of cli/) */ +function getMonorepoRoot() { + return path.resolve(__dirname, '../..'); +} + +function getProjectRoot() { + return path.resolve(process.env.REACTPRESS_ORIGINAL_CWD || process.cwd()); +} + +function ensureOriginalCwd() { + const root = getProjectRoot(); + process.env.REACTPRESS_ORIGINAL_CWD = root; + return root; +} + +function isMonorepoCheckout(cwd = getMonorepoRoot()) { + return ( + fs.existsSync(path.join(cwd, 'pnpm-workspace.yaml')) || + fs.existsSync(path.join(cwd, 'server', 'src', 'main.ts')) + ); +} + +module.exports = { + getMonorepoRoot, + getProjectRoot, + ensureOriginalCwd, + isMonorepoCheckout, +}; diff --git a/cli/lib/spawn.js b/cli/lib/spawn.js new file mode 100644 index 0000000..46b8eba --- /dev/null +++ b/cli/lib/spawn.js @@ -0,0 +1,92 @@ +const { spawn, spawnSync } = require('child_process'); +const path = require('path'); +const chalk = require('chalk'); +const { getMonorepoRoot } = require('./root'); +const { getCliPackageRoot } = require('./paths'); +const { t, resolveLocale } = require('./i18n'); + +function runSync(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: options.cwd || getMonorepoRoot(), + stdio: 'inherit', + env: { + ...process.env, + REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(), + REACTPRESS_ORIGINAL_CWD: + options.cwd || process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(), + ...options.env, + }, + shell: options.shell ?? false, + }); + if (result.status !== 0) { + const err = new Error( + t('spawn.commandFailed', { command, code: result.status ?? 1 }) + ); + err.exitCode = result.status ?? 1; + throw err; + } + return result; +} + +function runNodeScript(scriptPath, args = [], options = {}) { + return new Promise((resolve, reject) => { + const child = spawn(process.execPath, [scriptPath, ...args], { + stdio: 'inherit', + cwd: options.cwd || getMonorepoRoot(), + env: { + ...process.env, + REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(), + REACTPRESS_ORIGINAL_CWD: + options.cwd || process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(), + ...options.env, + }, + }); + + child.on('error', (error) => { + console.error(chalk.red('[ReactPress]'), error.message || error); + reject(error); + }); + + child.on('close', (code) => { + if (code !== 0) { + reject(Object.assign(new Error(t('spawn.exitCode', { code })), { exitCode: code })); + return; + } + resolve(code); + }); + }); +} + +function spawnDetached(scriptPath, args = [], options = {}) { + const child = spawn(process.execPath, [scriptPath, ...args], { + stdio: options.stdio ?? 'ignore', + detached: true, + cwd: options.cwd, + env: { + ...process.env, + REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(), + REACTPRESS_ORIGINAL_CWD: + options.cwd || process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(), + ...options.env, + }, + }); + child.unref(); + return child; +} + +function runReactpressCli(args, options = {}) { + const cliBin = path.join(getCliPackageRoot(), 'dist', 'index.js'); + return runSync(process.execPath, [cliBin, ...args], options); +} + +function resolveCliScript(relativePath) { + return path.join(__dirname, '..', relativePath); +} + +module.exports = { + runSync, + runNodeScript, + spawnDetached, + runReactpressCli, + resolveCliScript, +}; diff --git a/cli/lib/status.js b/cli/lib/status.js new file mode 100644 index 0000000..2021a7f --- /dev/null +++ b/cli/lib/status.js @@ -0,0 +1,108 @@ +const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); +const { + loadServerSiteUrl, + loadClientSiteUrl, + getHealthUrl, + checkHealth, + isHttpResponding, +} = require('./http'); +const { isUsingMonorepoServer } = require('./paths'); +const { readPid, isProcessRunning } = require('./process'); +const { isDockerRunning } = require('./docker'); +const { ensureOriginalCwd } = require('./root'); +const { t } = require('./i18n'); + +function envFileStatus(projectRoot) { + const envPath = path.join(projectRoot, '.env'); + const configPath = path.join(projectRoot, '.reactpress', 'config.json'); + return { + env: fs.existsSync(envPath), + config: fs.existsSync(configPath), + envPath, + configPath, + }; +} + +async function printUnifiedStatus(projectRoot = ensureOriginalCwd()) { + const env = envFileStatus(projectRoot); + const apiUrl = loadServerSiteUrl(projectRoot); + const clientUrl = loadClientSiteUrl(projectRoot); + const pid = readPid(projectRoot); + const healthUrl = getHealthUrl(projectRoot); + const [apiHttp, clientHttp, health] = await Promise.all([ + isHttpResponding(apiUrl), + isHttpResponding(clientUrl), + checkHealth(healthUrl), + ]); + + const apiSource = isUsingMonorepoServer() + ? t('status.apiSource.monorepo') + : t('status.apiSource.bundle'); + + console.log(''); + console.log(chalk.bold.cyan(t('status.title'))); + console.log(chalk.gray(' ─────────────────────────────────────')); + console.log(t('status.dir', { path: projectRoot })); + console.log(t('status.apiSource', { source: apiSource })); + console.log( + ` ${chalk.bold('Config')} ${ + env.config + ? chalk.green(t('status.configOk')) + : chalk.yellow(t('status.configBad')) + }`, + ); + console.log( + ` ${chalk.bold('.env')} ${ + env.env ? chalk.green(t('status.envOk')) : chalk.yellow(t('status.envBad')) + }`, + ); + console.log(chalk.gray(' ─────────────────────────────────────')); + console.log(chalk.bold(' API')); + console.log(` URL ${apiUrl}`); + console.log( + ` HTTP ${ + apiHttp ? chalk.green(t('status.apiOnline')) : chalk.red(t('status.apiOffline')) + }`, + ); + console.log( + ` Health ${ + health.ok + ? chalk.green(`${healthUrl} ✓`) + : chalk.gray(t('status.apiUnreachable', { url: healthUrl })) + }`, + ); + if (health.ok && health.data?.data) { + const db = health.data.data.database; + console.log( + ` Database ${ + db === 'up' + ? chalk.green(t('status.dbUp')) + : chalk.red(db === 'down' ? t('status.dbDown') : '—') + }`, + ); + } + console.log( + ` PID ${pid ?? '—'} ${ + pid && isProcessRunning(pid) ? chalk.green(t('status.pidRunning')) : '' + }`, + ); + console.log(chalk.bold(t('status.frontend'))); + console.log(` URL ${clientUrl}`); + console.log( + ` HTTP ${ + clientHttp ? chalk.green(t('status.apiOnline')) : chalk.gray(t('status.apiOffline')) + }`, + ); + console.log(chalk.gray(' ─────────────────────────────────────')); + console.log(chalk.bold(t('status.docker'))); + console.log( + ` Engine ${ + isDockerRunning() ? chalk.green(t('status.dockerUp')) : chalk.red(t('status.dockerDown')) + }`, + ); + console.log(''); +} + +module.exports = { printUnifiedStatus, envFileStatus }; diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..f419165 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,61 @@ +{ + "name": "@fecommunity/reactpress", + "version": "3.0.0", + "description": "ReactPress 3.0 — zero-config CMS: one package, one reactpress command", + "author": "fecommunity", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/fecommunity/reactpress.git", + "directory": "cli" + }, + "keywords": [ + "reactpress", + "cms", + "blog", + "cli" + ], + "engines": { + "node": ">=18" + }, + "bin": { + "reactpress": "./bin/reactpress.js", + "reactpress-cli": "./bin/reactpress-cli-shim.js" + }, + "main": "./bin/reactpress.js", + "files": [ + "bin", + "lib", + "ui", + "dist", + "server", + "templates", + "scripts/install-bundled-runtime.mjs", + "README.md", + "LICENSE" + ], + "scripts": { + "prepare": "node scripts/sync-bundled-core.mjs", + "prepack": "node scripts/sync-bundled-core.mjs" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "optionalDependencies": { + "mysql2": "^3.12.0" + }, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.4.1", + "concurrently": "^7.0.0", + "cross-spawn": "^7.0.3", + "fs-extra": "^11.2.0", + "inquirer": "^8.2.4", + "open": "^8.2.1", + "ora": "^5.4.1" + }, + "devDependencies": { + "@fecommunity/reactpress-cli-core": "npm:@fecommunity/reactpress-cli@0.1.0" + } +} diff --git a/cli/scripts/install-bundled-runtime.mjs b/cli/scripts/install-bundled-runtime.mjs new file mode 100644 index 0000000..89e63b5 --- /dev/null +++ b/cli/scripts/install-bundled-runtime.mjs @@ -0,0 +1,46 @@ +import { existsSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const cliRoot = join(dirname(fileURLToPath(import.meta.url)), '..'); +const serverDir = join(cliRoot, 'server'); + +function npmInstall() { + console.log('[reactpress-cli] installing bundled server runtime dependencies…'); + const result = spawnSync( + 'npm', + ['install', '--omit=dev', '--legacy-peer-deps', '--no-bin-links'], + { + cwd: serverDir, + stdio: 'inherit', + shell: process.platform === 'win32', + } + ); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +if (!existsSync(join(serverDir, 'package.json'))) { + console.warn('[reactpress-cli] bundled server missing, skip runtime install'); + process.exit(0); +} + +const serverRuntimeModules = [ + ['node_modules', '@nestjs', 'core'], + ['node_modules', '@nestjs', 'typeorm'], + ['node_modules', 'typeorm'], + ['node_modules', 'express'], + ['node_modules', '@fecommunity', 'reactpress-toolkit'], +]; + +const serverReady = + existsSync(join(serverDir, 'dist', 'main.js')) && + serverRuntimeModules.every((parts) => existsSync(join(serverDir, ...parts))); + +if (serverReady) { + process.exit(0); +} + +npmInstall(); diff --git a/cli/scripts/sync-bundled-core.mjs b/cli/scripts/sync-bundled-core.mjs new file mode 100644 index 0000000..6802188 --- /dev/null +++ b/cli/scripts/sync-bundled-core.mjs @@ -0,0 +1,84 @@ +#!/usr/bin/env node +/** + * Copy core CLI runtime (dist, server, templates) from the legacy npm package + * so @fecommunity/reactpress-cli can be published as a self-contained package. + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const cliRoot = path.join(__dirname, '..'); +const require = createRequire(import.meta.url); + +const LEGACY_PKG = '@fecommunity/reactpress-cli-core'; + +const SKIP_DIRS = new Set(['node_modules', '.git', 'logs']); +const SKIP_FILES = new Set(['package-lock.json']); + +function copyDir(src, dest) { + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + if (entry.isDirectory() && SKIP_DIRS.has(entry.name)) continue; + if (!entry.isDirectory() && SKIP_FILES.has(entry.name)) continue; + const from = path.join(src, entry.name); + const to = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDir(from, to); + } else { + fs.copyFileSync(from, to); + } + } +} + +function main() { + let legacyRoot; + try { + legacyRoot = path.dirname(require.resolve(`${LEGACY_PKG}/package.json`)); + } catch { + console.warn( + `[sync-bundled-core] Skip: install devDependency ${LEGACY_PKG} (npm:@fecommunity/reactpress-cli@0.1.0) first` + ); + process.exit(0); + } + + const entries = ['dist', 'server', 'templates', 'scripts']; + for (const name of entries) { + const src = path.join(legacyRoot, name); + if (!fs.existsSync(src)) continue; + const dest = path.join(cliRoot, name); + copyDir(src, dest); + console.log(`[sync-bundled-core] ${name}/ -> cli/${name}/`); + } + + for (const file of ['LICENSE', 'README.md']) { + const src = path.join(legacyRoot, file); + if (fs.existsSync(src)) { + fs.copyFileSync(src, path.join(cliRoot, file)); + } + } + + const distPkg = path.join(cliRoot, 'dist', 'package.json'); + fs.writeFileSync(distPkg, JSON.stringify({ type: 'module' }, null, 2) + '\n'); + + // Keep ESM bridge to CommonJS i18n (not shipped in legacy package) + const i18nBridge = path.join(cliRoot, 'dist', 'i18n.js'); + if (!fs.existsSync(i18nBridge)) { + fs.writeFileSync( + i18nBridge, + `import { createRequire } from 'node:module'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const require = createRequire(import.meta.url); +const { t, getLocale, setLocale } = require(join(dirname(fileURLToPath(import.meta.url)), '..', 'lib', 'i18n', 'index.js')); + +export { t, getLocale, setLocale }; +` + ); + console.log('[sync-bundled-core] restored dist/i18n.js bridge'); + } +} + +main(); diff --git a/cli/server/bin/reactpress-server.js b/cli/server/bin/reactpress-server.js new file mode 100644 index 0000000..f9943e5 --- /dev/null +++ b/cli/server/bin/reactpress-server.js @@ -0,0 +1,204 @@ +#!/usr/bin/env node + +/** + * ReactPress Server CLI Entry Point + * This script allows starting the ReactPress server via npx + * Supports both regular and PM2 startup modes + */ + +const path = require('path'); +const fs = require('fs'); +const { spawn, spawnSync } = require('child_process'); +const { t } = require('../../lib/i18n'); + +// Capture the original working directory where npx was executed +// BUT prioritize the REACTPRESS_ORIGINAL_CWD environment variable if it exists +// This ensures consistency when running via pnpm dev from root directory +const originalCwd = process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(); + +// Get command line arguments +const args = process.argv.slice(2); +const usePM2 = args.includes('--pm2'); +const showHelp = args.includes('--help') || args.includes('-h'); + +// Show help if requested +if (showHelp) { + console.log(` +ReactPress Server - NestJS-based backend API for ReactPress CMS + +Usage: + reactpress-server [options] +${t('server.help.invokedBy')} + +Options: + --pm2 Start server with PM2 process manager + --help, -h Show this help message + +Examples: + node bin/reactpress-server.js # Start server normally + node bin/reactpress-server.js --pm2 # Start server with PM2 + `); + process.exit(0); +} + +// Get the directory where this script is located +const binDir = __dirname; +const serverDir = path.join(binDir, '..'); +const distPath = path.join(serverDir, 'dist', 'main.js'); +const ecosystemPath = path.join(serverDir, 'ecosystem.config.js'); + +// Function to check if PM2 is installed +function isPM2Installed() { + try { + require.resolve('pm2'); + return true; + } catch (e) { + // Check if PM2 is installed globally + try { + spawnSync('pm2', ['--version'], { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } + } +} + +// Function to install PM2 +function installPM2() { + console.log('[ReactPress Server] Installing PM2...'); + const installResult = spawnSync('npm', ['install', 'pm2', '--no-save'], { + stdio: 'inherit', + cwd: serverDir + }); + + if (installResult.status !== 0) { + console.error('[ReactPress Server] Failed to install PM2'); + return false; + } + + return true; +} + +// Function to start with PM2 +function startWithPM2() { + // Check if PM2 is installed + if (!isPM2Installed()) { + // Try to install PM2 + if (!installPM2()) { + console.error('[ReactPress Server] Cannot start with PM2'); + process.exit(1); + } + } + + // Check if the server is built + if (!fs.existsSync(distPath)) { + console.log('[ReactPress Server] Server not built yet. Building...'); + + // Try to build the server + const buildResult = spawnSync('npm', ['run', 'build'], { + stdio: 'inherit', + cwd: serverDir + }); + + if (buildResult.status !== 0) { + console.error('[ReactPress Server] Failed to build server'); + process.exit(1); + } + } + + console.log('[ReactPress Server] Starting with PM2...'); + + // Use ecosystem.config.js if it exists, otherwise use direct command + let pm2Command = 'pm2'; + try { + // Try to resolve PM2 path + pm2Command = path.join(serverDir, 'node_modules', '.bin', 'pm2'); + if (!fs.existsSync(pm2Command)) { + pm2Command = 'pm2'; + } + } catch (e) { + pm2Command = 'pm2'; + } + + // Check if ecosystem.config.js exists + if (fs.existsSync(ecosystemPath)) { + const pm2 = spawn(pm2Command, ['start', ecosystemPath], { + stdio: 'inherit', + cwd: serverDir + }); + + pm2.on('close', (code) => { + console.log(`[ReactPress Server] PM2 process exited with code ${code}`); + process.exit(code); + }); + + pm2.on('error', (error) => { + console.error('[ReactPress Server] Failed to start with PM2:', error); + process.exit(1); + }); + } else { + // Fallback to direct start + const pm2 = spawn(pm2Command, ['start', distPath, '--name', 'reactpress-server'], { + stdio: 'inherit', + cwd: serverDir + }); + + pm2.on('close', (code) => { + console.log(`[ReactPress Server] PM2 process exited with code ${code}`); + process.exit(code); + }); + + pm2.on('error', (error) => { + console.error('[ReactPress Server] Failed to start with PM2:', error); + process.exit(1); + }); + } +} + +// Function to start with regular Node.js +function startWithNode() { + // Check if the server is built + if (!fs.existsSync(distPath)) { + console.log('[ReactPress Server] Server not built yet. Building...'); + + // Try to build the server + const buildResult = spawnSync('npm', ['run', 'build'], { + stdio: 'inherit', + cwd: serverDir + }); + + if (buildResult.status !== 0) { + console.error('[ReactPress Server] Failed to build server'); + process.exit(1); + } + } + + // ONLY set the environment variable if it's not already set + // This preserves the value set by set-env.js when running pnpm dev from root + if (!process.env.REACTPRESS_ORIGINAL_CWD) { + process.env.REACTPRESS_ORIGINAL_CWD = originalCwd; + } else { + console.log(`[ReactPress Server] Using existing REACTPRESS_ORIGINAL_CWD: ${process.env.REACTPRESS_ORIGINAL_CWD}`); + } + + // Change to the server directory + process.chdir(serverDir); + + // Set environment variables + process.env.NODE_ENV = process.env.NODE_ENV || 'production'; + + // Import and run the server + try { + require(distPath); + } catch (error) { + console.error('[ReactPress Server] Failed to start server:', error); + process.exit(1); + } +} + +// Main execution +if (usePM2) { + startWithPM2(); +} else { + startWithNode(); +} diff --git a/cli/server/ecosystem.config.js b/cli/server/ecosystem.config.js new file mode 100644 index 0000000..6a90515 --- /dev/null +++ b/cli/server/ecosystem.config.js @@ -0,0 +1,18 @@ +module.exports = { + apps: [ + { + name: 'reactpress-server', + script: './dist/main.js', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '1G', + env: { + NODE_ENV: 'production', + }, + env_development: { + NODE_ENV: 'development', + }, + }, + ], +}; \ No newline at end of file diff --git a/cli/server/package.json b/cli/server/package.json new file mode 100644 index 0000000..4f84e92 --- /dev/null +++ b/cli/server/package.json @@ -0,0 +1,146 @@ +{ + "name": "reactpress-server", + "version": "1.0.0-beta.16", + "description": "ReactPress Server - NestJS-based backend API for ReactPress CMS", + "author": "fecommunity", + "license": "MIT", + "main": "dist/main.js", + "types": "dist/index.d.ts", + "files": [ + "dist/**/*", + "public/**/*", + "bin/**/*", + "README.md", + "LICENSE" + ], + "keywords": [ + "reactpress", + "server", + "nestjs", + "cms", + "blog", + "api", + "typescript" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/fecommunity/reactpress.git", + "directory": "server" + }, + "engines": { + "node": ">=16.5.0" + }, + "scripts": { + "prebuild": "rimraf dist", + "build": "nest build && npm run copy-assets", + "copy-assets": "cp -r public dist/ 2>/dev/null || true", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "dev": "nest start --watch", + "debug": "nest start --debug --watch", + "start": "cross-env NODE_ENV=production node dist/main", + "pm2": "pm2 start npm --name @fecommunity/reactpress-server -- start", + "lint": "tslint -p tsconfig.json -c tslint.json", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json", + "prepublishOnly": "npm run build", + "pm2:start": "pm2 start dist/main.js --name reactpress-server", + "pm2:stop": "pm2 stop reactpress-server", + "pm2:restart": "pm2 restart reactpress-server", + "pm2:delete": "pm2 delete reactpress-server", + "pm2:logs": "pm2 logs reactpress-server", + "pm2:status": "pm2 status reactpress-server", + "generate:swagger": "ts-node src/generate-swagger.ts" + }, + "dependencies": { + "@fecommunity/reactpress-toolkit": "file:../toolkit", + "@nestjs/cli": "^6.9.0", + "@nestjs/common": "^6.7.2", + "@nestjs/config": "^0.6.3", + "@nestjs/core": "^6.7.2", + "@nestjs/jwt": "^6.1.1", + "@nestjs/passport": "^6.1.1", + "@nestjs/platform-express": "^6.11.5", + "@nestjs/swagger": "^4.8.2", + "@nestjs/typeorm": "^6.3.4", + "@types/express-serve-static-core": "^4.19.5", + "ali-oss": "^6.5.1", + "axios": "^0.23.0", + "bcryptjs": "^2.4.3", + "body-parser": "^1.19.0", + "chalk": "^4.1.2", + "class-transformer": "^0.2.3", + "commander": "^9.4.1", + "compression": "^1.7.4", + "cross-env": "^7.0.3", + "date-fns": "^2.17.0", + "deepmerge": "^4.2.2", + "dotenv": "^8.2.0", + "express": "^4.18.2", + "express-rate-limit": "^5.0.0", + "fs-extra": "^10.0.0", + "helmet": "^3.21.2", + "highlight.js": "^9.18.0", + "inquirer": "^8.2.4", + "lodash": "^4.17.21", + "log4js": "^6.1.0", + "marked": "^0.8.0", + "mysql2": "^3.12.0", + "node-ip2region": "^1.0.2", + "nodemailer": "^6.4.2", + "nuid": "^1.1.0", + "open": "^8.2.1", + "passport": "^0.4.1", + "passport-jwt": "^4.0.0", + "pm2": "^5.2.0", + "reflect-metadata": "^0.1.13", + "rimraf": "^3.0.0", + "rxjs": "^6.5.3", + "segment": "^0.1.3", + "swagger-themes": "^1.4.3", + "swagger-ui-express": "^4.1.6", + "typeorm": "^0.2.45", + "ua-parser-js": "^0.7.28" + }, + "devDependencies": { + "@nestjs/schematics": "^6.7.0", + "@nestjs/testing": "^6.7.1", + "@types/express": "4.17.18", + "@types/jest": "^24.0.18", + "@types/node": "^12.7.5", + "@types/supertest": "^2.0.8", + "@typescript-eslint/eslint-plugin": "^5.21.0", + "@typescript-eslint/parser": "^5.21.0", + "eslint": "^8.15.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-simple-import-sort": "^7.0.0", + "jest": "^24.9.0", + "next-transpile-modules": "^6.3.0", + "prettier": "^1.18.2", + "supertest": "^4.0.2", + "ts-jest": "^24.1.0", + "ts-loader": "^6.1.1", + "ts-node": "^8.4.1", + "tsconfig-paths": "^3.9.0", + "tslint": "^5.20.0", + "typescript": "~4.1.6" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/cli/server/public/favicon.png b/cli/server/public/favicon.png new file mode 100644 index 0000000..521edf8 Binary files /dev/null and b/cli/server/public/favicon.png differ diff --git a/cli/server/public/index.html b/cli/server/public/index.html new file mode 100644 index 0000000..4c285e3 --- /dev/null +++ b/cli/server/public/index.html @@ -0,0 +1,439 @@ + + + + + + ReactPress › Installation + + + +
+ + +
+
+
+ +
+ +
+

Database Connection Information

+ +
+ You should have this information available from your web hosting provider. If you don't have it, contact them before continuing. +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +

Usually "localhost" or "127.0.0.1"

+
+ +

Default MySQL port is 3306

+
+ +

Your MySQL username

+
+ +

Your MySQL password

+
+ +

The name of the database you want to use with ReactPress

+
+ +
+ +

+ +

+
+ + +
+

Site Information

+ +
+ Please provide the following information. Don't worry, you can always change these settings later. +
+ + + + + + + + + + +
+ +

The URL where your ReactPress frontend will be accessible

+
+ +

The URL where your ReactPress API server will run

+
+ +
+ +

+ +

+
+ + +
+

🎉 Installation Complete!

+ +
+

ReactPress has been installed successfully!

+
+ +

Your ReactPress server configuration has been saved. You can:

+ +
    +
  • Close this browser window
  • +
  • Restart the server by running npx @fecommunity/reactpress-server in your terminal
  • +
  • Access your admin panel at http://localhost:3001/admin (when client is running)
  • +
+ + +
+

Waiting for server to start...

+
+ +
+ + +
+ +
+ Note: To start the server with your new configuration, run npx @fecommunity/reactpress-server in your terminal. +
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/cli/server/public/swagger/custom.css b/cli/server/public/swagger/custom.css new file mode 100644 index 0000000..fee918b --- /dev/null +++ b/cli/server/public/swagger/custom.css @@ -0,0 +1,11 @@ +.swagger-ui div.topbar { + display: none !important; + padding: 0 !important; +} + + +.swagger-ui .scheme-container { + display: none !important; + margin: 0 !important; + padding: 0 !important; +} \ No newline at end of file diff --git a/cli/templates/config.default.json b/cli/templates/config.default.json new file mode 100644 index 0000000..7144102 --- /dev/null +++ b/cli/templates/config.default.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "database": { + "mode": "embedded-docker", + "containerName": "reactpress_cli_db" + }, + "server": { + "port": 3002, + "clientUrl": "http://localhost:3001", + "serverUrl": "http://localhost:3002" + } +} diff --git a/cli/templates/docker-compose.prod.yml b/cli/templates/docker-compose.prod.yml new file mode 100644 index 0000000..5dc89de --- /dev/null +++ b/cli/templates/docker-compose.prod.yml @@ -0,0 +1,56 @@ +# ReactPress 3.0 生产部署示例 +# 用法: 复制到项目根目录,按需修改环境变量后 docker compose -f docker-compose.prod.yml up -d + +services: + db: + image: mysql:8.0 + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWD:-reactpress} + MYSQL_DATABASE: ${DB_DATABASE:-reactpress} + volumes: + - reactpress_mysql_data:/var/lib/mysql + ports: + - '${DB_PORT:-3306}:3306' + healthcheck: + test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost'] + interval: 10s + timeout: 5s + retries: 5 + + api: + image: node:20-alpine + restart: unless-stopped + working_dir: /app + depends_on: + db: + condition: service_healthy + environment: + NODE_ENV: production + DB_HOST: db + DB_PORT: 3306 + DB_USER: root + DB_PASSWD: ${DB_PASSWD:-reactpress} + DB_DATABASE: ${DB_DATABASE:-reactpress} + SERVER_PORT: 3002 + SERVER_SITE_URL: http://localhost:3002 + SERVER_API_PREFIX: /api + ports: + - '3002:3002' + command: sh -c "npm i -g @fecommunity/reactpress-cli@3 && reactpress-cli start --api-only" + # 生产建议挂载已 init 的项目目录,或使用自定义镜像内置 dist + + client: + image: node:20-alpine + restart: unless-stopped + depends_on: + - api + environment: + CLIENT_SITE_URL: http://localhost:3001 + SERVER_SITE_URL: http://api:3002 + ports: + - '3001:3001' + command: sh -c "npm i -g @fecommunity/reactpress-client@3 && reactpress-client start" + +volumes: + reactpress_mysql_data: diff --git a/cli/templates/docker-compose.yml b/cli/templates/docker-compose.yml new file mode 100644 index 0000000..4755498 --- /dev/null +++ b/cli/templates/docker-compose.yml @@ -0,0 +1,27 @@ +services: + db: + image: mysql:8.0 + container_name: reactpress_cli_db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-reactpress_root} + MYSQL_DATABASE: ${DB_DATABASE:-reactpress} + MYSQL_USER: ${DB_USER:-reactpress} + MYSQL_PASSWORD: ${DB_PASSWD:-reactpress} + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --default-authentication-plugin=mysql_native_password + volumes: + - reactpress_cli_db_data:/var/lib/mysql + ports: + - "${DB_PORT:-3306}:3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-reactpress_root}"] + interval: 5s + timeout: 5s + retries: 12 + start_period: 20s + +volumes: + reactpress_cli_db_data: diff --git a/cli/templates/env.default b/cli/templates/env.default new file mode 100644 index 0000000..c38aa03 --- /dev/null +++ b/cli/templates/env.default @@ -0,0 +1,11 @@ +# ReactPress — auto-generated by reactpress-cli (do not edit DB_* unless you know what you are doing) +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_USER=reactpress +DB_PASSWD=reactpress +DB_DATABASE=reactpress + +CLIENT_SITE_URL=http://localhost:3001 +SERVER_SITE_URL=http://localhost:3002 +SERVER_PORT=3002 +SERVER_API_PREFIX=/api diff --git a/cli/templates/package.json b/cli/templates/package.json new file mode 100644 index 0000000..aaf0974 --- /dev/null +++ b/cli/templates/package.json @@ -0,0 +1,10 @@ +{ + "name": "reactpress-site", + "private": true, + "version": "1.0.0", + "description": "ReactPress CMS & Blog site", + "scripts": { + "start": "reactpress-cli start", + "stop": "reactpress-cli stop" + } +} diff --git a/cli/ui/banner.js b/cli/ui/banner.js new file mode 100644 index 0000000..3a80de3 --- /dev/null +++ b/cli/ui/banner.js @@ -0,0 +1,22 @@ +const chalk = require('chalk'); +const { brand } = require('./theme'); +const { getMonorepoRoot } = require('../lib/root'); +const path = require('path'); +const { t } = require('../lib/i18n'); + +function printBanner() { + const version = require(path.join(getMonorepoRoot(), 'package.json')).version; + console.log(''); + console.log(brand.primary(' ╭─────────────────────────────────────────╮')); + console.log( + brand.primary(' │') + + chalk.bold.white(' ReactPress') + + brand.muted(t('banner.subtitle')) + + brand.primary('│'), + ); + console.log(brand.primary(' ╰─────────────────────────────────────────╯')); + console.log(brand.muted(` v${version} · init · dev · build · deploy · publish`)); + console.log(''); +} + +module.exports = { printBanner }; diff --git a/cli/ui/interactive.js b/cli/ui/interactive.js new file mode 100644 index 0000000..700391f --- /dev/null +++ b/cli/ui/interactive.js @@ -0,0 +1,184 @@ +const inquirer = require('inquirer'); +const open = require('open'); +const { printBanner } = require('./banner'); +const { brand, label } = require('./theme'); +const { ensureOriginalCwd } = require('../lib/root'); +const { ensureProjectEnvironment } = require('../lib/bootstrap'); +const { runDev } = require('../lib/dev'); +const { runApiDev } = require('../lib/api-dev'); +const { runLifecycleCommand } = require('../lib/lifecycle'); +const { runDockerCommand } = require('../lib/docker'); +const { printUnifiedStatus } = require('../lib/status'); +const { runDoctor } = require('../lib/doctor'); +const { runBuild, TARGETS } = require('../lib/build'); +const { runNodeScript } = require('../lib/spawn'); +const { getClientBin } = require('../lib/paths'); +const { loadClientSiteUrl } = require('../lib/http'); +const { t } = require('../lib/i18n'); + +function getMenuActions() { + return [ + { name: t('menu.dev'), value: 'dev' }, + { name: t('menu.init'), value: 'init' }, + { name: t('menu.status'), value: 'status' }, + { name: t('menu.doctor'), value: 'doctor' }, + new inquirer.Separator(), + { name: t('menu.devApi'), value: 'dev:api' }, + { name: t('menu.devClient'), value: 'dev:client' }, + { name: t('menu.serverStart'), value: 'server:start' }, + { name: t('menu.serverStop'), value: 'server:stop' }, + { name: t('menu.serverRestart'), value: 'server:restart' }, + new inquirer.Separator(), + { name: t('menu.build'), value: 'build' }, + { name: t('menu.dockerStart'), value: 'docker:start' }, + { name: t('menu.dockerUp'), value: 'docker:up' }, + { name: t('menu.dockerStop'), value: 'docker:stop' }, + new inquirer.Separator(), + { name: t('menu.openAdmin'), value: 'open:admin' }, + { name: t('menu.publish'), value: 'publish' }, + { name: t('menu.exit'), value: 'exit' }, + ]; +} + +async function runMenuAction(action, projectRoot) { + switch (action) { + case 'dev': + console.log(label(t('menu.startingDev'))); + await runDev(projectRoot); + return false; + case 'init': { + console.log(label(t('menu.initProject'))); + const result = await ensureProjectEnvironment(projectRoot); + console.log(brand.success(result.message || t('menu.done'))); + return true; + } + case 'status': + await printUnifiedStatus(projectRoot); + return true; + case 'doctor': { + const code = await runDoctor(projectRoot); + if (code !== 0) process.exit(code); + return true; + } + case 'dev:api': + await runApiDev(projectRoot); + return false; + case 'dev:client': + await runNodeScript(getClientBin(), [], { cwd: projectRoot }); + return false; + case 'server:start': { + const code = await runLifecycleCommand('start', projectRoot); + if (code !== 0) process.exit(code); + return true; + } + case 'server:stop': + await runLifecycleCommand('stop', projectRoot); + return true; + case 'server:restart': { + const code = await runLifecycleCommand('restart', projectRoot); + if (code !== 0) process.exit(code); + return true; + } + case 'build': { + const buildChoices = TARGETS.map((target) => ({ + name: + target === 'all' + ? t('menu.buildAll') + : t(`build.label.${target}`), + value: target, + })); + const { target } = await inquirer.prompt([ + { + type: 'list', + name: 'target', + message: t('menu.buildTarget'), + choices: buildChoices, + }, + ]); + await runBuild(target, projectRoot); + return true; + } + case 'docker:start': + await runDockerCommand('start', projectRoot); + return false; + case 'docker:up': + await runDockerCommand('up', projectRoot); + return true; + case 'docker:stop': + await runDockerCommand('down', projectRoot); + return true; + case 'open:admin': { + const url = loadClientSiteUrl(projectRoot); + console.log(label(t('menu.opening', { url }))); + await open(url); + return true; + } + case 'publish': { + const prev = process.argv.slice(); + process.argv = [process.argv[0], process.argv[1], '--publish']; + await require('../lib/publish').main(); + process.argv = prev; + return true; + } + case 'exit': + console.log(brand.muted(t('menu.goodbye'))); + return false; + default: + return true; + } +} + +async function runInteractiveLoop() { + const projectRoot = ensureOriginalCwd(); + printBanner(); + + let loop = true; + while (loop) { + const { action } = await inquirer.prompt([ + { + type: 'list', + name: 'action', + message: t('menu.prompt'), + pageSize: 14, + choices: getMenuActions(), + }, + ]); + + if (action === 'exit') { + console.log(brand.muted(t('menu.goodbye'))); + break; + } + + try { + const stay = await runMenuAction(action, projectRoot); + if (!stay) { + break; + } + + if (action !== 'status') { + const { again } = await inquirer.prompt([ + { + type: 'confirm', + name: 'again', + message: t('menu.back'), + default: true, + }, + ]); + loop = again; + } + } catch (err) { + console.error(brand.error(` ${err.message || err}`)); + const { retry } = await inquirer.prompt([ + { + type: 'confirm', + name: 'retry', + message: t('menu.retry'), + default: true, + }, + ]); + loop = retry; + } + } +} + +module.exports = { runInteractiveLoop, runMenuAction }; diff --git a/cli/ui/theme.js b/cli/ui/theme.js new file mode 100644 index 0000000..d752db2 --- /dev/null +++ b/cli/ui/theme.js @@ -0,0 +1,25 @@ +const chalk = require('chalk'); + +const brand = { + primary: chalk.hex('#6366f1'), + accent: chalk.hex('#22d3ee'), + success: chalk.green, + warn: chalk.yellow, + error: chalk.red, + muted: chalk.gray, + bold: chalk.bold, +}; + +function label(text) { + return brand.primary(`› ${text}`); +} + +function ok(text) { + return brand.success(`✓ ${text}`); +} + +function fail(text) { + return brand.error(`✗ ${text}`); +} + +module.exports = { brand, label, ok, fail }; diff --git a/client/README.md b/client/README.md index 6d49396..154717e 100644 --- a/client/README.md +++ b/client/README.md @@ -56,10 +56,10 @@ Perfect for: - Microfrontend architecture ### Full ReactPress Stack -Use with ReactPress server for complete CMS solution: +Use with ReactPress API for complete CMS solution: ```bash -# Start server first -npx @fecommunity/reactpress-server +# Start API first +pnpm exec reactpress-cli start # In another terminal, start client npx @fecommunity/reactpress-client diff --git a/client/package.json b/client/package.json index 9734a99..ee8b939 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@fecommunity/reactpress-client", - "version": "1.0.0-beta.32", + "version": "3.0.0", "bin": { "reactpress-client": "./bin/reactpress-client.js" }, @@ -50,6 +50,7 @@ "next-sitemap": "^1.6.102", "next-with-less": "^2.0.5", "nprogress": "^0.2.0", + "open": "^8.4.2", "preact": "^10.5.14", "qrcode-svg": "^1.1.0", "react": "17.0.2", diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7e8f1e1..2196089 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -19,27 +19,8 @@ services: networks: - reactpress-network - server: - build: - context: . - dockerfile: server/Dockerfile - container_name: reactpress_server - restart: always - depends_on: - - db - environment: - - NODE_ENV=production - # 从.env文件读取环境变量,使用默认值 - - DB_HOST=${DB_HOST:-db} - - DB_PORT=${DB_PORT:-3306} - - DB_USER=${DB_USER:-reactpress} - - DB_PASSWD=${DB_PASSWD:-reactpress} - - DB_DATABASE=${DB_DATABASE:-reactpress} - - SERVER_SITE_URL=${SERVER_SITE_URL:-http://localhost:3002} - ports: - - "3002:3002" - networks: - - reactpress-network + # API 由宿主机 monorepo server/ 提供(pnpm run start:api 或 pm2:api) + # nginx 通过 host.docker.internal:3002 转发 /api client: build: @@ -47,8 +28,6 @@ services: dockerfile: client/Dockerfile container_name: reactpress_client restart: always - depends_on: - - server environment: - NODE_ENV=production # 从.env文件读取环境变量,使用默认值 @@ -66,7 +45,8 @@ services: - "8080:80" depends_on: - client - - server + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf networks: diff --git a/docs/blog/changelog.md b/docs/blog/changelog.md new file mode 100644 index 0000000..f301d45 --- /dev/null +++ b/docs/blog/changelog.md @@ -0,0 +1,66 @@ +--- +slug: changelog +title: Changelog +date: 2026-05-17 +authors: [fecommunity] +tags: [reactpress, release] +--- + + + +## [3.0.0](https://github.com/fecommunity/reactpress/compare/v2.0.2...v3.0.0) (2026-05-17) + +**ReactPress 3.0 Platform** — one package, one command, your CMS in about a minute. + +- **Zero config**: `reactpress init` + `reactpress dev`, embedded Docker MySQL +- **Single entry**: `@fecommunity/reactpress@3` +- **DX**: interactive menu, `doctor`, `status`, Headless (API Key, Webhook, health) +- **Breaking**: `@fecommunity/reactpress-server` deprecated — [migration guide](/docs/tutorial-extras/migration-2-to-3) + +--- + +## [2.0.1](https://github.com/fecommunity/reactpress/compare/v2.0.0...v2.0.1) (2025-09-26) + +### Bug Fixes + +* correct HttpClient filename case sensitivity ([7dd892a](https://github.com/fecommunity/reactpress/commit/7dd892a8d5b05a3ab24eaf73577848eb25b06450)) + +### Features + +* add config for toolkit package ([0ed839d](https://github.com/fecommunity/reactpress/commit/0ed839d4667d671ea06b088c0bac5a2890680445)) + +## [2.0.0](https://github.com/fecommunity/reactpress/compare/v1.10.0...v2.0.0) (2025-09-21) + +### Bug Fixes + +* server load issue ([a6f759b](https://github.com/fecommunity/reactpress/commit/a6f759b386e32727501b0eea3ea38f5a89dfe700)) +* type defs ([d6491d5](https://github.com/fecommunity/reactpress/commit/d6491d56f2ffdd19d5a47fda7273958cd4243fb3)) + +### Features + +* add hello-world template ([7e2c948](https://github.com/fecommunity/reactpress/commit/7e2c9487ddc6023d7b382250b131fbe828013680)) +* add reactpress toolkit ([58f9312](https://github.com/fecommunity/reactpress/commit/58f9312644736aceb362e517fad8c3b3a83f275f)) +* add swagger v2 ui ([ef9fdc1](https://github.com/fecommunity/reactpress/commit/ef9fdc166955b4659c81fb559138ce38ef599cfe)) +* add twentytwentyfive theme ([715281f](https://github.com/fecommunity/reactpress/commit/715281fedcf8072348e4b8b6794891c7e67e1f99)) +* support npx install server ([e7f7b97](https://github.com/fecommunity/reactpress/commit/e7f7b970bb4dd8b845fcd8dde4048678a403557a)) +* support quick install ([96c1d0a](https://github.com/fecommunity/reactpress/commit/96c1d0a7cc1c72b7f6c489ba236ab6eb78472dee)) + +## [1.10.0](https://github.com/fecommunity/reactpress/compare/v1.9.0...v1.10.0) (2025-08-03) + +### Features + +* add type defs for config ([d8a6fed](https://github.com/fecommunity/reactpress/commit/d8a6fed7bc13f74be0916f80497590c7e737fb86)) + +## [1.9.0](https://github.com/fecommunity/reactpress/compare/v1.8.0...v1.9.0) (2025-05-21) + +## [1.8.0](https://github.com/fecommunity/reactpress/compare/v1.7.0...v1.8.0) (2025-03-22) + +### Features + +* upgrade next version ([64cac4d](https://github.com/fecommunity/reactpress/commit/64cac4dcb9268a6bbb14fbbfe6995406638f7508)) + +## [1.0.0](https://github.com/fecommunity/reactpress/compare/a6b73a189090e0199cc6f803bfb498cdeb7868a5...v1.0.0) (2024-09-28) + +### Features + +* init easy-blog project ([a6b73a1](https://github.com/fecommunity/reactpress/commit/a6b73a189090e0199cc6f803bfb498cdeb7868a5)) diff --git a/docs/blog/changlog.md b/docs/blog/changlog.md deleted file mode 100644 index 5baf0df..0000000 --- a/docs/blog/changlog.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -slug: changelog -title: 更新日志 -authors: [fecommunity] -tags: [reactpress] ---- - - - -# [1.9.0](https://github.com/fecommunity/reactpress/compare/v1.8.0...v1.9.0) (2025-05-21) - -# [1.8.0](https://github.com/fecommunity/reactpress/compare/v1.7.0...v1.8.0) (2025-03-22) - -### Features - -- upgrade next version ([64cac4d](https://github.com/fecommunity/reactpress/commit/64cac4dcb9268a6bbb14fbbfe6995406638f7508)) - -# [1.7.0](https://github.com/fecommunity/reactpress/compare/v1.6.0...v1.7.0) (2025-03-07) - -### Features - -- add reactpress docs ([002972b](https://github.com/fecommunity/reactpress/commit/002972b6194d13917d60de8dae019445739760cc)) -- release reactpress v1.6.0 ([9d75118](https://github.com/fecommunity/reactpress/commit/9d75118b7e19d85ab95b3e6f227f1b95cd95acb8)) - -# [1.6.0](https://github.com/fecommunity/reactpress/compare/v1.5.0...v1.6.0) (2024-12-21) - -### Features - -- knowledge page perfect ([#16](https://github.com/fecommunity/reactpress/issues/16)) ([b9ae27d](https://github.com/fecommunity/reactpress/commit/b9ae27d087b3b451668b3c0acb40359b20a089e2)) - -# [1.5.0](https://github.com/fecommunity/reactpress/compare/v1.4.0...v1.5.0) (2024-12-21) - -### Bug Fixes - -- adapt small screen content display issue ([84895c8](https://github.com/fecommunity/reactpress/commit/84895c8341d34285068806d5908b2af358719f80)) -- add key for tex loop item ([e8a2e36](https://github.com/fecommunity/reactpress/commit/e8a2e36f13d5031d845c3c3055374e5ff0446bc2)) -- nav config build error ([66acf46](https://github.com/fecommunity/reactpress/commit/66acf46485f902aa0647b4886beb0701372b95c2)) -- nav query id undefined ([2d0bbd7](https://github.com/fecommunity/reactpress/commit/2d0bbd78f4d73579039b3a9bf7c846fe2d976073)) -- route level default value ([4aae64e](https://github.com/fecommunity/reactpress/commit/4aae64e86346052cb51e63e7c149853a6d8d8ae6)) - -### Features - -- add animation tags cloud ([68b1a5b](https://github.com/fecommunity/reactpress/commit/68b1a5b2fe73fafd7a9b282ed77f836aa7a76bf6)) -- add global config setting page ([c825425](https://github.com/fecommunity/reactpress/commit/c82542540b20f423b3b3f3ba04ea5e48e2523f5c)) -- add nav page ([c6703c6](https://github.com/fecommunity/reactpress/commit/c6703c6e3dea33a8cc226f1c02d9b5cac0ef827e)) -- optimized page interaction experience ([b0ac19a](https://github.com/fecommunity/reactpress/commit/b0ac19ab31b6bcf0d8916aeb373e6fa7288303aa)) - -# [1.4.0](https://github.com/fecommunity/reactpress/compare/v1.3.0...v1.4.0) (2024-12-08) - -# [1.3.0](https://github.com/fecommunity/reactpress/compare/v1.2.0...v1.3.0) (2024-12-01) - -### Features - -- add system systemSubTitle setting config ([136f012](https://github.com/fecommunity/reactpress/commit/136f01288cb9714092a2aa0dc01421817bb26b7d)) -- optimized the css style loading experience for homepage ([0e7761f](https://github.com/fecommunity/reactpress/commit/0e7761fd28c1cac099ac15ddaa644a904aca8da4)) -- react helmet seo perfect ([848a5da](https://github.com/fecommunity/reactpress/commit/848a5da2a60cbd4c0684d6607ad9df6eeaa2dd7b)) - -# [1.2.0](https://github.com/fecommunity/reactpress/compare/v1.1.0...v1.2.0) (2024-11-23) - -### Bug Fixes - -- system notice info empty error ([4e41daa](https://github.com/fecommunity/reactpress/commit/4e41daa14dc96499e6d65ebc2648f87147fc248d)) -- update admin view article link ([41656a7](https://github.com/fecommunity/reactpress/commit/41656a740d301cda7ec54bb6ceaaa8e8cea7c222)) - -### Features - -- add category tag for article list ([f5068a1](https://github.com/fecommunity/reactpress/commit/f5068a17b4728a47330fbbbca0f236d12e299e40)) -- support system notification feature ([515e556](https://github.com/fecommunity/reactpress/commit/515e556d7b63192cbad4bda68d5ecf639cbaa96b)) - -# [1.1.0](https://github.com/fecommunity/reactpress/compare/v1.0.0...v1.1.0) (2024-11-02) - -# [1.0.0](https://github.com/fecommunity/reactpress/compare/a6b73a189090e0199cc6f803bfb498cdeb7868a5...v1.0.0) (2024-09-28) - -### Bug Fixes - -- add ignoreValidator for system creating user ([83408d2](https://github.com/fecommunity/reactpress/commit/83408d20383a6a57546dacfcd7751210c6c66d4c)) -- next build type defs ([ec428b3](https://github.com/fecommunity/reactpress/commit/ec428b3cfe6950a368e3aeb7bf9945cf81a1f481)) - -### Features - -- init easy-blog project ([a6b73a1](https://github.com/fecommunity/reactpress/commit/a6b73a189090e0199cc6f803bfb498cdeb7868a5)) diff --git a/docs/blog/tags.yml b/docs/blog/tags.yml index fd21754..e95b3a0 100644 --- a/docs/blog/tags.yml +++ b/docs/blog/tags.yml @@ -2,3 +2,8 @@ reactpress: label: ReactPress permalink: /reactpress description: ReactPress description + +release: + label: Release + permalink: /release + description: Version releases diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 9e06e38..1f68d16 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -1,12 +1,40 @@ import { themes as prismThemes } from 'prism-react-renderer'; -import type { Config } from '@docusaurus/types'; +import type { Config, Plugin } from '@docusaurus/types'; import type * as Preset from '@docusaurus/preset-classic'; // This runs in Node.js - Don't use client-side code here (browser APIs, JSX...) +/** + * Docusaurus 3.7 builds client/server in parallel; CleanWebpackPlugin may delete + * `build/__server/server.bundle.js` before SSG. Disable stale-asset cleanup on client. + * See https://github.com/facebook/docusaurus/pull/11037 (removed in newer versions). + */ +function preserveServerBundlePlugin(): Plugin { + return { + name: 'preserve-server-bundle-on-clean', + configureWebpack(config, isServer) { + if (isServer) { + return undefined; + } + for (const plugin of config.plugins ?? []) { + if ( + plugin && + typeof plugin === 'object' && + plugin.constructor.name === 'CleanWebpackPlugin' && + 'cleanStaleWebpackAssets' in plugin + ) { + (plugin as {cleanStaleWebpackAssets: boolean}).cleanStaleWebpackAssets = + false; + } + } + return undefined; + }, + }; +} + const config: Config = { title: 'ReactPress', - tagline: '一个基于Next.js的博客&CMS系统', + tagline: 'One package. Your CMS in about a minute.', favicon: 'img/favicon.ico', // Set the production url of your site here @@ -56,12 +84,13 @@ const config: Config = { onInlineAuthors: 'warn', onUntruncatedBlogPosts: 'warn', blogSidebarTitle: 'ReactPress', - blogTitle: '更新日志', + blogTitle: 'Changelog', }, theme: { customCss: [ require.resolve('./src/css/customTheme.scss'), require.resolve('./src/css/index.scss'), + require.resolve('./src/css/homepage.scss'), require.resolve('./src/css/showcase.scss'), require.resolve('./src/css/versions.scss'), ], @@ -69,7 +98,7 @@ const config: Config = { } satisfies Preset.Options, ], ], - plugins: ['docusaurus-plugin-sass'], + plugins: ['docusaurus-plugin-sass', preserveServerBundlePlugin], themeConfig: { // Replace with your project's social card image: 'img/docusaurus-social-card.jpg', @@ -84,10 +113,10 @@ const config: Config = { type: 'docSidebar', sidebarId: 'tutorialSidebar', position: 'left', - label: '教程', + label: 'Tutorial', }, - { to: '/blog', label: '博客', position: 'left' }, - { href: 'https://blog.gaoredu.com', label: 'Demo演示', position: 'left' }, + { to: '/blog', label: 'Blog', position: 'left' }, + { href: 'https://blog.gaoredu.com', label: 'Demo', position: 'left' }, { type: 'localeDropdown', position: 'right', @@ -106,13 +135,13 @@ const config: Config = { title: 'Docs', items: [ { - label: '教程', + label: 'Tutorial', to: '/docs/intro', }, ], }, { - title: '社区', + title: 'Community', items: [ { label: 'Stack Overflow', @@ -123,13 +152,13 @@ const config: Config = { href: 'https://github.com/fecommunity/reactpress/issues', }, { - label: '高热度网', + label: 'Demo site', href: 'https://blog.gaoredu.com/', }, ], }, { - title: '更多', + title: 'More', items: [ { label: 'Blog', diff --git a/docs/i18n/en/code.json b/docs/i18n/en/code.json index 2cce82c..46dddd9 100644 --- a/docs/i18n/en/code.json +++ b/docs/i18n/en/code.json @@ -310,58 +310,307 @@ "message": "Tags", "description": "The title of the tag list page" }, + "home.meta.description": { + "message": "ReactPress 3.0: one package, one minute to your own CMS. Zero-config install for blogs and content publishing." + }, "home.hero.subTitle": { - "message": "A free CMS and blog system using Next.js." + "message": "One package, one minute to your own CMS." }, "home.hero.intro": { "message": "Get Started" }, + "home.hero.whatsNew": { + "message": "What's New in 3.0" + }, "home.hero.try": { - "message": "Try a Demo" + "message": "Live Demo" + }, + "home.hero.cliLabel": { + "message": "Copy commands to get started" + }, + "home.cli.title": { + "message": "reactpress" + }, + "home.cli.copyAll": { + "message": "Copy" + }, + "home.cli.copied": { + "message": "Copied!" + }, + "home.cli.copyAll.aria": { + "message": "Copy quick start commands" + }, + "home.cli.copyLine.aria": { + "message": "Copy command: {line}" + }, + "home.cli.hint": { + "message": "Copy all commands and paste into your terminal to get started" + }, + "home.cli.running": { + "message": "Running" + }, + "home.cli.terminal.aria": { + "message": "ReactPress quick start command demo" + }, + "home.highlights.title": { + "message": "3.0 highlights" + }, + "复制命令,立即开始": { + "message": "Copy commands to get started" + }, + "reactpress — 快速开始": { + "message": "reactpress — quick start" + }, + "一键复制": { + "message": "Copy install command" + }, + "一键复制全部": { + "message": "Copy install command" + }, + "已复制": { + "message": "Copied!" + }, + "一键复制安装命令": { + "message": "Copy install command" + }, + "一键复制全部命令": { + "message": "Copy install command" + }, + "复制命令:{line}": { + "message": "Copy command: {line}" + }, + "在任意空目录粘贴运行,约 1 分钟完成零配置起站": { + "message": "Paste in any empty folder — zero-config site in about a minute" + }, + "全局安装后执行 reactpress init,约 1 分钟零配置起站": { + "message": "After global install, run reactpress init — zero-config site in about a minute" }, - "home.feature.intl.title": { - "message": "Componentization" + "3.0 核心亮点": { + "message": "3.0 highlights" }, - "home.feature.intl.description": { - "message": "Interactive language and visual style based on the latest version of AntDesign component library v5." + "复制": { + "message": "Copy" + }, + "home.highlight.zero.title": { + "message": "Zero-config setup" + }, + "home.highlight.zero.desc": { + "message": "Two commands set up your site and database — no manual environment configuration." + }, + "home.highlight.cli.title": { + "message": "One command line tool" + }, + "home.highlight.cli.desc": { + "message": "After a global install, initialize, develop, self-check, and view status from the terminal." + }, + "home.highlight.dx.title": { + "message": "Easy to get started" + }, + "home.highlight.dx.desc": { + "message": "Interactive guides, environment checks, and status tips — open your site and admin right after startup." + }, + "home.quickstart.title": { + "message": "Your CMS in one minute" + }, + "home.quickstart.desc": { + "message": "Run these commands in an empty folder to start the blog and admin with zero manual setup." + }, + "home.quickstart.link30": { + "message": "ReactPress 3.0 guide" + }, + "home.quickstart.linkMigrate": { + "message": "Migrate from 2.x" + }, + "home.quickstart.aria": { + "message": "Quick start commands" }, "home.call.feature": { - "message": "Features" + "message": "Platform capabilities" }, - "组件化": { - "message": "Componentization" + "home.call.subtitle": { + "message": "From personal blogs to team content sites — publish, manage, and collaborate out of the box." }, - "基于 AntDesign 组件库 v5 最新版的交互语言和视觉风格。": { - "message": "Interactive language and visual style based on the latest version of AntDesign component library v5." + "home.call.cta": { + "message": "Read full documentation" }, - "国际化": { + "home.feature.zero.title": { + "message": "Zero-config setup" + }, + "home.feature.zero.desc": { + "message": "Installer-style flow auto-configures your site and database — as familiar as setting up a popular blog platform." + }, + "home.feature.cli.title": { + "message": "All-in-one CLI" + }, + "home.feature.cli.desc": { + "message": "Environment checks, status output, interactive menus, and direct links right after startup." + }, + "home.feature.ui.title": { + "message": "Modern interface" + }, + "home.feature.ui.desc": { + "message": "Modern admin and public site with light/dark theme support." + }, + "home.feature.content.title": { + "message": "Content & authoring" + }, + "home.feature.content.desc": { + "message": "Built-in Markdown editor; articles, categories, tags, pages, comments, and media in one place." + }, + "home.feature.headless.title": { + "message": "Open integrations" + }, + "home.feature.headless.desc": { + "message": "Open APIs, access keys, and event callbacks for external systems and automation." + }, + "home.feature.i18n.title": { "message": "Internationalization" }, - "支持中英文切换,国际化配置管理能力。": { - "message": "Support switching between Chinese and English, with international configuration management capabilities." + "home.feature.i18n.desc": { + "message": "Chinese/English UI, comfortable on desktop, tablet, and mobile." }, - "黑白主题": { - "message": "Black and White Theme" + "零配置起站": { + "message": "Zero-config setup" }, - "支持亮色和暗黑模式主题自由切换。": { - "message": "Support free switching between bright and dark mode themes." + "两条命令自动完成安装与数据库准备,无需手工配置环境。": { + "message": "Two commands set up your site and database — no manual environment configuration." }, - "创作管理": { - "message": "Blog Management" + "init + dev 自动生成配置与 .env,默认 Docker MySQL,无需手写环境变量。": { + "message": "Two commands set up your site and database — no manual environment configuration." + }, + "一条命令搞定": { + "message": "One command line tool" + }, + "唯一 CLI 入口": { + "message": "One command line tool" + }, + "全局安装后,初始化、开发、自检与状态查看都在命令行里完成。": { + "message": "After a global install, initialize, develop, self-check, and view status from the terminal." + }, + "全局安装 @fecommunity/reactpress@3,init / dev / doctor / status 一条命令搞定。": { + "message": "After a global install, initialize, develop, self-check, and view status from the terminal." + }, + "上手更省心": { + "message": "Easy to get started" + }, + "可诊断的开发体验": { + "message": "Easy to get started" + }, + "交互式引导、环境自检与运行状态提示,启动成功即可打开前台与后台。": { + "message": "Interactive guides, environment checks, and status tips — open your site and admin right after startup." + }, + "交互式菜单、doctor 自检、status 状态与 dev 成功后的直达链接提示。": { + "message": "Interactive guides, environment checks, and status tips — open your site and admin right after startup." + }, + "一分钟拥有自己的 CMS": { + "message": "Your CMS in one minute" + }, + "在空目录执行以下命令,即可零配置启动博客与管理后台。": { + "message": "Run these commands in an empty folder to start the blog and admin with zero manual setup." + }, + "ReactPress 3.0 说明": { + "message": "ReactPress 3.0 guide" + }, + "从 2.x 迁移": { + "message": "Migrate from 2.x" + }, + "快速开始命令": { + "message": "Quick start commands" + }, + "平台能力一览": { + "message": "Platform capabilities" + }, + "从个人博客到团队内容站,发布、管理与协作,开箱即用。": { + "message": "From personal blogs to team content sites — publish, manage, and collaborate out of the box." + }, + "从个人博客到团队内容站,ReactPress 提供开箱即用的发布与治理能力。": { + "message": "From personal blogs to team content sites — publish, manage, and collaborate out of the box." + }, + "阅读完整文档": { + "message": "Read full documentation" + }, + "装一个包,一分钟拥有自己的 CMS。": { + "message": "One package, one minute to your own CMS." + }, + "装一个包,一分钟拥有自己的 CMS — 基于 React、Next.js 与 NestJS 的全栈发布平台。": { + "message": "One package, one minute to your own CMS." + }, + "快速开始": { + "message": "Get Started" + }, + "3.0 新特性": { + "message": "What's New in 3.0" + }, + "在线演示": { + "message": "Live Demo" + }, + "安装向导式流程,自动完成配置与数据库,像安装常用博客程序一样简单。": { + "message": "Installer-style flow auto-configures your site and database — as familiar as setting up a popular blog platform." + }, + "reactpress init 与 dev 自动生成配置、.env 与嵌入式 MySQL,WordPress 式安装向导。": { + "message": "Installer-style flow auto-configures your site and database — as familiar as setting up a popular blog platform." + }, + "命令行一站式": { + "message": "All-in-one CLI" + }, + "统一 CLI": { + "message": "All-in-one CLI" + }, + "环境自检、运行状态查看,交互式菜单与启动成功后的直达链接。": { + "message": "Environment checks, status output, interactive menus, and direct links right after startup." + }, + "doctor 环境自检、status 运行状态,交互式菜单与 dev 成功后的直达链接。": { + "message": "Environment checks, status output, interactive menus, and direct links right after startup." + }, + "现代界面": { + "message": "Modern interface" + }, + "组件化 UI": { + "message": "Modern interface" + }, + "现代化管理后台与前台,支持亮/暗主题切换。": { + "message": "Modern admin and public site with light/dark theme support." + }, + "基于 Ant Design 5 的现代化后台与前台,支持亮/暗主题切换。": { + "message": "Modern admin and public site with light/dark theme support." + }, + "创作与内容": { + "message": "Content & authoring" + }, + "内置 Markdown 编辑器,文章、分类、标签、页面、评论与媒体管理一应俱全。": { + "message": "Built-in Markdown editor; articles, categories, tags, pages, comments, and media in one place." + }, + "内置 Markdown 编辑器,文章、分类、标签、页面、评论与媒体(本地上传 / OSS)。": { + "message": "Built-in Markdown editor; articles, categories, tags, pages, comments, and media in one place." + }, + "开放集成": { + "message": "Open integrations" + }, + "Headless API": { + "message": "Open integrations" + }, + "开放接口、访问密钥与事件回调,便于对接外部系统与自动化流程。": { + "message": "Open APIs, access keys, and event callbacks for external systems and automation." + }, + "API Key、Webhook、健康检查与 toolkit SDK,便于对接外部系统与自动化。": { + "message": "Open APIs, access keys, and event callbacks for external systems and automation." + }, + "国际化": { + "message": "Internationalization" }, - "内置 MarkDown 编辑器,支持文章写文章、分类目录管理,标签管理。": { - "message": "Built in MarkDown editor, supporting article writing, category and table of contents management, and tag management." + "中英文界面切换,在电脑、平板与手机上均可舒适使用。": { + "message": "Chinese/English UI, comfortable on desktop, tablet, and mobile." }, - "内容管理": { - "message": "Content Management" + "ReactPress 3.0:装一个包、一分钟拥有自己的 CMS。零配置安装,开箱即用的博客与内容发布平台。": { + "message": "ReactPress 3.0: one package, one minute to your own CMS. Zero-config install for blogs and content publishing." }, - "支持自定义新页面、内容评论管理,完整的社区互动功能。": { - "message": "Support custom new pages, content comment management, and complete community interaction functions." + "ReactPress 3.0:装一个包、一分钟拥有自己的 CMS。基于 React、Next.js 与 NestJS 的全栈发布平台,零配置 init + dev。": { + "message": "ReactPress 3.0: one package, one minute to your own CMS. Zero-config install for blogs and content publishing." }, - "多端适配": { - "message": "Multi Terminal Adaptation" + "全局安装后,在空目录初始化,约 1 分钟即可零配置起站": { + "message": "After global install, initialize in an empty folder — your site is ready in about a minute" }, - "完美适配电脑、平板、移动端H5页面。": { - "message": "Perfectly compatible with H5 pages on computers, tablets, and mobile devices." + "全局安装后执行 reactpress init,约 1 分钟零配置起站": { + "message": "After global install, initialize in an empty folder — your site is ready in about a minute" } } diff --git a/docs/i18n/en/docusaurus-plugin-content-blog/changlog.md b/docs/i18n/en/docusaurus-plugin-content-blog/changlog.md deleted file mode 100644 index d72fe2a..0000000 --- a/docs/i18n/en/docusaurus-plugin-content-blog/changlog.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -slug: changelog -title: Changelog -authors: [fecommunity] -tags: [reactpress] ---- - - - -## [2.0.1](https://github.com/fecommunity/reactpress/compare/v2.0.0...v2.0.1) (2025-09-26) - - -### Bug Fixes - -* correct HttpClient filename case sensitivity ([7dd892a](https://github.com/fecommunity/reactpress/commit/7dd892a8d5b05a3ab24eaf73577848eb25b06450)) - - -### Features - -* add config for toolkit package ([0ed839d](https://github.com/fecommunity/reactpress/commit/0ed839d4667d671ea06b088c0bac5a2890680445)) - - - -# [2.0.0](https://github.com/fecommunity/reactpress/compare/v1.10.0...v2.0.0) (2025-09-21) - - -### Bug Fixes - -* server load issue ([a6f759b](https://github.com/fecommunity/reactpress/commit/a6f759b386e32727501b0eea3ea38f5a89dfe700)) -* type defs ([d6491d5](https://github.com/fecommunity/reactpress/commit/d6491d56f2ffdd19d5a47fda7273958cd4243fb3)) - - -### Features - -* add hello-world template ([7e2c948](https://github.com/fecommunity/reactpress/commit/7e2c9487ddc6023d7b382250b131fbe828013680)) -* add reactpress toolkit ([58f9312](https://github.com/fecommunity/reactpress/commit/58f9312644736aceb362e517fad8c3b3a83f275f)) -* add swagger v2 ui ([ef9fdc1](https://github.com/fecommunity/reactpress/commit/ef9fdc166955b4659c81fb559138ce38ef599cfe)) -* add twentytwentyfive theme ([715281f](https://github.com/fecommunity/reactpress/commit/715281fedcf8072348e4b8b6794891c7e67e1f99)) -* support npx install server ([e7f7b97](https://github.com/fecommunity/reactpress/commit/e7f7b970bb4dd8b845fcd8dde4048678a403557a)) -* support quick install ([96c1d0a](https://github.com/fecommunity/reactpress/commit/96c1d0a7cc1c72b7f6c489ba236ab6eb78472dee)) -* support quick install ([793bca7](https://github.com/fecommunity/reactpress/commit/793bca79f2bfa921f75bc1cc5f234207aa0ab40a)) - - - -# [1.10.0](https://github.com/fecommunity/reactpress/compare/v1.9.0...v1.10.0) (2025-08-03) - - -### Features - -* add type defs for config ([d8a6fed](https://github.com/fecommunity/reactpress/commit/d8a6fed7bc13f74be0916f80497590c7e737fb86)) - - - -# [1.9.0](https://github.com/fecommunity/reactpress/compare/v1.8.0...v1.9.0) (2025-05-21) - - - -# [1.8.0](https://github.com/fecommunity/reactpress/compare/v1.7.0...v1.8.0) (2025-03-22) - - -### Features - -* upgrade next version ([64cac4d](https://github.com/fecommunity/reactpress/commit/64cac4dcb9268a6bbb14fbbfe6995406638f7508)) - - - -# [1.7.0](https://github.com/fecommunity/reactpress/compare/v1.6.0...v1.7.0) (2025-03-07) - - -### Features - -* add reactpress docs ([002972b](https://github.com/fecommunity/reactpress/commit/002972b6194d13917d60de8dae019445739760cc)) -* release reactpress v1.6.0 ([9d75118](https://github.com/fecommunity/reactpress/commit/9d75118b7e19d85ab95b3e6f227f1b95cd95acb8)) - - - -# [1.6.0](https://github.com/fecommunity/reactpress/compare/v1.5.0...v1.6.0) (2024-12-21) - - -### Features - -* knowledge page perfect ([#16](https://github.com/fecommunity/reactpress/issues/16)) ([b9ae27d](https://github.com/fecommunity/reactpress/commit/b9ae27d087b3b451668b3c0acb40359b20a089e2)) - - - -# [1.5.0](https://github.com/fecommunity/reactpress/compare/v1.4.0...v1.5.0) (2024-12-21) - - -### Bug Fixes - -* adapt small screen content display issue ([84895c8](https://github.com/fecommunity/reactpress/commit/84895c8341d34285068806d5908b2af358719f80)) -* add key for tex loop item ([e8a2e36](https://github.com/fecommunity/reactpress/commit/e8a2e36f13d5031d845c3c3055374e5ff0446bc2)) -* nav config build error ([66acf46](https://github.com/fecommunity/reactpress/commit/66acf46485f902aa0647b4886beb0701372b95c2)) -* nav query id undefined ([2d0bbd7](https://github.com/fecommunity/reactpress/commit/2d0bbd78f4d73579039b3a9bf7c846fe2d976073)) -* route level default value ([4aae64e](https://github.com/fecommunity/reactpress/commit/4aae64e86346052cb51e63e7c149853a6d8d8ae6)) - - -### Features - -* add animation tags cloud ([68b1a5b](https://github.com/fecommunity/reactpress/commit/68b1a5b2fe73fafd7a9b282ed77f836aa7a76bf6)) -* add global config setting page ([c825425](https://github.com/fecommunity/reactpress/commit/c82542540b20f423b3b3f3ba04ea5e48e2523f5c)) -* add nav page ([c6703c6](https://github.com/fecommunity/reactpress/commit/c6703c6e3dea33a8cc226f1c02d9b5cac0ef827e)) -* optimized page interaction experience ([b0ac19a](https://github.com/fecommunity/reactpress/commit/b0ac19ab31b6bcf0d8916aeb373e6fa7288303aa)) - - - -# [1.4.0](https://github.com/fecommunity/reactpress/compare/v1.3.0...v1.4.0) (2024-12-08) - - - -# [1.3.0](https://github.com/fecommunity/reactpress/compare/v1.2.0...v1.3.0) (2024-12-01) - - -### Features - -* add system systemSubTitle setting config ([136f012](https://github.com/fecommunity/reactpress/commit/136f01288cb9714092a2aa0dc01421817bb26b7d)) -* optimized the css style loading experience for homepage ([0e7761f](https://github.com/fecommunity/reactpress/commit/0e7761fd28c1cac099ac15ddaa644a904aca8da4)) -* react helmet seo perfect ([848a5da](https://github.com/fecommunity/reactpress/commit/848a5da2a60cbd4c0684d6607ad9df6eeaa2dd7b)) - - - -# [1.2.0](https://github.com/fecommunity/reactpress/compare/v1.1.0...v1.2.0) (2024-11-23) - - -### Bug Fixes - -* system notice info empty error ([4e41daa](https://github.com/fecommunity/reactpress/commit/4e41daa14dc96499e6d65ebc2648f87147fc248d)) -* update admin view article link ([41656a7](https://github.com/fecommunity/reactpress/commit/41656a740d301cda7ec54bb6ceaaa8e8cea7c222)) - - -### Features - -* add category tag for article list ([f5068a1](https://github.com/fecommunity/reactpress/commit/f5068a17b4728a47330fbbbca0f236d12e299e40)) -* support system notification feature ([515e556](https://github.com/fecommunity/reactpress/commit/515e556d7b63192cbad4bda68d5ecf639cbaa96b)) - - - -# [1.1.0](https://github.com/fecommunity/reactpress/compare/v1.0.0...v1.1.0) (2024-11-02) - - - -# [1.0.0](https://github.com/fecommunity/reactpress/compare/a6b73a189090e0199cc6f803bfb498cdeb7868a5...v1.0.0) (2024-09-28) - - -### Bug Fixes - -* add ignoreValidator for system creating user ([83408d2](https://github.com/fecommunity/reactpress/commit/83408d20383a6a57546dacfcd7751210c6c66d4c)) -* next build type defs ([ec428b3](https://github.com/fecommunity/reactpress/commit/ec428b3cfe6950a368e3aeb7bf9945cf81a1f481)) - - -### Features - -* init easy-blog project ([a6b73a1](https://github.com/fecommunity/reactpress/commit/a6b73a189090e0199cc6f803bfb498cdeb7868a5)) - - - diff --git a/docs/i18n/en/docusaurus-plugin-content-blog/tags.yml b/docs/i18n/en/docusaurus-plugin-content-blog/tags.yml index fd21754..e95b3a0 100644 --- a/docs/i18n/en/docusaurus-plugin-content-blog/tags.yml +++ b/docs/i18n/en/docusaurus-plugin-content-blog/tags.yml @@ -2,3 +2,8 @@ reactpress: label: ReactPress permalink: /reactpress description: ReactPress description + +release: + label: Release + permalink: /release + description: Version releases diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/intro.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/intro.md index bdb136f..23b38b8 100644 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current/intro.md +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/intro.md @@ -6,7 +6,9 @@ title: Introduction ## Introduction -`ReactPress` is an open-source publishing platform developed using the React. Users can set up their own blogs and websites on servers that support React and MySQL databases. `ReactPress` can also be used as a content management system (CMS). +`ReactPress` is an open-source publishing platform built with React. Users can run blogs and websites on servers with React and MySQL, or use it as a CMS. + +**ReactPress 3.0** ships as a single global package: `npm i -g @fecommunity/reactpress@3`, then `reactpress init` and `reactpress dev` in any empty folder. See [ReactPress 3.0 Platform](./tutorial-extras/reactpress-3-0.md). ## 🆚 Comparison diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/_category_.json b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/_category_.json index fe860c8..3910d0a 100644 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/_category_.json +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/_category_.json @@ -1,8 +1,8 @@ { - "label": "基础教程", + "label": "Basics", "position": 2, "link": { "type": "generated-index", - "description": "ReactPress基础教程" + "description": "ReactPress basics" } } diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/start.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/start.md index 62605cb..b0d2f1c 100644 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/start.md +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-basics/start.md @@ -3,47 +3,46 @@ sidebar_position: 1 title: Development --- +## Two ways to develop + +| Scenario | Approach | Prerequisites | +|----------|----------|-----------------| +| **Try / new site (recommended)** | Global `reactpress` | Node ≥ 18, Docker | +| **Contribute to monorepo** | `pnpm dev` in repo | Node ≥ 18, Docker, pnpm | + +--- + +## Option 1: Global CLI (3.0 recommended) -## ⌨️ Local Development -### Environmental preparation ```bash -$ git clone --depth=1 https://github.com/fecommunity/reactpress.git -$ cd reactpress -$ npm i -g pnpm -$ pnpm i +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev ``` -### File Structure - -The code structure of the project is as follows: -```shell -├─ client // Next.js based frontend client -├─ server // NestJS based backend API service -├─ toolkit // TypeScript API client toolkit -├─ templates // Template files -├─ scripts // Build scripts -├─ docs // Documentation -└─ package.json -``` +Open http://localhost:3001 (admin at `/admin`, API health at `/api/health`). -ReactPress 2.0 adopts a monorepo structure, separating the frontend, backend, and toolkit into independent packages that can be developed and deployed separately. +```bash +reactpress # Interactive menu +reactpress doctor +reactpress status +``` -### Configuration file +See [ReactPress 3.0 Platform](../tutorial-extras/reactpress-3-0.md). -After the project starts, the `. env ` configuration file in the root directory will be loaded. Please ensure that the MySQL database service is consistent with the following configuration, and create the ` reactpress ` database in advance +--- -```js -DB_SOST=127.0.0.1//Database address -DB-PORT=3306//Port -DBVNet=reactpress//username -DB-PASSWD=reactpress//Password -DBDATABASE=React Press//Database -``` -### Start up +## Option 2: Monorepo -After the environment is ready, execute the startup command: ```bash -$ pnpm run dev +git clone --depth=1 https://github.com/fecommunity/reactpress.git +cd reactpress +npm i -g pnpm +pnpm install +pnpm run dev ``` -Open the browser to access http://127.0.0.1:3001 \ No newline at end of file +Equivalent to global `reactpress dev`. Optional: `pnpm run init`, `pnpm run dev:api`, `pnpm run dev:client`. + +Configuration: `.reactpress/config.json` (source of truth) and synced `.env`. See [configuration](../tutorial-extras/config-intro.md). diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/_category_.json b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/_category_.json index d7a15fa..e06784a 100644 --- a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/_category_.json +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/_category_.json @@ -1,7 +1,8 @@ { - "label": "进阶教程", + "label": "Extras", "position": 3, "link": { - "type": "generated-index" + "type": "generated-index", + "description": "Advanced topics and package guides" } } diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/docker-deployment.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/docker-deployment.md new file mode 100644 index 0000000..1f53dc0 --- /dev/null +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/docker-deployment.md @@ -0,0 +1,127 @@ +--- +sidebar_position: 5 +id: docker-deployment +title: Docker deployment guide +--- + +# Docker deployment guide + +ReactPress supports containerized deployment with Docker so you can run the same stack consistently across environments. + +## Docker architecture + +Production Docker setup uses multiple services: + +- **db**: MySQL 5.7 +- **server**: NestJS API +- **client**: Next.js frontend +- **nginx**: reverse proxy + +## Directory layout + +``` +reactpress/ +├── docker-compose.prod.yml +├── client/Dockerfile +├── server/Dockerfile +└── nginx.conf +``` + +## Quick start + +### 1. Prerequisites + +```bash +docker --version +docker-compose --version +``` + +### 2. Clone the project + +```bash +git clone https://github.com/fecommunity/reactpress.git +cd reactpress +``` + +### 3. Environment variables + +Create `.env` in the project root: + +```env +DB_HOST=db +DB_PORT=3306 +DB_USER=reactpress +DB_PASSWD=reactpress +DB_DATABASE=reactpress + +CLIENT_SITE_URL=http://localhost:8080 +SERVER_SITE_URL=http://localhost:8080 +SERVER_API_URL=http://nginx/api +``` + +### 4. Start services + +```bash +docker-compose -f docker-compose.prod.yml up -d +docker-compose -f docker-compose.prod.yml ps +docker-compose -f docker-compose.prod.yml logs -f +``` + +Open `http://localhost:8080`. + +## Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| DB_HOST | db | Database host | +| DB_PORT | 3306 | Database port | +| DB_USER | reactpress | Database user | +| DB_PASSWD | reactpress | Database password | +| DB_DATABASE | reactpress | Database name | +| CLIENT_SITE_URL | http://localhost:8080 | Public site URL | +| SERVER_SITE_URL | http://localhost:8080 | Server URL | +| SERVER_API_URL | http://nginx/api | API base URL | + +### Port mapping + +| Service | Container | Host | Notes | +|---------|-----------|------|-------| +| nginx | 80 | 8080 | Entry point | +| server | 3002 | 3002 | API | +| client | 3001 | 3001 | Frontend | + +## Development with Docker + +Use `docker-compose.dev.yml` for hot reload and file sync. See the Chinese tutorial in the repo for a full dev compose example, or run: + +```bash +reactpress docker start +``` + +## Common operations + +```bash +# Production lifecycle +docker-compose -f docker-compose.prod.yml up -d +docker-compose -f docker-compose.prod.yml down +docker-compose -f docker-compose.prod.yml restart +docker-compose -f docker-compose.prod.yml ps +docker-compose -f docker-compose.prod.yml logs -f + +# Database shell +docker exec -it reactpress_db mysql -u reactpress -p reactpress +``` + +## Troubleshooting + +1. **Port conflict** — change host ports in compose or stop the blocking process. +2. **Database connection refused** — ensure the `db` service is healthy and `.env` matches compose. +3. **Permission errors** — check volume mounts and container user permissions. + +## Security + +- Change default database passwords in production. +- Avoid exposing unnecessary ports. +- Keep base images and dependencies updated. + +For advanced tuning (resource limits, CI/CD), see the full Chinese guide in `docs/tutorial/tutorial-extras/docker-deployment.md` or open a discussion on GitHub. diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/migration-2-to-3.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/migration-2-to-3.md new file mode 100644 index 0000000..74e4a1b --- /dev/null +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/migration-2-to-3.md @@ -0,0 +1,32 @@ +--- +sidebar_position: 6 +title: 2.x → 3.0 Migration +--- + +# ReactPress 2.x → 3.0 Migration + +3.0 uses **`@fecommunity/reactpress`** as the only recommended entry: global command `reactpress`, bundled API, zero-config `init` + `dev`. + +See [ReactPress 3.0 Platform](./reactpress-3-0.md) for the full overview. + +## Command mapping + +| 2.x | 3.0 | +|-----|-----| +| `npm i -g @fecommunity/reactpress-cli` | `npm i -g @fecommunity/reactpress@3` | +| `reactpress-cli init` | `reactpress init` | +| `reactpress-cli start` | `reactpress dev` | +| `npx @fecommunity/reactpress-server` | `reactpress dev --api-only` | +| Multiple packages | **One package** `@fecommunity/reactpress` | +| — | `reactpress doctor` / `reactpress status` | + +## Upgrade steps + +```bash +mysqldump -u root -p reactpress > backup.sql +npm uninstall -g @fecommunity/reactpress-cli 2>/dev/null || true +npm i -g @fecommunity/reactpress@3 +reactpress doctor +reactpress dev +curl http://localhost:3002/api/health +``` diff --git a/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/reactpress-3-0.md b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/reactpress-3-0.md new file mode 100644 index 0000000..12b193d --- /dev/null +++ b/docs/i18n/en/docusaurus-plugin-content-docs/current/tutorial-extras/reactpress-3-0.md @@ -0,0 +1,64 @@ +--- +sidebar_position: 4 +title: ReactPress 3.0 Platform +--- + +# ReactPress 3.0 Platform + +> **One package. One command. Your CMS in about a minute.** + +Version 3.0 (codename **Platform**) focuses on **zero config**, a **single entry point**, and a **great developer experience**. The stack remains React 17 + Next.js 12 + NestJS 6 (Next 14 / React 18 is planned for **3.1**). + +## Three pillars + +| Pillar | What you feel | 3.0 delivery | +|--------|----------------|--------------| +| **Zero config** | No hand-written `.env`, no multi-package install | `init` + `dev`, embedded Docker MySQL by default | +| **Single entry** | One package name, one command | `npm i -g @fecommunity/reactpress@3` → `reactpress` | +| **Great DX** | Less docs, visible status | Interactive menu, `doctor`, `status`, dev success URLs | + +## Quick start (about one minute) + +After a **global install**, in an empty directory: + +```bash +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev +``` + +| URL | Purpose | +|-----|---------| +| http://localhost:3001 | Site | +| http://localhost:3001/admin | Admin | +| http://localhost:3002/api/health | Health check | + +Run `reactpress` with no args for the interactive menu. + +## Commands + +| Command | Description | +|---------|-------------| +| `reactpress` | Interactive menu | +| `reactpress init` | Zero-config setup | +| `reactpress dev` | Full stack (default) | +| `reactpress dev --api-only` | API only (Headless) | +| `reactpress dev --client-only` | Client only | +| `reactpress doctor` | Environment diagnostics | +| `reactpress status` | Status summary | +| `reactpress start` / `stop` / `restart` | Production lifecycle | + +## Packages (3.0) + +| Package | Role | +|---------|------| +| **`@fecommunity/reactpress`** | **Main package** — CLI + bundled API | +| `@fecommunity/reactpress-client` | Advanced: client-only deploy | +| `@fecommunity/reactpress-toolkit` | Headless TS SDK | +| `@fecommunity/reactpress-cli` | **Deprecated** alias | +| `@fecommunity/reactpress-server` | **Deprecated** — use bundled API | + +## Upgrade from 2.x + +See [2.x → 3.0 migration](./migration-2-to-3.md). diff --git a/docs/i18n/en/docusaurus-theme-classic/footer.json b/docs/i18n/en/docusaurus-theme-classic/footer.json index 9ea62e7..b2b7c7f 100644 --- a/docs/i18n/en/docusaurus-theme-classic/footer.json +++ b/docs/i18n/en/docusaurus-theme-classic/footer.json @@ -3,40 +3,40 @@ "message": "Docs", "description": "The title of the footer links column with title=Docs in the footer" }, - "link.title.社区": { + "link.title.Community": { "message": "Community", - "description": "The title of the footer links column with title=社区 in the footer" + "description": "The title of the footer links column with title=Community in the footer" }, - "link.title.更多": { + "link.title.More": { "message": "More", - "description": "The title of the footer links column with title=更多 in the footer" + "description": "The title of the footer links column with title=More in the footer" }, - "link.item.label.教程": { + "link.item.label.Tutorial": { "message": "Tutorial", - "description": "The label of footer link with label=教程 linking to /docs/intro" + "description": "The label of footer link with label=Tutorial linking to /docs/intro" }, "link.item.label.Stack Overflow": { "message": "Stack Overflow", - "description": "The label of footer link with label=Stack Overflow linking to https://stackoverflow.com/questions/tagged/reactpress" + "description": "The label of footer link with label=Stack Overflow" }, "link.item.label.Github": { "message": "Github", - "description": "The label of footer link with label=Github linking to https://github.com/fecommunity/reactpress/issues" + "description": "The label of footer link with label=Github" }, - "link.item.label.高热度网": { - "message": "高热度网", - "description": "The label of footer link with label=高热度网 linking to https://blog.gaoredu.com/" + "link.item.label.Demo site": { + "message": "Demo site", + "description": "The label of footer link with label=Demo site" }, "link.item.label.Blog": { "message": "Blog", - "description": "The label of footer link with label=Blog linking to /blog" + "description": "The label of footer link with label=Blog" }, "link.item.label.GitHub": { "message": "GitHub", - "description": "The label of footer link with label=GitHub linking to https://github.com/fecommunity/reactpress" + "description": "The label of footer link with label=GitHub" }, "copyright": { - "message": "Copyright © 2025 ReactPress.", + "message": "Copyright © 2026 ReactPress.", "description": "The footer copyright" } } diff --git a/docs/i18n/en/docusaurus-theme-classic/navbar.json b/docs/i18n/en/docusaurus-theme-classic/navbar.json index 0a750e6..491a929 100644 --- a/docs/i18n/en/docusaurus-theme-classic/navbar.json +++ b/docs/i18n/en/docusaurus-theme-classic/navbar.json @@ -7,17 +7,17 @@ "message": "ReactPress Logo", "description": "The alt text of navbar logo" }, - "item.label.教程": { + "item.label.Tutorial": { "message": "Tutorial", - "description": "Navbar item with label 教程" + "description": "Navbar item with label Tutorial" }, - "item.label.博客": { + "item.label.Blog": { "message": "Blog", - "description": "Navbar item with label 博客" + "description": "Navbar item with label Blog" }, - "item.label.Demo演示": { + "item.label.Demo": { "message": "Demo", - "description": "Navbar item with label Demo演示" + "description": "Navbar item with label Demo" }, "item.label.GitHub": { "message": "GitHub", diff --git a/docs/i18n/zh/code.json b/docs/i18n/zh/code.json new file mode 100644 index 0000000..fe44ff1 --- /dev/null +++ b/docs/i18n/zh/code.json @@ -0,0 +1,104 @@ +{ + "theme.tagline": { + "message": "装一个包,一分钟拥有自己的 CMS" + }, + "home.hero.subTitle": { + "message": "装一个包,一分钟拥有自己的 CMS。" + }, + "home.hero.intro": { + "message": "快速开始" + }, + "home.hero.whatsNew": { + "message": "3.0 新特性" + }, + "home.hero.try": { + "message": "在线演示" + }, + "home.cli.running": { + "message": "运行中" + }, + "home.cli.copied": { + "message": "已复制" + }, + "home.cli.copyAll": { + "message": "复制" + }, + "home.cli.copyAll.aria": { + "message": "复制快速开始命令" + }, + "home.cli.terminal.aria": { + "message": "ReactPress 快速开始命令演示" + }, + "home.highlights.title": { + "message": "3.0 核心亮点" + }, + "home.highlight.zero.title": { + "message": "零配置起站" + }, + "home.highlight.zero.desc": { + "message": "两条命令自动完成安装与数据库准备,无需手工配置环境。" + }, + "home.highlight.cli.title": { + "message": "一条命令搞定" + }, + "home.highlight.cli.desc": { + "message": "全局安装后,初始化、开发、自检与状态查看都在命令行里完成。" + }, + "home.highlight.dx.title": { + "message": "上手更省心" + }, + "home.highlight.dx.desc": { + "message": "交互式引导、环境自检与运行状态提示,启动成功即可打开前台与后台。" + }, + "home.call.feature": { + "message": "平台能力一览" + }, + "home.call.subtitle": { + "message": "从个人博客到团队内容站,发布、管理与协作,开箱即用。" + }, + "home.call.cta": { + "message": "阅读完整文档" + }, + "home.feature.zero.title": { + "message": "零配置起站" + }, + "home.feature.zero.desc": { + "message": "安装向导式流程,自动完成配置与数据库,像安装常用博客程序一样简单。" + }, + "home.feature.cli.title": { + "message": "命令行一站式" + }, + "home.feature.cli.desc": { + "message": "环境自检、运行状态查看,交互式菜单与启动成功后的直达链接。" + }, + "home.feature.ui.title": { + "message": "现代界面" + }, + "home.feature.ui.desc": { + "message": "现代化管理后台与前台,支持亮/暗主题切换。" + }, + "home.feature.content.title": { + "message": "创作与内容" + }, + "home.feature.content.desc": { + "message": "内置 Markdown 编辑器,文章、分类、标签、页面、评论与媒体管理一应俱全。" + }, + "home.feature.headless.title": { + "message": "开放集成" + }, + "home.feature.headless.desc": { + "message": "开放接口、访问密钥与事件回调,便于对接外部系统与自动化流程。" + }, + "home.feature.i18n.title": { + "message": "国际化" + }, + "home.feature.i18n.desc": { + "message": "中英文界面切换,在电脑、平板与手机上均可舒适使用。" + }, + "home.meta.description": { + "message": "ReactPress 3.0:装一个包、一分钟拥有自己的 CMS。零配置安装,开箱即用的博客与内容发布平台。" + }, + "home.cli.hint": { + "message": "复制全部命令,粘贴到终端按序执行即可起站" + } +} diff --git a/docs/i18n/zh/docusaurus-plugin-content-blog/changelog.md b/docs/i18n/zh/docusaurus-plugin-content-blog/changelog.md new file mode 100644 index 0000000..4aa6c24 --- /dev/null +++ b/docs/i18n/zh/docusaurus-plugin-content-blog/changelog.md @@ -0,0 +1,48 @@ +--- +slug: changelog +title: 更新日志 +date: 2026-05-17 +authors: [fecommunity] +tags: [reactpress, release] +--- + + + +## [3.0.0](https://github.com/fecommunity/reactpress/compare/v2.0.2...v3.0.0) (2026-05-17) + +**ReactPress 3.0 平台版** — 装一个包,敲一条命令,一分钟拥有自己的 CMS。 + +### 三大重点 + +- **零配置**:`reactpress init` + `reactpress dev`,自动生成 `.reactpress/config.json`、`.env` 与嵌入式 Docker MySQL +- **唯一入口**:`npm i -g @fecommunity/reactpress@3`,命令统一为 `reactpress` +- **极致 DX**:交互菜单、`reactpress doctor`、`reactpress status`、dev 成功后的链接提示 + +### 新特性 + +- **CLI**:主包 `@fecommunity/reactpress`;内置 API;`reactpress-cli` bin deprecated +- **Headless**:`GET /api/health`;API Key;Webhook(HMAC 签名) +- **内容**:定时发布;文章修订历史与回滚 +- **运维**:生产 compose 示例;`reactpress db backup` + +### Breaking Changes + +- `@fecommunity/reactpress-cli` → `@fecommunity/reactpress@3` +- `@fecommunity/reactpress-server` deprecated +- 迁移指南:[2.x → 3.0](/docs/tutorial-extras/migration-2-to-3) + +--- + +## [1.9.0](https://github.com/fecommunity/reactpress/compare/v1.8.0...v1.9.0) (2025-05-21) + +## [1.8.0](https://github.com/fecommunity/reactpress/compare/v1.7.0...v1.8.0) (2025-03-22) + +### Features + +- upgrade next version ([64cac4d](https://github.com/fecommunity/reactpress/commit/64cac4dcb9268a6bbb14fbbfe6995406638f7508)) + +## [1.0.0](https://github.com/fecommunity/reactpress/compare/a6b73a189090e0199cc6f803bfb498cdeb7868a5...v1.0.0) (2024-09-28) + +### Features + +- init easy-blog project ([a6b73a1](https://github.com/fecommunity/reactpress/commit/a6b73a189090e0199cc6f803bfb498cdeb7868a5)) diff --git a/docs/i18n/zh/docusaurus-plugin-content-blog/options.json b/docs/i18n/zh/docusaurus-plugin-content-blog/options.json new file mode 100644 index 0000000..3bdcd52 --- /dev/null +++ b/docs/i18n/zh/docusaurus-plugin-content-blog/options.json @@ -0,0 +1,14 @@ +{ + "title": { + "message": "更新日志", + "description": "The title for the blog used in SEO" + }, + "description": { + "message": "ReactPress 版本更新记录", + "description": "The description for the blog used in SEO" + }, + "sidebar.title": { + "message": "ReactPress", + "description": "The label for the left sidebar" + } +} diff --git a/docs/i18n/zh/docusaurus-theme-classic/footer.json b/docs/i18n/zh/docusaurus-theme-classic/footer.json new file mode 100644 index 0000000..a4ddcc1 --- /dev/null +++ b/docs/i18n/zh/docusaurus-theme-classic/footer.json @@ -0,0 +1,42 @@ +{ + "link.title.Docs": { + "message": "文档", + "description": "The title of the footer links column with title=Docs in the footer" + }, + "link.title.Community": { + "message": "社区", + "description": "The title of the footer links column with title=Community in the footer" + }, + "link.title.More": { + "message": "更多", + "description": "The title of the footer links column with title=More in the footer" + }, + "link.item.label.Tutorial": { + "message": "教程", + "description": "The label of footer link with label=Tutorial linking to /docs/intro" + }, + "link.item.label.Stack Overflow": { + "message": "Stack Overflow", + "description": "The label of footer link with label=Stack Overflow" + }, + "link.item.label.Github": { + "message": "Github", + "description": "The label of footer link with label=Github" + }, + "link.item.label.Demo site": { + "message": "高热度网", + "description": "The label of footer link with label=Demo site" + }, + "link.item.label.Blog": { + "message": "博客", + "description": "The label of footer link with label=Blog" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub" + }, + "copyright": { + "message": "Copyright © 2026 ReactPress.", + "description": "The footer copyright" + } +} diff --git a/docs/i18n/zh/docusaurus-theme-classic/navbar.json b/docs/i18n/zh/docusaurus-theme-classic/navbar.json new file mode 100644 index 0000000..291e45f --- /dev/null +++ b/docs/i18n/zh/docusaurus-theme-classic/navbar.json @@ -0,0 +1,26 @@ +{ + "title": { + "message": "ReactPress", + "description": "The title in the navbar" + }, + "logo.alt": { + "message": "ReactPress 标志", + "description": "The alt text of navbar logo" + }, + "item.label.Tutorial": { + "message": "教程", + "description": "Navbar item with label Tutorial" + }, + "item.label.Blog": { + "message": "博客", + "description": "Navbar item with label Blog" + }, + "item.label.Demo": { + "message": "Demo 演示", + "description": "Navbar item with label Demo" + }, + "item.label.GitHub": { + "message": "GitHub", + "description": "Navbar item with label GitHub" + } +} diff --git a/docs/migration-2-to-3.md b/docs/migration-2-to-3.md new file mode 100644 index 0000000..f8e353d --- /dev/null +++ b/docs/migration-2-to-3.md @@ -0,0 +1,68 @@ +# ReactPress 2.x → 3.0 迁移指南 + +> 站点文档副本:[docs/tutorial/tutorial-extras/migration-2-to-3.md](./docs/tutorial/tutorial-extras/migration-2-to-3.md) · [3.0 平台版说明](./docs/tutorial/tutorial-extras/reactpress-3-0.md) + +## 概述 + +3.0「平台版」以 **`@fecommunity/reactpress`** 为唯一推荐入口:全局命令 `reactpress`,内置 API,零配置 `init` + `dev`。独立安装的 `@fecommunity/reactpress-server`、以 `reactpress-cli` 为主路径的用法均已 deprecated。 + +## 命令对照 + +| 2.x | 3.0 | +|-----|-----| +| `npm i -g @fecommunity/reactpress-cli` | `npm i -g @fecommunity/reactpress@3` | +| `reactpress-cli init` | `reactpress init` | +| `reactpress-cli start` | `reactpress dev` 或 `reactpress server start` | +| `npx @fecommunity/reactpress-server` | `reactpress dev --api-only` 或 `reactpress server start` | +| `npx @fecommunity/reactpress-client` | `reactpress dev`(全栈)或 `reactpress dev --client-only` | +| `pnpm dev:server` | `pnpm dev:api` 或 `reactpress dev --api-only` | +| 多包分别安装 | **一个包** `@fecommunity/reactpress` | +| 无环境诊断 | `reactpress doctor` / `reactpress status` | + +> `reactpress-cli` 命令在 3.0 仍可用但会打印 deprecated 警告,3.1 移除。请统一改用 `reactpress`。 + +## 环境变量与配置 + +- 项目元数据以 **`.reactpress/config.json`** 为准;`.env` 由 CLI 同步生成,一般无需手改。 +- `REACTPRESS_ORIGINAL_CWD` 仅由 CLI 入口设置,请勿在业务代码中手动覆盖。 + +## Breaking Changes + +1. **主包名** — `@fecommunity/reactpress-cli` → **`@fecommunity/reactpress@3`**,bin 为 `reactpress`。 +2. **`@fecommunity/reactpress-server` deprecated** — 使用 CLI 内置 API;该包 3.0 保留最后一次兼容发布,3.1 起不再发布。 +3. **Monorepo 根包 `reactpress` 为 private** — 对外只安装 `@fecommunity/reactpress`;本仓贡献者仍可用 `pnpm dev`。 +4. **Swagger / OpenAPI** — 与 toolkit `@3.0.0` 对齐,请升级 SDK。 +5. **新增数据库表** — `api_keys`、`webhooks`、`article_revisions`;`articles.scheduled_publish_at`;首次启动由 TypeORM `synchronize` 创建(生产请先备份)。 + +## Headless 新能力 + +- **健康检查**: `GET /api/health` +- **API Key**: 管理端创建 → `X-API-Key` → `GET /api/article/headless/list` +- **Webhook**: `article.published`、`comment.created`;`X-ReactPress-Signature: sha256=...` + +## 推荐升级步骤 + +```bash +# 1. 备份数据库 +mysqldump -u root -p reactpress > backup.sql + +# 2. 换装主包 +npm uninstall -g @fecommunity/reactpress-cli 2>/dev/null || true +npm i -g @fecommunity/reactpress@3 + +# 3. 同步配置(先备份 .reactpress/) +reactpress init --force # 谨慎:会覆盖 config + +# 4. 诊断 +reactpress doctor + +# 5. 启动验证 +reactpress dev +curl http://localhost:3002/api/health +``` + +## 获取帮助 + +- [ReactPress 3.0 平台版(文档站)](./docs/tutorial/tutorial-extras/reactpress-3-0.md) +- [3.0 发布方案(仓库内)](../3.0.md) +- [GitHub Issues](https://github.com/fecommunity/reactpress/issues) diff --git a/docs/src/components/CliCommandBlock/index.tsx b/docs/src/components/CliCommandBlock/index.tsx new file mode 100644 index 0000000..15904f5 --- /dev/null +++ b/docs/src/components/CliCommandBlock/index.tsx @@ -0,0 +1,226 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import clsx from 'clsx'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Translate, { translate } from '@docusaurus/Translate'; +import { + QUICK_START_COMMANDS, + QUICK_START_COPY_COMMAND, +} from '@site/src/constants/quickStartCommands'; +import { useCliTypewriter } from './useCliTypewriter'; +import styles from './styles.module.css'; + +type Variant = 'hero' | 'section'; + +type Props = { + variant?: Variant; + className?: string; + showHint?: boolean; + /** 首页 hero 默认开启打字机动画 */ + animate?: boolean; + /** 展示的命令行;默认完整快速开始流程 */ + commands?: readonly string[]; + /** 复制到剪贴板的内容;默认与展示命令一致(多行脚本) */ + copyCommand?: string; +}; + +async function copyText(text: string): Promise { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + try { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.left = '-9999px'; + document.body.appendChild(ta); + ta.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(ta); + return ok; + } catch { + return false; + } + } +} + +function CopyIcon() { + return ( + + + + + ); +} + +function CheckIcon() { + return ( + + + + ); +} + +function TerminalCursor() { + return ; +} + +export default function CliCommandBlock({ + variant = 'hero', + className, + showHint = variant !== 'hero', + animate = variant === 'hero', + commands = QUICK_START_COMMANDS, + copyCommand = QUICK_START_COPY_COMMAND, +}: Props) { + const { i18n } = useDocusaurusContext(); + const locale = i18n.currentLocale === 'zh' ? 'zh' : 'en'; + const [copied, setCopied] = useState(false); + const lines = + commands.length > 0 ? commands : [...QUICK_START_COMMANDS]; + + const { history, activeInput, isTyping, animate: isAnimating } = + useCliTypewriter({ + enabled: animate, + locale, + commands: lines, + }); + + const scrollRef = useRef(null); + + useEffect(() => { + const el = scrollRef.current; + if (isAnimating && el) { + el.scrollTop = el.scrollHeight; + } + }, [history, activeInput, isAnimating]); + + const handleCopy = useCallback(async () => { + const ok = await copyText(copyCommand); + if (ok) { + setCopied(true); + window.setTimeout(() => setCopied(false), 2000); + } + }, [copyCommand]); + + const showLiveInput = isAnimating && isTyping; + const isReady = + isAnimating && + history.some((line) => line.kind === 'success') && + !showLiveInput; + + return ( +
+
+
+
+
+ + + +
+ + reactpress + {isReady && ( + + Running + + )} + + +
+ +
+
+ {history.map((line, index) => ( +
+ {line.kind === 'input' ? ( + <> + + $ + + {line.text} + + ) : ( + + {line.text} + + )} +
+ ))} + + {showLiveInput && ( +
+ + $ + + + {activeInput} + + +
+ )} + +
+
+ + {showHint && ( +

+ + Copy all commands and paste into your terminal to get started + +

+ )} +
+
+ ); +} diff --git a/docs/src/components/CliCommandBlock/styles.module.css b/docs/src/components/CliCommandBlock/styles.module.css new file mode 100644 index 0000000..9e133b1 --- /dev/null +++ b/docs/src/components/CliCommandBlock/styles.module.css @@ -0,0 +1,358 @@ +.wrapper { + position: relative; + width: 100%; + max-width: 560px; + margin-left: auto; + margin-right: auto; +} + +.section { + max-width: 640px; +} + +.glow { + position: absolute; + inset: -1px; + border-radius: 14px; + background: linear-gradient(135deg, #bd34fe, #41d1ff, #087ea4, #bd34fe); + background-size: 300% 300%; + animation: borderGlow 8s ease infinite; + opacity: 0.65; + z-index: 0; +} + +@keyframes borderGlow { + 0%, + 100% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } +} + +.panel { + position: relative; + z-index: 1; + border-radius: 12px; + overflow: hidden; + background: rgba(12, 14, 22, 0.88); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.04) inset, + 0 12px 40px rgba(0, 0, 0, 0.28); +} + +.hero .panel { + animation: panelEnter 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +@keyframes panelEnter { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.toolbar { + display: flex; + align-items: center; + gap: 0.65rem; + padding: 0.5rem 0.85rem; + background: rgba(255, 255, 255, 0.04); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.dots { + display: flex; + gap: 5px; + flex-shrink: 0; +} + +.dots span { + width: 9px; + height: 9px; + border-radius: 50%; +} + +.dots span:nth-child(1) { + background: #ff5f57; +} + +.dots span:nth-child(2) { + background: #febc2e; +} + +.dots span:nth-child(3) { + background: #28c840; +} + +.toolbarTitle { + flex: 1; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; + font-weight: 500; + color: rgba(255, 255, 255, 0.4); + font-family: var(--ifm-font-family-monospace); + text-align: left; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.copyBtn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.35rem 0.75rem; + border: none; + border-radius: 7px; + font-size: 0.78rem; + font-weight: 600; + cursor: pointer; + color: #0a0c12; + background: linear-gradient(135deg, #41d1ff 0%, #bd34fe 100%); + box-shadow: 0 2px 12px rgba(65, 209, 255, 0.3); + transition: + transform 0.2s ease, + box-shadow 0.2s ease, + filter 0.2s ease; + flex-shrink: 0; +} + +.copyBtn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 18px rgba(189, 52, 254, 0.35); + filter: brightness(1.06); +} + +.copyBtn:active { + transform: scale(0.98); +} + +.copyBtnDone { + background: linear-gradient(135deg, #74e68f 0%, #41d1ff 100%); + box-shadow: 0 2px 12px rgba(116, 230, 143, 0.3); +} + +.body { + padding: 0; +} + +.bodyAnimated { + min-height: 11.5rem; +} + +.terminalScroll { + max-height: 13.5rem; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.15) transparent; +} + +.terminalScroll::-webkit-scrollbar { + width: 5px; +} + +.terminalScroll::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + border-radius: 4px; +} + +.commandRow { + display: flex; + align-items: flex-start; + gap: 0.5rem; + padding: 0.45rem 0.9rem; +} + +.commandRow:first-child { + padding-top: 0.65rem; +} + +.commandRow:last-child { + padding-bottom: 0.65rem; +} + +.commandRow + .commandRow { + border-top: 1px solid rgba(255, 255, 255, 0.04); +} + +.prompt { + flex-shrink: 0; + font-family: var(--ifm-font-family-monospace); + font-size: 0.88rem; + font-weight: 700; + color: #74e68f; + user-select: none; +} + +.commandText { + flex: 1; + font-family: var(--ifm-font-family-monospace); + font-size: clamp(0.78rem, 2vw, 0.88rem); + color: #e8edf5; + background: none; + border: none; + padding: 0; + text-align: left; + word-break: break-all; +} + +.cursor { + display: inline-block; + width: 0.5em; + height: 1em; + margin-left: 1px; + vertical-align: text-bottom; + background: #74e68f; + animation: cursorBlink 1s step-end infinite; +} + +@keyframes cursorBlink { + 0%, + 50% { + opacity: 1; + } + 51%, + 100% { + opacity: 0; + } +} + +.outputRow, +.successRow { + padding: 0.2rem 0.9rem 0.2rem 1.65rem; + animation: lineIn 0.28s ease both; +} + +.outputRow:first-of-type, +.successRow:first-of-type { + padding-top: 0.15rem; +} + +@keyframes lineIn { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.outputText { + display: block; + font-family: var(--ifm-font-family-monospace); + font-size: clamp(0.72rem, 1.8vw, 0.82rem); + color: rgba(255, 255, 255, 0.48); + line-height: 1.45; +} + +.successText { + display: block; + font-family: var(--ifm-font-family-monospace); + font-size: clamp(0.72rem, 1.8vw, 0.82rem); + color: #74e68f; + line-height: 1.5; +} + +.successRow:first-of-type .successText { + font-weight: 600; + margin-top: 0.15rem; +} + +.toolbarStatus { + font-size: 0.68rem; + font-weight: 600; + color: #74e68f; + padding: 0.12rem 0.45rem; + border-radius: 999px; + background: rgba(116, 230, 143, 0.12); + border: 1px solid rgba(116, 230, 143, 0.28); + animation: statusIn 0.35s ease both; +} + +@keyframes statusIn { + from { + opacity: 0; + transform: scale(0.92); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.dotLive { + box-shadow: 0 0 0 2px rgba(40, 200, 64, 0.35); + animation: dotPulse 2s ease-in-out infinite; +} + +@keyframes dotPulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.55; + } +} + +.wrapper[data-ready] .glow { + opacity: 0.85; +} + +.hint { + margin: 0; + padding: 0.55rem 0.9rem 0.7rem; + font-size: 0.76rem; + color: rgba(255, 255, 255, 0.38); + text-align: center; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +@media (max-width: 600px) { + .toolbarTitle { + display: none; + } + + .copyBtn span { + font-size: 0.75rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .glow { + animation: none; + } + + .hero .panel { + animation: none; + } + + .cursor, + .outputRow, + .successRow, + .toolbarStatus, + .dotLive { + animation: none; + } +} + +:global(html[data-theme='light']) .panel { + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.06) inset, + 0 12px 40px rgba(8, 126, 164, 0.18); +} diff --git a/docs/src/components/CliCommandBlock/useCliTypewriter.ts b/docs/src/components/CliCommandBlock/useCliTypewriter.ts new file mode 100644 index 0000000..93cfc9c --- /dev/null +++ b/docs/src/components/CliCommandBlock/useCliTypewriter.ts @@ -0,0 +1,186 @@ +import { useEffect, useMemo, useState } from 'react'; +import { + QUICK_START_COMMANDS, + getQuickStartDemoOutputs, + getQuickStartDevReadyLines, + type QuickStartLocale, +} from '@site/src/constants/quickStartCommands'; + +export type TerminalLine = { + kind: 'input' | 'output' | 'success'; + text: string; +}; + +type Options = { + enabled: boolean; + locale?: QuickStartLocale; + commands?: readonly string[]; + loop?: boolean; + charDelayMs?: number; + linePauseMs?: number; + outputDelayMs?: number; + holdMs?: number; +}; + +function prefersReducedMotion(): boolean { + if (typeof window === 'undefined') { + return false; + } + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; +} + +function buildStaticTerminal( + commands: readonly string[], + locale: QuickStartLocale, +): TerminalLine[] { + const demoOutputs = getQuickStartDemoOutputs(locale); + const readyLines = getQuickStartDevReadyLines(locale); + const lines: TerminalLine[] = []; + for (const cmd of commands) { + lines.push({ kind: 'input', text: cmd }); + for (const out of demoOutputs[cmd] ?? []) { + lines.push({ kind: 'output', text: out }); + } + } + for (const line of readyLines) { + lines.push({ kind: 'success', text: line }); + } + return lines; +} + +export function useCliTypewriter({ + enabled, + locale = 'en', + commands = QUICK_START_COMMANDS, + loop = true, + charDelayMs = 42, + linePauseMs = 380, + outputDelayMs = 140, + holdMs = 4200, +}: Options) { + const demoOutputs = useMemo( + () => getQuickStartDemoOutputs(locale), + [locale], + ); + const readyLines = useMemo( + () => getQuickStartDevReadyLines(locale), + [locale], + ); + const staticLines = useMemo( + () => buildStaticTerminal(commands, locale), + [commands, locale], + ); + + const [history, setHistory] = useState([]); + const [activeInput, setActiveInput] = useState(''); + const [isTyping, setIsTyping] = useState(false); + const [reduceMotion, setReduceMotion] = useState(false); + + useEffect(() => { + setReduceMotion(prefersReducedMotion()); + const mq = window.matchMedia('(prefers-reduced-motion: reduce)'); + const onChange = () => setReduceMotion(mq.matches); + mq.addEventListener('change', onChange); + return () => mq.removeEventListener('change', onChange); + }, []); + + const animate = enabled && !reduceMotion; + + useEffect(() => { + if (!animate) { + setHistory(staticLines); + setActiveInput(''); + setIsTyping(false); + return; + } + + let cancelled = false; + const timers = new Set>(); + + const wait = (ms: number) => + new Promise((resolve) => { + const id = setTimeout(() => { + timers.delete(id); + resolve(); + }, ms); + timers.add(id); + }); + + const run = async () => { + do { + if (cancelled) { + return; + } + + setHistory([]); + setActiveInput(''); + setIsTyping(true); + + for (const cmd of commands) { + if (cancelled) { + return; + } + + for (let i = 0; i <= cmd.length; i += 1) { + if (cancelled) { + return; + } + setActiveInput(cmd.slice(0, i)); + if (i < cmd.length) { + await wait(charDelayMs); + } + } + + setHistory((prev) => [...prev, { kind: 'input', text: cmd }]); + setActiveInput(''); + await wait(linePauseMs); + + for (const out of demoOutputs[cmd] ?? []) { + if (cancelled) { + return; + } + await wait(outputDelayMs); + setHistory((prev) => [...prev, { kind: 'output', text: out }]); + } + } + + for (const line of readyLines) { + if (cancelled) { + return; + } + await wait(outputDelayMs); + setHistory((prev) => [...prev, { kind: 'success', text: line }]); + } + + setIsTyping(false); + await wait(holdMs); + } while (loop && !cancelled); + }; + + run(); + + return () => { + cancelled = true; + timers.forEach(clearTimeout); + }; + }, [ + animate, + commands, + demoOutputs, + readyLines, + staticLines, + loop, + charDelayMs, + linePauseMs, + outputDelayMs, + holdMs, + ]); + + return { + animate, + history: animate ? history : staticLines, + activeInput: animate ? activeInput : '', + isTyping: animate && isTyping, + showCursor: animate && isTyping, + }; +} diff --git a/docs/src/components/Features/index.tsx b/docs/src/components/Features/index.tsx index 957a03e..15ee09c 100644 --- a/docs/src/components/Features/index.tsx +++ b/docs/src/components/Features/index.tsx @@ -2,81 +2,87 @@ import type { ReactNode } from 'react'; import clsx from 'clsx'; import Heading from '@theme/Heading'; import styles from './styles.module.css'; -import Translate, { translate } from '@docusaurus/Translate'; +import { translate } from '@docusaurus/Translate'; type FeatureItem = { title: string; Svg: React.ComponentType>; description: ReactNode; + accent: 1 | 2 | 3; }; const FeatureList: FeatureItem[] = [ { - title: translate({ - message: '组件化', - }), - Svg: require('@site/static/img/undraw_react.svg').default, + title: translate({ message: 'Zero-config setup', id: 'home.feature.zero.title' }), + Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: translate({ - message: '基于 AntDesign 组件库 v5 最新版的交互语言和视觉风格。', + message: + 'Installer-style flow auto-configures your site and database — as familiar as setting up a popular blog platform.', + id: 'home.feature.zero.desc', }), + accent: 1, }, { - title: translate({ - message: '国际化', - }), - Svg: require('@site/static/img/undraw_around_the_world.svg').default, + title: translate({ message: 'All-in-one CLI', id: 'home.feature.cli.title' }), + Svg: require('@site/static/img/undraw_version_control.svg').default, description: translate({ - message: '支持中英文切换,国际化配置管理能力。', + message: + 'Environment checks, status output, interactive menus, and direct links right after startup.', + id: 'home.feature.cli.desc', }), + accent: 2, }, { - title: translate({ - message: '黑白主题', - }), - Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, + title: translate({ message: 'Modern interface', id: 'home.feature.ui.title' }), + Svg: require('@site/static/img/undraw_react.svg').default, description: translate({ - message: '支持亮色和暗黑模式主题自由切换。', + message: 'Modern admin and public site with light/dark theme support.', + id: 'home.feature.ui.desc', }), + accent: 3, }, { - title: translate({ - message: '创作管理', - }), - Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, + title: translate({ message: 'Content & authoring', id: 'home.feature.content.title' }), + Svg: require('@site/static/img/undraw_typewriter.svg').default, description: translate({ - message: '内置 MarkDown 编辑器,支持文章写文章、分类目录管理,标签管理。', + message: + 'Built-in Markdown editor; articles, categories, tags, pages, comments, and media in one place.', + id: 'home.feature.content.desc', }), + accent: 1, }, { - title: translate({ - message: '内容管理', - }), - Svg: require('@site/static/img/undraw_version_control.svg').default, + title: translate({ message: 'Open integrations', id: 'home.feature.headless.title' }), + Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: translate({ - message: '支持自定义新页面、内容评论管理,完整的社区互动功能。', + message: 'Open APIs, access keys, and event callbacks for external systems and automation.', + id: 'home.feature.headless.desc', }), + accent: 2, }, { - title: translate({ - message: '多端适配', - }), - Svg: require('@site/static/img/undraw_typewriter.svg').default, + title: translate({ message: 'Internationalization', id: 'home.feature.i18n.title' }), + Svg: require('@site/static/img/undraw_around_the_world.svg').default, description: translate({ - message: '完美适配电脑、平板、移动端H5页面。', + message: 'Chinese/English UI, comfortable on desktop, tablet, and mobile.', + id: 'home.feature.i18n.desc', }), + accent: 3, }, ]; -function Feature({ title, Svg, description }: FeatureItem) { +function Feature({ title, Svg, description, accent }: FeatureItem) { return ( -
-
- -
-
- {title} -

{description}

-
+
+
+
+ +
+ + {title} + +

{description}

+
); } diff --git a/docs/src/components/Features/styles.module.css b/docs/src/components/Features/styles.module.css index b248eb2..2539a88 100644 --- a/docs/src/components/Features/styles.module.css +++ b/docs/src/components/Features/styles.module.css @@ -1,11 +1,75 @@ .features { display: flex; align-items: center; - padding: 2rem 0; + padding: 0.5rem 0 1rem; width: 100%; } +.featureCol { + margin-bottom: 1.25rem; +} + +.featureCard { + height: 100%; + padding: 1.4rem 1.3rem 1.55rem; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + transition: + box-shadow 0.25s ease, + transform 0.25s ease, + border-color 0.25s ease; +} + +html[data-theme='light'] .featureCard { + background: rgba(255, 255, 255, 0.8); + border-color: var(--home-border); +} + +.featureCard:hover { + transform: translateY(-4px); + border-color: rgba(189, 52, 254, 0.35); + box-shadow: + 0 16px 48px rgba(0, 0, 0, 0.12), + 0 0 40px rgba(189, 52, 254, 0.08); +} + +.accent1 { + border-top: 3px solid #bd34fe; + box-shadow: inset 0 1px 0 rgba(189, 52, 254, 0.2); +} + +.accent2 { + border-top: 3px solid var(--home-button-primary); +} + +.accent3 { + border-top: 3px solid #41d1ff; +} + +.featureIcon { + text-align: center; + margin-bottom: 0.5rem; +} + .featureSvg { - height: 200px; - width: 200px; + height: 110px; + width: 110px; + opacity: 0.9; +} + +.featureTitle { + text-align: center; + font-size: 1.1rem !important; + margin-bottom: 0.5rem !important; +} + +.featureDesc { + text-align: center; + font-size: 0.9rem; + line-height: 1.55; + color: var(--home-secondary-text); + margin: 0; } diff --git a/docs/src/constants/quickStartCommands.ts b/docs/src/constants/quickStartCommands.ts new file mode 100644 index 0000000..021ba2c --- /dev/null +++ b/docs/src/constants/quickStartCommands.ts @@ -0,0 +1,77 @@ +/** ReactPress 3.0 quick-start commands (homepage + docs) */ +export const QUICK_START_INSTALL_COMMAND = 'npm i -g @fecommunity/reactpress@3'; + +export const QUICK_START_COMMANDS = [ + QUICK_START_INSTALL_COMMAND, + 'mkdir my-blog && cd my-blog', + 'reactpress init', + 'reactpress dev', +] as const; + +/** One-click copy: multi-line script */ +export const QUICK_START_SCRIPT = QUICK_START_COMMANDS.join('\n'); + +export const QUICK_START_COPY_COMMAND = QUICK_START_SCRIPT; + +export type QuickStartLocale = 'en' | 'zh'; + +type DemoOutputs = Record; +type DevReadyLines = readonly string[]; + +const QUICK_START_DEMO_OUTPUTS_EN: DemoOutputs = { + [QUICK_START_INSTALL_COMMAND]: ['added 1 package in 6s'], + 'mkdir my-blog && cd my-blog': [], + 'reactpress init': [ + '[reactpress] Created .reactpress/config.json', + '[reactpress] Docker MySQL is ready', + ], + 'reactpress dev': [ + '[reactpress] Starting API (first run may install deps)…', + '[reactpress] API ready, starting frontend…', + ], +}; + +const QUICK_START_DEMO_OUTPUTS_ZH: DemoOutputs = { + [QUICK_START_INSTALL_COMMAND]: ['added 1 package in 6s'], + 'mkdir my-blog && cd my-blog': [], + 'reactpress init': [ + '[reactpress] 已生成 .reactpress/config.json', + '[reactpress] Docker MySQL 已就绪', + ], + 'reactpress dev': [ + '[reactpress] 正在启动 API(首次可能需安装依赖,请稍候)…', + '[reactpress] API 已就绪,正在启动前端…', + ], +}; + +const QUICK_START_DEV_READY_LINES_EN: DevReadyLines = [ + '✓ ReactPress dev environment is ready', + 'Site http://localhost:3001', + 'Admin http://localhost:3001/admin', + 'API http://localhost:3002/api', +]; + +const QUICK_START_DEV_READY_LINES_ZH: DevReadyLines = [ + '✓ ReactPress 开发环境已就绪', + '前台 http://localhost:3001', + '管理端 http://localhost:3001/admin', + 'API http://localhost:3002/api', +]; + +/** @deprecated Use getQuickStartDemoOutputs(locale) */ +export const QUICK_START_DEMO_OUTPUTS = QUICK_START_DEMO_OUTPUTS_EN; + +/** @deprecated Use getQuickStartDevReadyLines(locale) */ +export const QUICK_START_DEV_READY_LINES = QUICK_START_DEV_READY_LINES_EN; + +export function getQuickStartDemoOutputs( + locale: string, +): DemoOutputs { + return locale === 'zh' ? QUICK_START_DEMO_OUTPUTS_ZH : QUICK_START_DEMO_OUTPUTS_EN; +} + +export function getQuickStartDevReadyLines(locale: string): DevReadyLines { + return locale === 'zh' + ? QUICK_START_DEV_READY_LINES_ZH + : QUICK_START_DEV_READY_LINES_EN; +} diff --git a/docs/src/css/homepage.scss b/docs/src/css/homepage.scss new file mode 100644 index 0000000..4fb2613 --- /dev/null +++ b/docs/src/css/homepage.scss @@ -0,0 +1,31 @@ +/* 首页全局增强 */ + +.homepage :global(.main-wrapper) { + background: var(--home-hero-floor-background-bottom); +} + +.homepage :global(.navbar) { + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + box-shadow: none; + transition: background 0.25s ease, border-color 0.25s ease; +} + +html[data-theme='light'] .homepage :global(.navbar) { + border-bottom-color: var(--home-border); + background: rgba(255, 255, 255, 0.88); +} + +html[data-theme='dark'] .homepage :global(.navbar) { + background: rgba(15, 17, 24, 0.78); +} + +.homepage :global(footer.footer) { + border-top: 1px solid var(--home-border); +} + +/* Hero → Highlights 过渡 */ +.homepage :global(.main-wrapper > main) { + overflow-x: hidden; +} diff --git a/docs/src/pages/Home/CallToAction/index.tsx b/docs/src/pages/Home/CallToAction/index.tsx index f5829ad..7c3b6b8 100644 --- a/docs/src/pages/Home/CallToAction/index.tsx +++ b/docs/src/pages/Home/CallToAction/index.tsx @@ -1,14 +1,8 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - import React from 'react'; import Features from '@site/src/components/Features'; import Translate from '@docusaurus/Translate'; +import Link from '@docusaurus/Link'; import styles from './styles.module.css'; @@ -17,10 +11,22 @@ function CallToAction() {
-

- 主要特性 -

+
+

+ Platform capabilities +

+

+ + From personal blogs to team content sites — publish, manage, and collaborate out of the box. + +

+
+
+ + Read full documentation + +
); diff --git a/docs/src/pages/Home/CallToAction/styles.module.css b/docs/src/pages/Home/CallToAction/styles.module.css index 8baa748..5b8c678 100644 --- a/docs/src/pages/Home/CallToAction/styles.module.css +++ b/docs/src/pages/Home/CallToAction/styles.module.css @@ -1,24 +1,29 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - .wrapper { text-align: center; overflow: hidden; position: relative; + background: var(--home-section-bottom); +} + +.wrapper::before { + content: ''; + position: absolute; + inset: 0; + background: + radial-gradient(ellipse 80% 50% at 50% 0%, rgba(189, 52, 254, 0.12), transparent), + radial-gradient(ellipse 60% 40% at 80% 100%, rgba(65, 209, 255, 0.1), transparent); + pointer-events: none; } .background { position: absolute; width: 100%; height: 100%; - background-image: url("/img/cta-bg-pattern.png"); + background-image: url('/img/cta-bg-pattern.png'); background-size: cover; background-position: center center; - opacity: 0.1; + opacity: 0.06; + pointer-events: none; } .container { @@ -27,37 +32,77 @@ flex-direction: column; justify-content: center; align-items: center; - gap: 1rem; - padding: 2rem; - margin: 8rem auto; + gap: 0.5rem; + padding: 3.5rem 1.25rem 4.5rem; + max-width: 1200px; + margin: 0 auto; +} + +.header { + margin-bottom: 0.75rem; } .title { - font-size: 40px; - line-height: 140%; + font-size: clamp(1.5rem, 4vw, 2.1rem); + line-height: 1.3; + margin: 0 0 0.5rem; + background: linear-gradient(120deg, var(--home-text) 30%, var(--home-button-primary)); + background-clip: text; + -webkit-text-fill-color: transparent; +} + +html[data-theme='light'] .title { + background: none; + -webkit-text-fill-color: unset; + color: var(--home-text); +} + +.subtitle { + font-size: 1rem; + line-height: 1.55; + color: var(--home-secondary-text); + max-width: 32rem; + margin: 0 auto 1rem; +} + +.footerCta { + margin-top: 2rem; } .primaryButton { - background-color: var(--home-button-primary); - color: var(--home-button-primary-text); + background: linear-gradient(135deg, #bd34fe 0%, #41d1ff 50%, #087ea4 100%); + background-size: 200% auto; + color: #fff !important; border: none; - padding: 10px 24px; + padding: 13px 32px; border-radius: 99rem; - font-weight: bold; - font-size: 17px; - display: flex; + font-weight: 600; + font-size: 1rem; + display: inline-flex; align-items: center; justify-content: center; - gap: 0.35rem; + text-decoration: none !important; + box-shadow: 0 8px 32px rgba(189, 52, 254, 0.35); + transition: + transform 0.2s ease, + box-shadow 0.2s ease, + background-position 0.3s ease; } .primaryButton:hover { - background-color: var(--home-button-primary-hover); - color: var(--home-button-primary-text); + color: #fff !important; + transform: translateY(-2px); + background-position: right center; + box-shadow: 0 12px 40px rgba(65, 209, 255, 0.4); } @media (max-width: 600px) { .background { display: none; } + + .container { + padding-top: 2.5rem; + padding-bottom: 3rem; + } } diff --git a/docs/src/pages/Home/Hero/index.tsx b/docs/src/pages/Home/Hero/index.tsx index e31f5db..5c93d78 100644 --- a/docs/src/pages/Home/Hero/index.tsx +++ b/docs/src/pages/Home/Hero/index.tsx @@ -1,27 +1,27 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - import React from 'react'; - +import Link from '@docusaurus/Link'; import Logo from '@site/src/pages/Home/Logo'; - import GridBackground from '@site/src/pages/Home/Hero/GridBackground'; import FloorBackground from '@site/src/pages/Home/Hero/FloorBackground'; import Devices from '@site/src/pages/Home/Hero/Devices'; +import CliCommandBlock from '@site/src/components/CliCommandBlock'; import styles from './styles.module.css'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Translate from '@docusaurus/Translate'; +import GitHubButton from 'react-github-btn'; function Hero() { const { siteConfig } = useDocusaurusContext(); return ( -
-
+
+
+ + + +
+ +
@@ -32,25 +32,55 @@ function Hero() {
+
- -

{siteConfig.title}

-

- 一个基于Next.js的博客&CMS系统。 -

-
- - 入门指南 - - - 试用一下 - - - - +
+ +
+

{siteConfig.title}

+ 3.0 + + + Star + + +
+

+ + One package, one minute to your own CMS. + +

+ +
+
+ + Get Started + + + What's New in 3.0 + + + Live Demo + +
+ +
+ +
+
-
+ ); } diff --git a/docs/src/pages/Home/Hero/styles.module.css b/docs/src/pages/Home/Hero/styles.module.css index afcf0b7..3c2185c 100644 --- a/docs/src/pages/Home/Hero/styles.module.css +++ b/docs/src/pages/Home/Hero/styles.module.css @@ -1,34 +1,91 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - .container { - height: 900px; + position: relative; + display: flex; + flex-direction: column; + min-height: clamp(680px, 88vh, 860px); + overflow: hidden; } -.backgroundContainer { +.mesh { position: absolute; - z-index: -1; + inset: 0; + z-index: 0; + pointer-events: none; overflow: hidden; - width: 100%; } -.socialLinks { +.orb1, +.orb2, +.orb3 { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.45; + animation: orbDrift 14s ease-in-out infinite; +} + +.orb1 { + width: 380px; + height: 380px; + top: -60px; + left: 8%; + background: radial-gradient(circle, #bd34fe 0%, transparent 70%); +} + +.orb2 { + width: 320px; + height: 320px; + top: 12%; + right: 6%; + background: radial-gradient(circle, #41d1ff 0%, transparent 70%); + animation-delay: -5s; +} + +.orb3 { + width: 260px; + height: 260px; + bottom: 28%; + left: 38%; + background: radial-gradient(circle, #087ea4 0%, transparent 70%); + animation-delay: -9s; + opacity: 0.35; +} + +@keyframes orbDrift { + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + 50% { + transform: translate(16px, -10px) scale(1.03); + } +} + +/* 底部场景层:网格 + 设备 + 地面,固定在容器下方,不被内容遮挡 */ +.scene { + position: absolute; + left: 0; + right: 0; + bottom: 0; + z-index: 1; display: flex; + flex-direction: column; + align-items: center; justify-content: flex-end; - gap: 1rem; - margin-top: 1rem; - margin-right: 1rem; + height: min(52%, 420px); + min-height: 300px; + pointer-events: none; } .gridBackground { display: flex; justify-content: center; align-items: flex-end; - height: 600px; + width: 100%; + height: 380px; + opacity: 0.9; + mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 100%); } .devices { @@ -37,157 +94,285 @@ justify-content: center; align-items: flex-end; width: 100%; - height: 100%; - z-index: 1; - top: -100px; - left: -20px; + bottom: 48px; + left: 0; + opacity: 0.82; + transform: translateY(0); + animation: devicesFade 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both; +} + +@keyframes devicesFade { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 0.82; + transform: translateY(0); + } } .floorBackground { display: flex; justify-content: center; width: 100vw; - height: 300px; - position: relative; - top: -4px; + height: 220px; + flex-shrink: 0; background: linear-gradient( to bottom, - var(--home-hero-floor-background) 0%, - var(--home-hero-floor-background-bottom) + transparent 0%, + var(--home-hero-floor-background-bottom) 88% ), var(--home-hero-floor-background-bottom); } -.svgContent { - position: absolute; +.content { + position: relative; + z-index: 2; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2.5rem 1.25rem 0; + /* 为底部场景预留空间,避免文案与终端压住封面 */ + margin-bottom: min(48%, 400px); + text-align: center; } -.logo { - margin-bottom: 1rem; - animation: rotate 3s infinite linear; +.intro { + max-width: 42rem; + width: 100%; + animation: contentEnter 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; } - -@keyframes rotate { +@keyframes contentEnter { from { - transform: rotate(0deg); + opacity: 0; + transform: translateY(16px); } to { - transform: rotate(360deg); + opacity: 1; + transform: translateY(0); } } +.logo { + margin-bottom: 0.65rem; + transition: transform 0.25s ease, filter 0.25s ease; + filter: drop-shadow(0 0 16px rgba(65, 209, 255, 0.3)); +} + +.logo:hover { + transform: scale(1.05); + filter: drop-shadow(0 0 24px rgba(189, 52, 254, 0.4)); +} + +.titleRow { + display: flex; + align-items: center; + gap: 0.55rem 0.75rem; + flex-wrap: wrap; + justify-content: center; + row-gap: 0.5rem; +} + +.githubWrap { + display: inline-flex; + align-items: center; + flex-shrink: 0; + min-height: 28px; + line-height: 1; +} + .title { - font-size: 40px; - margin-bottom: 0.5rem; - margin-top: 1.5rem; - background: -webkit-linear-gradient(120deg, #bd34fe 30%, #41d1ff); + font-size: clamp(2rem, 4.8vw, 2.85rem); + margin: 0; + background: linear-gradient(120deg, #bd34fe 0%, #41d1ff 45%, #087ea4 100%); + background-size: 200% auto; background-clip: text; -webkit-text-fill-color: transparent; + animation: titleShine 5s linear infinite; } -.subtitle { - font-weight: 500; - color: var(--home-secondary-text); - font-size: 28px; - animation: flicker 2s infinite; +@keyframes titleShine { + to { + background-position: 200% center; + } } -.subtitle:hover { - font-weight: 600; +.badge { + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.06em; + padding: 0.22rem 0.55rem; + border-radius: 8px; + background: linear-gradient(135deg, #41d1ff, #bd34fe); + color: #0a0c12; + -webkit-text-fill-color: #0a0c12; + box-shadow: 0 3px 14px rgba(65, 209, 255, 0.35); } -.content { - position: relative; - top: 120px; +.subtitle { + font-weight: 400; + color: var(--home-secondary-text); + font-size: clamp(0.92rem, 2.2vw, 1.08rem); + line-height: 1.55; + max-width: 36rem; + margin: 0.65rem auto 0; +} + +.actions { + margin-top: 1.5rem; display: flex; flex-direction: column; - justify-content: center; align-items: center; - text-align: center; - z-index: 1; + gap: 1.1rem; } .buttonContainer { display: flex; - gap: 1rem; - margin-top: 1rem; + gap: 0.65rem; flex-wrap: wrap; justify-content: center; } -.primaryButton { - background-color: var(--home-button-primary); - color: var(--home-button-primary-text); +.primaryButton, +.secondaryButton, +.ghostButton { border: none; - padding: 10px 24px; + padding: 10px 22px; border-radius: 99rem; - font-weight: bold; - font-size: 17px; - display: flex; + font-weight: 600; + font-size: 0.92rem; + display: inline-flex; align-items: center; justify-content: center; - gap: 0.35rem; + text-decoration: none !important; + transition: + transform 0.2s ease, + box-shadow 0.2s ease, + background 0.2s ease, + color 0.2s ease, + border-color 0.2s ease; +} + +.primaryButton { + background: linear-gradient(135deg, #087ea4 0%, #41d1ff 100%); + color: #fff !important; + box-shadow: 0 4px 20px rgba(8, 126, 164, 0.32); } .primaryButton:hover { - color: var(--home-button-primary-text); - background-color: var(--home-button-primary-hover); + color: #fff !important; + transform: translateY(-2px); + box-shadow: 0 8px 28px rgba(65, 209, 255, 0.4); } .secondaryButton { - border: none; - padding: 10px 24px; - border-radius: 99rem; - font-weight: bold; - font-size: 17px; - display: flex; - align-items: center; - justify-content: center; - gap: 0.35rem; - color: var(--home-button-secondary-text); + color: var(--home-button-secondary-text) !important; border: 1px solid var(--home-button-secondary-border); - background-color: var(--home-button-secondary); + background: rgba(255, 255, 255, 0.06); + backdrop-filter: blur(8px); +} + +html[data-theme='light'] .secondaryButton { + background: rgba(255, 255, 255, 0.85); } .secondaryButton:hover { - background-color: var(--home-button-secondary-hover); - color: var(--home-button-secondary-text); + transform: translateY(-2px); + border-color: var(--home-button-primary); + color: var(--home-button-primary) !important; } -@media (max-width: 600px) { - .largeFormatDevices { - display: none; +.ghostButton { + color: #bd34fe !important; + background: transparent; + border: 1px solid rgba(189, 52, 254, 0.4); +} + +.ghostButton:hover { + transform: translateY(-2px); + background: rgba(189, 52, 254, 0.08); + box-shadow: 0 4px 16px rgba(189, 52, 254, 0.18); +} + +.cliWrap { + width: 100%; + max-width: 560px; + animation: cliEnter 0.65s cubic-bezier(0.22, 1, 0.36, 1) 0.12s both; +} + +@keyframes cliEnter { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); } } -@media (max-width: 450px) { +@media (prefers-reduced-motion: reduce) { + .orb1, + .orb2, + .orb3 { + animation: none; + } + + .title { + animation: none; + } + + .intro, + .cliWrap, + .devices { + animation: none; + } +} + +@media (max-width: 600px) { + .container { + min-height: 640px; + } + .content { - top: 50px; + padding-top: 1.75rem; + margin-bottom: min(50%, 340px); + } + + .devices { + bottom: 32px; + opacity: 0.65; + } + + @keyframes devicesFade { + to { + opacity: 0.65; + } } .primaryButton, - .secondaryButton { - width: calc(100% - 2rem); + .secondaryButton, + .ghostButton { + width: 100%; + max-width: 280px; } -} -.githubButton { - display: flex; - align-items: center; - > img { - width: 140px; + .actions { + margin-top: 1.25rem; + gap: 0.9rem; } } -@keyframes flicker { - 0%{ - opacity: 1; - } - 50%{ - opacity: 0.5; +@media (max-width: 450px) { + .gridBackground { + height: 280px; } - 100%{ - opacity: 1; + + .scene { + min-height: 260px; } -} \ No newline at end of file +} diff --git a/docs/src/pages/Home/Highlights/index.tsx b/docs/src/pages/Home/Highlights/index.tsx new file mode 100644 index 0000000..2bab801 --- /dev/null +++ b/docs/src/pages/Home/Highlights/index.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { translate } from '@docusaurus/Translate'; +import Translate from '@docusaurus/Translate'; +import styles from './styles.module.css'; + +type HighlightItem = { + icon: string; + title: string; + description: string; +}; + +const items: HighlightItem[] = [ + { + icon: '⚡', + title: translate({ message: 'Zero-config setup', id: 'home.highlight.zero.title' }), + description: translate({ + message: 'Two commands set up your site and database — no manual environment configuration.', + id: 'home.highlight.zero.desc', + }), + }, + { + icon: '📦', + title: translate({ message: 'One command line tool', id: 'home.highlight.cli.title' }), + description: translate({ + message: 'After a global install, initialize, develop, self-check, and view status from the terminal.', + id: 'home.highlight.cli.desc', + }), + }, + { + icon: '🩺', + title: translate({ message: 'Easy to get started', id: 'home.highlight.dx.title' }), + description: translate({ + message: 'Interactive guides, environment checks, and status tips — open your site and admin right after startup.', + id: 'home.highlight.dx.desc', + }), + }, +]; + +export default function Highlights() { + return ( +
+
+

+ 3.0 highlights +

+
+
+ {items.map((item, index) => ( +
+ + {item.icon} + +

{item.title}

+

{item.description}

+
+ ))} +
+
+ ); +} diff --git a/docs/src/pages/Home/Highlights/styles.module.css b/docs/src/pages/Home/Highlights/styles.module.css new file mode 100644 index 0000000..16d0ce8 --- /dev/null +++ b/docs/src/pages/Home/Highlights/styles.module.css @@ -0,0 +1,123 @@ +.section { + position: relative; + z-index: 3; + max-width: 1100px; + margin: 0 auto; + padding: 2rem 1.25rem 2.75rem; +} + +.header { + text-align: center; + margin-bottom: 1.5rem; +} + +.heading { + font-size: clamp(1.25rem, 3vw, 1.5rem); + font-weight: 700; + margin: 0; + color: var(--home-text); + animation: fadeUp 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +@keyframes fadeUp { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.card { + position: relative; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 14px; + padding: 1.3rem 1.35rem; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: 0 6px 28px rgba(0, 0, 0, 0.06); + transition: + border-color 0.3s ease, + box-shadow 0.3s ease, + transform 0.3s cubic-bezier(0.22, 1, 0.36, 1); + overflow: hidden; + animation: fadeUp 0.55s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +html[data-theme='light'] .card { + background: rgba(255, 255, 255, 0.78); + border-color: var(--home-border); + box-shadow: 0 6px 28px rgba(8, 126, 164, 0.08); +} + +.card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--home-button-primary), transparent); + opacity: 0; + transition: opacity 0.3s ease; +} + +.card:hover { + border-color: rgba(65, 209, 255, 0.4); + box-shadow: + 0 10px 36px rgba(65, 209, 255, 0.12), + 0 0 0 1px rgba(65, 209, 255, 0.08); + transform: translateY(-3px); +} + +.card:hover::before { + opacity: 1; +} + +.icon { + font-size: 1.55rem; + line-height: 1; + display: block; + margin-bottom: 0.65rem; + filter: drop-shadow(0 0 6px rgba(65, 209, 255, 0.35)); +} + +.cardTitle { + font-size: 1.05rem; + font-weight: 700; + margin: 0 0 0.45rem; + color: var(--home-text); +} + +.cardDesc { + font-size: 0.88rem; + line-height: 1.55; + margin: 0; + color: var(--home-secondary-text); +} + +@media (prefers-reduced-motion: reduce) { + .heading, + .card { + animation: none; + } +} + +@media (max-width: 900px) { + .grid { + grid-template-columns: 1fr; + } + + .section { + padding-top: 1.5rem; + } +} diff --git a/docs/src/pages/Home/index.tsx b/docs/src/pages/Home/index.tsx index 3a3e799..5709bce 100644 --- a/docs/src/pages/Home/index.tsx +++ b/docs/src/pages/Home/index.tsx @@ -1,19 +1,14 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - import React from 'react'; import Hero from '@site/src/pages/Home/Hero'; +import Highlights from '@site/src/pages/Home/Highlights'; import CallToAction from '@site/src/pages/Home/CallToAction'; export default function Home() { return ( <> + ); diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 56914b6..dfe2061 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -9,17 +9,24 @@ import Head from '@docusaurus/Head'; import Home from '@site/src/pages/Home'; import Layout from '@theme/Layout'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import { translate } from '@docusaurus/Translate'; const Index = () => { const { siteConfig } = useDocusaurusContext(); - const title = `${siteConfig.title}-${siteConfig.tagline}`; + const title = `${siteConfig.title} · ${siteConfig.tagline}`; + const description = translate({ + message: + 'ReactPress 3.0: one package, one minute to your own CMS. Zero-config install for blogs and content publishing.', + id: 'home.meta.description', + }); return ( - + {title} + diff --git a/docs/tutorial/intro.md b/docs/tutorial/intro.md index f400531..01f4a7d 100644 --- a/docs/tutorial/intro.md +++ b/docs/tutorial/intro.md @@ -6,12 +6,13 @@ title: 介绍 ## 项目简介 -`ReactPress` 是使用React开发的开源发布平台,用户可以在支持React和MySQL数据库的服务器上架设属于自己的博客、网站。也可以把 `ReactPress` 当作一个内容管理系统(CMS)来使用。 +`ReactPress` 是使用 React 开发的开源发布平台,用户可以在支持 React 和 MySQL 的服务器上架设属于自己的博客、网站,也可以把 `ReactPress` 当作内容管理系统(CMS)来使用。 +**ReactPress 3.0** 以「装一个包、敲一条命令」为产品目标:全局安装 `@fecommunity/reactpress@3`,在任意空目录执行 `reactpress init` 与 `reactpress dev` 即可零配置起站。详见 [ReactPress 3.0 平台版](./tutorial-extras/reactpress-3-0.md)。 ## 🆚 框架对比 -以下是`ReactPress`、`WordPress` 和 `VuePress` 三者的对比: +以下是 `ReactPress`、`WordPress` 和 `VuePress` 三者的对比: | 项目 | ReactPress | WordPress | VuePress | | --- | --- | --- | --- | @@ -31,111 +32,77 @@ title: 介绍 ## ✨ 特性 -- 📦 技术栈:基于 `React` + `NextJS` + `MySQL 5.7` + `NestJS` 构建 -- 🌈 组件化:基于 `antd 5.20` 最新版的交互语言和视觉风格 -- 🌍 国际化:支持中英文切换,国际化配置管理能力 -- 🌞 黑白主题:支持亮色和暗黑模式主题自由切换 -- 🖌️ 创作管理:内置 `MarkDown` 编辑器,支持文章写文章、分类目录管理,标签管理 -- 📃 页面管理:支持自定义新页面 -- 💬 评论管理:支持内容评论管理 -- 📷️ 媒体管理:支持文件本地上传和 `OSS` 文件上传 -- 📱 移动端:完美适配移动端H5页面 +- 📦 **3.0 唯一入口**:`@fecommunity/reactpress` 一条命令管理 init / dev / doctor / status +- ⚡ **零配置起站**:自动生成 `.reactpress/config.json`、`.env` 与嵌入式 MySQL +- 🩺 **可诊断**:`reactpress doctor` 与 `reactpress status` 快速排错 +- 🌈 组件化:基于 `antd 5.20` 的交互与视觉 +- 🌍 国际化:中英文切换 +- 🌞 黑白主题:亮色 / 暗黑模式 +- 🖌️ 创作管理:内置 Markdown 编辑器,文章、分类、标签 +- 📃 页面管理、💬 评论管理、📷 媒体管理(本地上传与 OSS) +- 🔌 **Headless**:API Key、Webhook、健康检查、toolkit SDK - ... ## 🔥 在线示例 [ReactPress Demo](https://blog.gaoredu.com/) -## 📦 NPM 包 +## ⌨️ 快速开始(3.0 推荐) -ReactPress 2.0 提供了三个核心 NPM 包,可以独立使用或组合使用: +### 终端用户 — 一个全局包 -- [@fecommunity/reactpress-client](./tutorial-extras/client-package) - 基于 Next.js 的前端客户端 -- [@fecommunity/reactpress-server](./tutorial-extras/server-package) - 基于 NestJS 的后端 API 服务 -- [@fecommunity/reactpress-toolkit](./tutorial-extras/toolkit-package) - TypeScript API 客户端工具包 - -每个包都有详细的使用文档,可以在进阶教程中找到。 - -## ⌨️ 本地开发 - -### 环境准备 ```bash -$ git clone --depth=1 https://github.com/fecommunity/reactpress.git -$ cd reactpress -$ npm i -g pnpm -$ pnpm i +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev ``` -### 配置文件 +浏览器访问 `http://localhost:3001`(管理端 `/admin`,API 健康检查 `/api/health`)。 -项目启动后会加载根目录下的 `.env` 配置文件,请确保MySQL数据库服务和下面的配置保持一致,并提前创建好 `reactpress` 数据库 +无子命令时运行 `reactpress` 进入交互菜单。从 2.x 升级见 [迁移指南](./tutorial-extras/migration-2-to-3.md)。 -```js -DB_HOST=127.0.0.1 // 数据库地址 -DB_PORT=3306 // 端口 -DB_USER=reactpress // 用户名 -DB_PASSWD=reactpress // 密码 -DB_DATABASE=reactpress // 数据库 -``` - -环境准备好后,执行启动命令: +### 本仓库贡献者 — Monorepo ```bash -$ pnpm run dev +git clone --depth=1 https://github.com/fecommunity/reactpress.git +cd reactpress +npm i -g pnpm +pnpm install +pnpm run dev ``` -打开浏览器访问 http://127.0.0.1:3001 +需要 Node.js ≥ 18 与 Docker(默认嵌入式 MySQL)。`pnpm run init` 可仅准备环境而不启动服务。 + +## 📦 包与文档 +| 包 | 说明 | +|----|------| +| [**@fecommunity/reactpress**](./tutorial-extras/reactpress-3-0.md) | **3.0 主包**(CLI + 内置 API) | +| [@fecommunity/reactpress-client](./tutorial-extras/client-package) | 进阶:仅部署前台 | +| [@fecommunity/reactpress-server](./tutorial-extras/server-package) | **Deprecated**,请用主包内置 API | +| [@fecommunity/reactpress-toolkit](./tutorial-extras/toolkit-package) | TypeScript API SDK(Headless) | ## 🔗 链接 - [首页](https://github.com/fecommunity/reactpress) -- [帮助文档](https://blog.gaoredu.com/knowledge/c7edfecf-4f47-4bd3-ba93-093e43cf5314/bef19159-4a6f-4343-b84e-b1a636b570f8) +- [3.0 平台版说明](./tutorial-extras/reactpress-3-0.md) +- [2.x → 3.0 迁移](./tutorial-extras/migration-2-to-3.md) - [报告问题](https://github.com/fecommunity/reactpress/issues) -- [参与共建](https://github.com/fecommunity/reactpress/pulls) -- [next.js 源码](https://github.com/vercel/next.js) -- [nest.js 源码](https://github.com/nestjs/nest) - +- [参与共建](https://github.com/fecommunity/reactpress/pulls) > 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。 ## 👥 社区互助 -如果您在使用的过程中碰到问题,可以通过下面几个途径寻求帮助,同时我们也鼓励资深用户通过下面的途径给新人提供帮助。 - -通过 WeChat 联系,可通过搜素微信号 `red_tea_v2` 或扫码加入 ,并备注来源。 - -通过 GitHub Discussions 提问时,建议使用 `Q&A` 标签。 - -通过 Stack Overflow 或者 Segment Fault 提问时,建议加上 `reactpress` 标签。 - +如果您在使用的过程中碰到问题,可以通过下面几个途径寻求帮助: -1. [GitHub Discussions](https://github.com/ant-design/ant-design/discussions) -2. [Stack Overflow](http://stackoverflow.com/questions/tagged/antd)(英文) -3. [Segment Fault](https://segmentfault.com/t/antd)(中文) +1. 先运行 `reactpress doctor` 与 `reactpress status` +2. [GitHub Issues](https://github.com/fecommunity/reactpress/issues) +3. [GitHub Discussions](https://github.com/fecommunity/reactpress/discussions) Email: admin@gaoredu.com -## ❤️ 致谢 - -ReactPress 项目深受以下开源项目的启发和帮助: - -- **[fantasticit]** - **[wipi]** - [[https://github.com/fantasticit/wipi](https://github.com/fantasticit/wipi)] - -- **[Lrunlin]** - **[blog]** - [[https://github.com/Lrunlin/blog](https://github.com/Lrunlin/blog)] - -- **[biaochenxuying]** - **[blog-react]** - [[https://github.com/biaochenxuying/blog-react](https://github.com/biaochenxuying/blog-react)] - -- **[MrXujiang]** - **[next-admin]** - [[https://github.com/MrXujiang/next-admin](https://github.com/MrXujiang/next-admin)] - -- **[lfb]** - **[nodejs-koa-blog]** - [[https://github.com/lfb/nodejs-koa-blog](https://github.com/lfb/nodejs-koa-blog)] - -…… - -我们衷心感谢这些项目的作者和贡献者们!正是有了你们的努力和付出,才有了 ReactPress 项目的今天。 - - - ## ✨ Star History -[![Star History Chart](https://api.star-history.com/svg?repos=fecommunity/reactpress&type=Date)](https://star-history.com/#fecommunity/reactpress&Date) \ No newline at end of file +[![Star History Chart](https://api.star-history.com/svg?repos=fecommunity/reactpress&type=Date)](https://star-history.com/#fecommunity/reactpress&Date) diff --git a/docs/tutorial/tutorial-basics/deploy-your-site.md b/docs/tutorial/tutorial-basics/deploy-your-site.md index 03321df..1ab6aff 100644 --- a/docs/tutorial/tutorial-basics/deploy-your-site.md +++ b/docs/tutorial/tutorial-basics/deploy-your-site.md @@ -3,72 +3,79 @@ sidebar_position: 5 title: 生产环境部署 --- -### 环境准备 -```bash -$ git clone --depth=1 https://github.com/fecommnity/reactpress.git -$ cd reactpress -$ npm i -g pnpm -$ pnpm i -``` - -### 配置文件 +## 3.0 推荐:全局 CLI -项目启动后会加载根目录下的 `.env` 配置文件,请确保MySQL数据库服务和下面的配置保持一致,并提前创建好 `reactpress` 数据库 +已在服务器安装 Node ≥ 18 与 Docker(或外部 MySQL)时: -```js -DB_HOST=127.0.0.1 // 数据库地址 -DB_PORT=3306 // 端口 -DB_USER=reactpress // 用户名 -DB_PASSWD=reactpress // 密码 -DB_DATABASE=reactpress // 数据库 +```bash +npm i -g @fecommunity/reactpress@3 +cd /path/to/your-site # 含 .reactpress/ 的项目目录 +reactpress init # 若尚未初始化 +reactpress build # 按需构建 +reactpress start # 生产模式启动 API + 前台 ``` -环境准备好后,执行启动命令: +或使用仓库提供的生产 compose 示例(DB + 前端容器,API 在宿主机): ```bash -$ pnpm run build +reactpress build +reactpress start:api # 或 pm2 管理 API +docker compose -f docker-compose.prod.yml up -d ``` -### 启动服务 +数据库备份:`reactpress db backup`。 + +--- + +## Monorepo 自托管部署 + +### 环境准备 + ```bash -$ pnpm run pm2 +git clone --depth=1 https://github.com/fecommunity/reactpress.git +cd reactpress +npm i -g pnpm +pnpm install ``` -至此,ReactPress 服务就启动成功了。 - -### 独立包部署 +配置由 `pnpm init` 或 `reactpress init` 生成;生产前请确认 `.reactpress/config.json` 与 `.env` 中的数据库与 URL。 -ReactPress 2.0 支持独立部署各个包: +### 构建与启动 ```bash -# 仅部署服务器端 -npx @fecommunity/reactpress-server --pm2 +pnpm run build +pnpm run pm2 # API + 前台 +pm2 save +pm2 startup # 可选:开机自启 +``` + +或使用一键脚本(在仓库根目录): -# 仅部署客户端 -npx @fecommunity/reactpress-client --pm2 +```bash +sh scripts/deploy.sh ``` -有关每个包的详细部署信息,请参阅[进阶教程](../tutorial-extras/client-package)。 +### 代码更新 -### 代码更新启动 -当ReactPress代码更新后,可以按照如下Shell重新启动服务: ```bash -# 更新代码 -git checkout master git pull - -# 安装依赖&构建 pnpm install pnpm run build +pm2 restart all # 或 pnpm run pm2 +``` -# 启动进程 -pm2 delete reactpress-server -pm2 delete reactpress-client -pnpm run pm2 +--- -# 开机启动 -pm2 startup -pm2 save -``` +## 进阶:独立包部署 + +3.0 默认使用 **`@fecommunity/reactpress` 内置 API**。仅在需要单独部署前台或连接远程 API 时参考: + +| 场景 | 命令 | +|------|------| +| 全栈 | `reactpress start` | +| 仅 API | `reactpress dev --api-only` / `reactpress server start` | +| 仅前台 | `reactpress dev --client-only` 或 [@fecommunity/reactpress-client](../tutorial-extras/client-package) | + +`@fecommunity/reactpress-server` 已 deprecated,请勿作为新项目的生产入口。 -以上就是ReactPress生成环境的完整部署流程。 \ No newline at end of file +更多说明见 [ReactPress 3.0 平台版](../tutorial-extras/reactpress-3-0.md) 与 [Docker 部署](../tutorial-extras/docker-deployment.md)。 diff --git a/docs/tutorial/tutorial-basics/start.md b/docs/tutorial/tutorial-basics/start.md index 2ce02ed..d27ea8d 100644 --- a/docs/tutorial/tutorial-basics/start.md +++ b/docs/tutorial/tutorial-basics/start.md @@ -3,52 +3,91 @@ sidebar_position: 1 title: 本地开发 --- +## 两种开发方式 -## ⌨️ 本地开发 +ReactPress 3.0 提供两条路径,按你的场景选择其一即可。 + +| 场景 | 方式 | 前置 | +|------|------|------| +| **建站 / 试用(推荐)** | 全局 `reactpress` | Node ≥ 18、Docker | +| **贡献 monorepo** | 仓库内 `pnpm dev` | Node ≥ 18、Docker、pnpm | + +--- + +## 方式一:全局 CLI(3.0 推荐) + +无需克隆本仓库,任意空目录即可: -### 环境准备 ```bash -$ git clone --depth=1 https://github.com/fecommunity/reactpress.git -$ cd reactpress -$ npm i -g pnpm -$ pnpm i +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init +reactpress dev ``` -### 文件结构 +| 服务 | 地址 | +|------|------| +| 前台 | http://localhost:3001 | +| 管理端 | http://localhost:3001/admin | +| API | http://localhost:3002/api | +| 健康检查 | http://localhost:3002/api/health | -项目的代码结构如下: +`init` 会生成 `.reactpress/config.json`、`.env` 与 Docker MySQL,一般**无需手改** `.env`。 -```shell -├─ client // 基于 Next.js 的前端客户端 -├─ server // 基于 NestJS 的后端 API 服务 -├─ toolkit // TypeScript API 客户端工具包 -├─ templates // 模板文件 -├─ scripts // 构建脚本 -├─ docs // 文档 -└─ package.json +常用命令: + +```bash +reactpress # 交互菜单 +reactpress doctor # 环境诊断 +reactpress status # 运行状态 +reactpress dev --api-only # 仅 API(Headless) +reactpress dev --client-only # 仅前台 ``` -ReactPress 2.0 采用了 monorepo 结构,将前端、后端和工具包分离为独立的包,可以独立开发和部署。 +更多说明见 [ReactPress 3.0 平台版](../tutorial-extras/reactpress-3-0.md)。 +--- -### 配置文件 +## 方式二:Monorepo 本仓开发 -项目启动后会加载根目录下的 `.env` 配置文件,请确保MySQL数据库服务和下面的配置保持一致,并提前创建好 `reactpress` 数据库 +### 环境准备 -```js -DB_HOST=127.0.0.1 // 数据库地址 -DB_PORT=3306 // 端口 -DB_USER=reactpress // 用户名 -DB_PASSWD=reactpress // 密码 -DB_DATABASE=reactpress // 数据库 +```bash +git clone --depth=1 https://github.com/fecommunity/reactpress.git +cd reactpress +npm i -g pnpm +pnpm install ``` +### 文件结构 + +```shell +├─ client # Next.js 前台 +├─ server # NestJS API(本仓开发用) +├─ cli # @fecommunity/reactpress 发布物 +├─ toolkit # OpenAPI 生成的 TS SDK +├─ templates # 项目模板 +└─ package.json +``` ### 启动 -环境准备好后,执行启动命令: ```bash -$ pnpm run dev +pnpm run dev ``` -打开浏览器访问 http://127.0.0.1:3001 +等价于全局 `reactpress dev`:自动检查环境、构建 toolkit、启动 API(3002)与前台(3001)。 + +可选: + +```bash +pnpm run init # 仅准备 .reactpress + .env,不启动服务 +pnpm run dev:api # 仅 API +pnpm run dev:client # 仅前台 +``` + +### 配置说明 + +默认由 `pnpm dev` / `reactpress init` 生成 `.env`。高级场景可编辑 **`.reactpress/config.json`**,再执行 `reactpress config --apply`。详见 [项目配置项](../tutorial-extras/config-intro.md)。 + +打开浏览器访问 `http://127.0.0.1:3001`。 diff --git a/docs/tutorial/tutorial-extras/client-package.md b/docs/tutorial/tutorial-extras/client-package.md index ce30582..ee09c6f 100644 --- a/docs/tutorial/tutorial-extras/client-package.md +++ b/docs/tutorial/tutorial-extras/client-package.md @@ -3,19 +3,34 @@ sidebar_position: 2 title: Client Package 使用指南 --- +:::info 3.0 说明 + +新用户请优先使用 **`reactpress dev`**(全栈,含前台 + 管理端 + API)。本包适用于**仅部署前台**、连接远程 API 的进阶场景。 + +```bash +npm i -g @fecommunity/reactpress@3 +reactpress dev --client-only +``` + +::: + # @fecommunity/reactpress-client 使用指南 -ReactPress Client 是一个基于 Next.js 14 的响应式前端应用程序,作为 ReactPress CMS 平台的用户界面。它提供了现代化的 UI 设计、直观的导航和内容管理功能。 +ReactPress Client 是基于 Next.js 的前端应用,作为 ReactPress CMS 的用户界面。 ## 快速开始 ### 安装和设置 ```bash -# 常规启动 -npx @fecommunity/reactpress-client +# 3.0 推荐(全栈) +reactpress dev -# 生产环境使用 PM2 启动 +# 仅前台(需已有 API) +reactpress dev --client-only + +# 独立包(进阶) +npx @fecommunity/reactpress-client npx @fecommunity/reactpress-client --pm2 ``` diff --git a/docs/tutorial/tutorial-extras/config-intro.md b/docs/tutorial/tutorial-extras/config-intro.md index 51c3e9a..69df880 100644 --- a/docs/tutorial/tutorial-extras/config-intro.md +++ b/docs/tutorial/tutorial-extras/config-intro.md @@ -3,33 +3,66 @@ sidebar_position: 1 title: 项目配置项 --- -项目启动后会默认加载 `.env` 文件,主要支持的配置项如下: +ReactPress 3.0 以 **`.reactpress/config.json`** 为配置源;`.env` 由 CLI 在 `init` / `config --apply` 时自动同步,多数用户无需手写。 + +## `.reactpress/config.json`(推荐) + +典型结构(字段因版本可能略有增减): + +```json +{ + "server": { "port": 3002 }, + "client": { "port": 3001 }, + "database": { + "mode": "embedded-docker", + "host": "127.0.0.1", + "port": 3306, + "user": "reactpress", + "password": "reactpress", + "database": "reactpress" + }, + "urls": { + "client": "http://localhost:3001", + "server": "http://localhost:3002" + } +} +``` + +- `database.mode`:`embedded-docker`(默认)或外部 MySQL 等模式 +- 修改后:`reactpress config --apply` 同步 `.env` 并按需重启 + +查看与修改: ```bash -# 数据库主机 -DB_HOST=127.0.0.1 +reactpress config +reactpress config server.port 3003 --apply +``` -# 数据库端口 -DB_PORT=3306 +## `.env`(自动生成) -# 数据库用户名 -DB_USER=reactpress +项目启动时加载根目录 `.env`,主要变量如下(由 config 同步,一般勿手改): -# 数据库密码 +```bash +# 数据库 +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_USER=reactpress DB_PASSWD=reactpress - -# 数据库名称 DB_DATABASE=reactpress -# 客户端地址 +# 站点 URL CLIENT_SITE_URL=http://localhost:3001 - -# 服务端地址 SERVER_SITE_URL=http://localhost:3002 -# Github OAuth 登录 Id -GITHUB_CLIENT_ID=0 - - # Github OAuth 登录 Secret +# Github OAuth(可选) +GITHUB_CLIENT_ID=0 GITHUB_CLIENT_SECRET=0 -``` \ No newline at end of file +``` + +## 外部 MySQL + +1. 在 `config.json` 中将 `database.mode` 改为外部库对应配置 +2. 执行 `reactpress config --apply` +3. 运行 `reactpress doctor` 确认连接 + +详见 [ReactPress 3.0 平台版](./reactpress-3-0.md)。 diff --git a/docs/tutorial/tutorial-extras/migration-2-to-3.md b/docs/tutorial/tutorial-extras/migration-2-to-3.md new file mode 100644 index 0000000..ccc2600 --- /dev/null +++ b/docs/tutorial/tutorial-extras/migration-2-to-3.md @@ -0,0 +1,72 @@ +--- +sidebar_position: 6 +title: 2.x → 3.0 迁移 +--- + +# ReactPress 2.x → 3.0 迁移指南 + +## 概述 + +3.0「平台版」以 **`@fecommunity/reactpress`** 为唯一推荐入口:全局命令 `reactpress`,内置 API,零配置 `init` + `dev`。独立安装的 `@fecommunity/reactpress-server`、以 `reactpress-cli` 为主路径的用法均已 deprecated。 + +完整 3.0 说明见 [ReactPress 3.0 平台版](./reactpress-3-0.md)。 + +## 命令对照 + +| 2.x | 3.0 | +|-----|-----| +| `npm i -g @fecommunity/reactpress-cli` | `npm i -g @fecommunity/reactpress@3` | +| `reactpress-cli init` | `reactpress init` | +| `reactpress-cli start` | `reactpress dev` 或 `reactpress server start` | +| `npx @fecommunity/reactpress-server` | `reactpress dev --api-only` 或 `reactpress server start` | +| `npx @fecommunity/reactpress-client` | `reactpress dev`(全栈)或 `reactpress dev --client-only` | +| `pnpm dev:server` | `pnpm dev:api` 或 `reactpress dev --api-only` | +| 多包分别安装 | **一个包** `@fecommunity/reactpress` | +| 无环境诊断 | `reactpress doctor` / `reactpress status` | + +> `reactpress-cli` 命令在 3.0 仍可用但会打印 deprecated 警告,3.1 移除。请统一改用 `reactpress`。 + +## 环境变量与配置 + +- 项目元数据以 **`.reactpress/config.json`** 为准;`.env` 由 CLI 同步生成,一般无需手改。 +- `REACTPRESS_ORIGINAL_CWD` 仅由 CLI 入口设置,请勿在业务代码中手动覆盖。 + +## Breaking Changes + +1. **主包名** — `@fecommunity/reactpress-cli` → **`@fecommunity/reactpress@3`**,bin 为 `reactpress`。 +2. **`@fecommunity/reactpress-server` deprecated** — 使用 CLI 内置 API;该包 3.0 保留最后一次兼容发布,3.1 起不再发布。 +3. **Monorepo 根包 `reactpress` 为 private** — 对外只安装 `@fecommunity/reactpress`;本仓贡献者仍可用 `pnpm dev`。 +4. **Swagger / OpenAPI** — 与 toolkit `@3.0.0` 对齐,请升级 SDK。 +5. **新增数据库表** — `api_keys`、`webhooks`、`article_revisions`;`articles.scheduled_publish_at`;首次启动由 TypeORM `synchronize` 创建(生产请先备份)。 + +## Headless 新能力 + +- **健康检查**: `GET /api/health` +- **API Key**: 管理端创建 → `X-API-Key` → `GET /api/article/headless/list` +- **Webhook**: `article.published`、`comment.created`;`X-ReactPress-Signature: sha256=...` + +## 推荐升级步骤 + +```bash +# 1. 备份数据库 +mysqldump -u root -p reactpress > backup.sql + +# 2. 换装主包 +npm uninstall -g @fecommunity/reactpress-cli 2>/dev/null || true +npm i -g @fecommunity/reactpress@3 + +# 3. 同步配置(先备份 .reactpress/) +reactpress init --force # 谨慎:会覆盖 config + +# 4. 诊断 +reactpress doctor + +# 5. 启动验证 +reactpress dev +curl http://localhost:3002/api/health +``` + +## 获取帮助 + +- [ReactPress 3.0 平台版](./reactpress-3-0.md) +- [GitHub Issues](https://github.com/fecommunity/reactpress/issues) diff --git a/docs/tutorial/tutorial-extras/reactpress-2-0.md b/docs/tutorial/tutorial-extras/reactpress-2-0.md index 3c71f7d..7420429 100644 --- a/docs/tutorial/tutorial-extras/reactpress-2-0.md +++ b/docs/tutorial/tutorial-extras/reactpress-2-0.md @@ -3,6 +3,12 @@ sidebar_position: 5 title: ReactPress 2.0 架构 --- +:::info 历史文档 + +**ReactPress 3.0** 已改为单包入口 `@fecommunity/reactpress`,内置 API。新用户请阅读 [ReactPress 3.0 平台版](./reactpress-3-0.md);从 2.x 升级见 [迁移指南](./migration-2-to-3.md)。 + +::: + # ReactPress 2.0 架构详解 ReactPress 2.0 采用了现代化的架构设计,基于 monorepo 结构将项目分解为三个独立但相互协作的核心包。这种设计提供了更好的可维护性、可扩展性和部署灵活性。 diff --git a/docs/tutorial/tutorial-extras/reactpress-3-0.md b/docs/tutorial/tutorial-extras/reactpress-3-0.md new file mode 100644 index 0000000..3bfed3d --- /dev/null +++ b/docs/tutorial/tutorial-extras/reactpress-3-0.md @@ -0,0 +1,129 @@ +--- +sidebar_position: 4 +title: ReactPress 3.0 平台版 +--- + +# ReactPress 3.0 平台版 + +> **装一个包,敲一条命令,一分钟拥有自己的 CMS。** + +3.0「平台版」(代号 **Platform**)围绕三件事交付:**零配置**、**唯一入口**、**极致开发体验**。技术栈仍为 React 17 + Next.js 12 + NestJS 6(Next 14 / React 18 归入后续 **3.1 现代栈版**)。 + +## 三大重点 + +| 重点 | 用户感知 | 3.0 交付 | +|------|----------|----------| +| **零配置** | 不用手写 `.env`、不用先装六个包 | `init` + `dev`,默认嵌入式 Docker MySQL | +| **唯一入口** | 只记一个包名、一个命令 | `npm i -g @fecommunity/reactpress@3` → `reactpress` | +| **极致 DX** | 少查文档、状态一眼可见 | 交互菜单、`doctor`、`status`、dev 成功链接提示 | + +## 一分钟快速开始 + +在**已完成全局安装**后,于空目录执行: + +```bash +npm i -g @fecommunity/reactpress@3 +mkdir my-blog && cd my-blog +reactpress init # 生成 .reactpress、.env、Docker MySQL +reactpress dev # API + 前台 + 管理端 +``` + +| 地址 | 说明 | +|------|------| +| http://localhost:3001 | 前台站点 | +| http://localhost:3001/admin | 管理后台 | +| http://localhost:3002/api | API 根路径 | +| http://localhost:3002/api/health | 健康检查 | +| Swagger | `dev` 成功后在终端查看完整链接 | + +**「1 分钟」**指二次冷启动(`init` + `dev` 合计 ≤ 60s);首次拉取 Docker 镜像可能更久,属正常现象。 + +不想记子命令?直接运行: + +```bash +reactpress +``` + +进入交互式菜单(初始化、开发、状态、Docker、发布等)。 + +## 命令参考 + +| 命令 | 作用 | +|------|------| +| `reactpress` | 交互式菜单 | +| `reactpress init` | 零配置初始化项目 | +| `reactpress dev` | 全栈开发(默认) | +| `reactpress dev --api-only` | 仅 API(Headless) | +| `reactpress dev --client-only` | 仅前台(需已有 API) | +| `reactpress doctor` | 环境诊断(Node、Docker、端口、DB、API) | +| `reactpress status` | 运行状态一页汇总 | +| `reactpress config` | 查看/修改 `.reactpress/config.json` | +| `reactpress start` / `stop` / `restart` | 生产生命周期 | +| `reactpress docker *` | Docker 开发环境 | +| `reactpress db backup` | 数据库备份 | + +## 零配置说明 + +`reactpress init` 自动完成: + +| 产出 | 说明 | +|------|------| +| `.reactpress/config.json` | 端口、数据库模式、站点 URL | +| `.reactpress/docker-compose.yml` | 默认 embedded-docker MySQL | +| `.env` | 由 CLI 从 config 同步,一般无需手改 | +| 数据库 | 等待就绪后自动迁移/同步 | + +仅在需要时改配置,例如外部 MySQL:编辑 `database.mode` 后执行 `reactpress config --apply`。 + +## 包模型(3.0) + +| npm 包 | 角色 | +|--------|------| +| **`@fecommunity/reactpress`** | **唯一对外主包**:CLI + 内置 API + 模板 | +| `@fecommunity/reactpress-client` | 进阶:仅部署前台、连接远程 API | +| `@fecommunity/reactpress-toolkit` | Headless / 自建前台用的 TS SDK | +| `@fecommunity/reactpress-template-*` | `reactpress new --template` 可选 | +| `@fecommunity/reactpress-cli` | **Deprecated**:re-export 主包,会打警告 | +| `@fecommunity/reactpress-server` | **Deprecated**:API 已内置主包 | + +```bash +# ✅ 3.0 推荐 +npm i -g @fecommunity/reactpress@3 + +# ❌ 不再作为新用户主路径 +npm i -g @fecommunity/reactpress-cli +npx @fecommunity/reactpress-server +``` + +## 平台能力(Headless) + +3.0 在统一 CLI 之外延续平台能力,适合进阶与自动化场景: + +- **健康检查**:`GET /api/health` +- **API Key**:管理端创建 → 请求头 `X-API-Key` → `GET /api/article/headless/list` +- **Webhook**:`article.published`、`comment.created`;签名为 `X-ReactPress-Signature: sha256=...` +- **定时发布**、**文章修订历史**与回滚 +- **生产示例**:`docker-compose.prod.yml`;`reactpress db backup` + +自建前台请使用 [@fecommunity/reactpress-toolkit](./toolkit-package) `@3`。 + +## 本仓库贡献者 + +克隆 monorepo 开发时仍使用 `pnpm install` + `pnpm dev`,底层与全局 `reactpress dev` 一致: + +```bash +git clone https://github.com/fecommunity/reactpress.git +cd reactpress +pnpm install +pnpm dev +``` + +## 从 2.x 升级 + +见 [2.x → 3.0 迁移指南](./migration-2-to-3.md)。 + +## 相关文档 + +- [项目配置项](./config-intro.md) — `.env` 与 `.reactpress/config.json` +- [ReactPress 2.0 架构](./reactpress-2-0.md) — 历史架构说明(多包时代) +- [Server / Client 包指南](./server-package.md) — 独立包进阶场景 diff --git a/docs/tutorial/tutorial-extras/server-package.md b/docs/tutorial/tutorial-extras/server-package.md index ae186e0..d266448 100644 --- a/docs/tutorial/tutorial-extras/server-package.md +++ b/docs/tutorial/tutorial-extras/server-package.md @@ -3,19 +3,35 @@ sidebar_position: 3 title: Server Package 使用指南 --- +:::warning Deprecated(3.0) + +`@fecommunity/reactpress-server` 在 3.0 已 **deprecated**。请使用主包: + +```bash +npm i -g @fecommunity/reactpress@3 +reactpress dev # 全栈(内置 API) +reactpress dev --api-only # 仅 API +reactpress start # 生产 +``` + +详见 [ReactPress 3.0 平台版](./reactpress-3-0.md)。下文保留 2.x 多包部署参考。 + +::: + # @fecommunity/reactpress-server 使用指南 -ReactPress Server 是一个基于 NestJS 10 的后端 API,为 ReactPress CMS 平台提供动力。它提供了 RESTful API 用于内容管理、用户认证、媒体处理等功能。通过其简单的安装过程,提供了流畅的设置体验。 +ReactPress Server 是一个基于 NestJS 的后端 API,为 ReactPress CMS 平台提供动力。3.0 起 API 已内置 `@fecommunity/reactpress`,独立 server 包仅作兼容。 -## 快速开始 +## 快速开始(2.x / 兼容) ### 安装和设置 ```bash -# 常规启动 -npx @fecommunity/reactpress-server +# 3.0 推荐 +reactpress dev --api-only -# 生产环境使用 PM2 启动 +# 2.x 兼容(不推荐新用户) +npx @fecommunity/reactpress-server npx @fecommunity/reactpress-server --pm2 ``` diff --git a/package.json b/package.json index 0563d93..3e476da 100644 --- a/package.json +++ b/package.json @@ -1,101 +1,51 @@ { - "name": "@fecommunity/reactpress", - "author": "fecommunity", - "version": "2.0.0-beta-4-beta.1", - "description": "ReactPress - A full-stack CMS built with React, Next.js, and NestJS", - "keywords": [ - "reactpress", - "cms", - "blog", - "react", - "nextjs", - "nestjs", - "typescript" - ], + "name": "reactpress", + "version": "3.0.0", + "private": true, + "description": "ReactPress CMS & Blog site", "bin": { - "reactpress": "./scripts/reactpress-cli.js", - "reactpress-server": "./server/bin/reactpress-server.js", - "reactpress-client": "./client/bin/reactpress-client.js" + "reactpress": "./cli/bin/reactpress.js" }, - "files": [ - "bin/**/*", - "scripts/**/*", - "client/bin/**/*", - "server/bin/**/*", - "CONTRIBUTING.md" - ], "scripts": { - "clean": "pnpm clean:node_modules && pnpm clean:dist", - "clean:node_modules": "npx rimraf ./node_modules ./**/node_modules", - "clean:dist": "npx rimraf ./dist ./**/dist", - "dev": "node ./scripts/reactpress-dev.js", - "dev:server": "pnpm run --dir ./server dev", - "dev:client": "pnpm run --dir ./client dev", + "init": "node ./cli/bin/reactpress.js init", + "init:force": "node ./cli/bin/reactpress.js init --force", + "dev": "node ./cli/bin/reactpress.js dev", + "dev:api": "node ./cli/bin/reactpress.js dev --api-only", + "dev:client": "node ./cli/bin/reactpress.js dev --client-only", + "dev:server": "echo '[deprecated] 请使用 pnpm dev:api 或 reactpress dev --api-only' && pnpm run dev:api", "dev:docs": "pnpm run --dir ./docs dev", - "docker:dev": "node ./scripts/docker-dev.js", - "docker:dev:start": "node ./scripts/docker-dev.js start", - "docker:dev:stop": "node ./scripts/docker-dev.js stop", - "docker:dev:restart": "node ./scripts/docker-dev.js restart", - "docker:dev:status": "node ./scripts/docker-dev.js status", - "docker:dev:logs": "node ./scripts/docker-dev.js logs", - "build": "pnpm build:toolkit && pnpm build:server && pnpm build:client", - "build:packages": "node scripts/reactpress-cli.js --build", + "docker:dev": "node ./cli/bin/reactpress.js docker start", + "docker:dev:start": "node ./cli/bin/reactpress.js docker start", + "docker:dev:stop": "node ./cli/bin/reactpress.js docker down", + "docker:dev:restart": "node ./cli/bin/reactpress.js docker restart", + "docker:dev:status": "node ./cli/bin/reactpress.js docker status", + "docker:dev:logs": "node ./cli/bin/reactpress.js docker logs", + "build": "node ./cli/bin/reactpress.js build", "build:toolkit": "pnpm run --dir ./toolkit build", "build:server": "pnpm run --dir ./server build", "build:client": "pnpm run --dir ./client build", "build:docs": "pnpm run --dir ./docs build", - "deploy:docs": "pnpm run --dir ./docs deploy:surge", - "deploy": "sh scripts/deploy.sh", - "start": "concurrently 'pnpm:start:*'", - "start:server": "pnpm run --dir ./server start", + "start": "node ./cli/bin/reactpress.js server start", + "start:bg": "node ./cli/bin/reactpress.js server start --bg", "start:client": "pnpm run --dir ./client start", - "pm2": "pnpm run pm2:server && pnpm run pm2:client", - "pm2:server": "pnpm run --dir ./server pm2", - "pm2:client": "pnpm run --dir ./client pm2", - "lint": "concurrently 'pnpm:lint:*'", - "lint:client": "eslint --fix './client/**/*.{ts,tsx,js,jsx}'", - "lint:server": "eslint --fix './server/./**/*.{ts,js}'", - "format": "concurrently 'pnpm:format:*'", - "format:js": "prettier --write --parser typescript './**/*.{ts,tsx,js,jsx}'", + "start:all": "node ./cli/bin/reactpress.js start", + "stop": "node ./cli/bin/reactpress.js server stop", + "restart": "node ./cli/bin/reactpress.js server restart", + "status": "node ./cli/bin/reactpress.js status", + "publish:packages": "node ./cli/bin/reactpress.js publish --publish", + "publish:build": "node ./cli/bin/reactpress.js publish --build", "prepare": "husky", "precommit": "lint-staged", - "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", - "release": "node scripts/reactpress-publish.js --publish" + "test:smoke": "node scripts/smoke-api-health.mjs", + "test:benchmark": "node scripts/benchmark-cold-start.mjs" }, "dependencies": { - "chalk": "^4.1.2", - "commander": "^9.4.1", - "concurrently": "^7.0.0", - "conventional-changelog-cli": "^3.0.0", - "cross-env": "^7.0.3", - "dotenv": "^17.2.3", - "express": "^5.1.0", - "fs-extra": "^10.0.0", - "inquirer": "^8.2.4", - "mysql2": "^3.12.0", - "open": "^8.2.1", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=16.5.0" + "@fecommunity/reactpress": "workspace:*" }, "devDependencies": { "husky": "^7.0.4", "lint-staged": "^12.4.1", "prettier": "^2.3.2", "typescript": "~4.1.6" - }, - "lint-staged": { - "*.{ts,tsx,js,jsx,.css,.scss}": "prettier --write", - "./client/**/*.{ts,tsx,js,jsx}": [ - "eslint --fix" - ], - "./server/src/**/*.{ts,js}": [ - "eslint --fix" - ], - "./config/**/*.{ts,tsx,js,jsx}": [ - "eslint --fix" - ] - }, - "packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac" -} \ No newline at end of file + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 869ea12..fddafe5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,25 @@ settings: importers: .: + dependencies: + '@fecommunity/reactpress': + specifier: workspace:* + version: link:cli + devDependencies: + husky: + specifier: ^7.0.4 + version: 7.0.4 + lint-staged: + specifier: ^12.4.1 + version: 12.5.0(enquirer@2.3.6) + prettier: + specifier: ^2.3.2 + version: 2.8.8 + typescript: + specifier: ~4.1.6 + version: 4.1.6 + + cli: dependencies: chalk: specifier: ^4.1.2 @@ -17,46 +36,29 @@ importers: concurrently: specifier: ^7.0.0 version: 7.6.0 - conventional-changelog-cli: - specifier: ^3.0.0 - version: 3.0.0 - cross-env: + cross-spawn: specifier: ^7.0.3 - version: 7.0.3 - dotenv: - specifier: ^17.2.3 - version: 17.2.3 - express: - specifier: ^5.1.0 - version: 5.1.0 + version: 7.0.6 fs-extra: - specifier: ^10.0.0 - version: 10.1.0 + specifier: ^11.2.0 + version: 11.3.0 inquirer: specifier: ^8.2.4 version: 8.2.7(@types/node@24.5.2) - mysql2: - specifier: ^3.12.0 - version: 3.12.0 open: specifier: ^8.2.1 version: 8.4.2 - rimraf: - specifier: ^3.0.2 - version: 3.0.2 + ora: + specifier: ^5.4.1 + version: 5.4.1 devDependencies: - husky: - specifier: ^7.0.4 - version: 7.0.4 - lint-staged: - specifier: ^12.4.1 - version: 12.5.0(enquirer@2.3.6) - prettier: - specifier: ^2.3.2 - version: 2.8.8 - typescript: - specifier: ~4.1.6 - version: 4.1.6 + '@fecommunity/reactpress-cli-core': + specifier: npm:@fecommunity/reactpress-cli@0.1.0 + version: '@fecommunity/reactpress-cli@0.1.0(@types/node@24.5.2)' + optionalDependencies: + mysql2: + specifier: ^3.12.0 + version: 3.22.3(@types/node@24.5.2) client: dependencies: @@ -128,7 +130,7 @@ importers: version: 2.1.35 next: specifier: ^12.3.4 - version: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + version: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) next-compose-plugins: specifier: ^2.2.1 version: 2.2.1 @@ -140,22 +142,25 @@ importers: version: 1.8.5(webpack@5.98.0) next-intl: specifier: ^1.5.1 - version: 1.5.1(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(react@17.0.2) + version: 1.5.1(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(react@17.0.2) next-page-transitions: specifier: ^1.0.0-beta.2 version: 1.0.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2) next-pwa: specifier: ^5.5.2 - version: 5.6.0(@babel/core@7.25.2)(@types/babel__core@7.20.5)(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(webpack@5.98.0) + version: 5.6.0(@babel/core@7.26.9)(@types/babel__core@7.20.5)(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(webpack@5.98.0) next-sitemap: specifier: ^1.6.102 - version: 1.9.12(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)) + version: 1.9.12(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)) next-with-less: specifier: ^2.0.5 - version: 2.0.5(less-loader@10.2.0(less@4.2.0)(webpack@5.98.0))(less@4.2.0)(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)) + version: 2.0.5(less-loader@10.2.0(less@4.2.0)(webpack@5.98.0))(less@4.2.0)(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)) nprogress: specifier: ^0.2.0 version: 0.2.0 + open: + specifier: ^8.4.2 + version: 8.4.2 preact: specifier: ^10.5.14 version: 10.24.0 @@ -179,7 +184,7 @@ importers: version: 2.0.0(prop-types@15.8.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react-spring: specifier: ^9.1.2 - version: 9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react-konva@18.2.10(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react-zdog@1.2.2)(react@17.0.2)(three@0.168.0)(zdog@1.1.3) + version: 9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react-konva@18.2.10(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react-zdog@1.2.2)(react@17.0.2)(three@0.168.0)(zdog@1.1.3) react-text-loop: specifier: 2.3.0 version: 2.3.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -219,7 +224,7 @@ importers: version: 8.11.0 eslint-config-next: specifier: 12.1.0 - version: 12.1.0(eslint@8.11.0)(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(typescript@4.6.2) + version: 12.1.0(eslint@8.11.0)(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(typescript@4.6.2) eslint-config-prettier: specifier: ^8.5.0 version: 8.10.0(eslint@8.11.0) @@ -258,10 +263,10 @@ importers: dependencies: '@docusaurus/core': specifier: 3.7.0 - version: 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + version: 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/preset-classic': specifier: 3.7.0 - version: 3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3) + version: 3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3) '@mdx-js/react': specifier: ^3.0.0 version: 3.1.0(@types/react@17.0.42)(react@19.0.0) @@ -270,7 +275,7 @@ importers: version: 2.1.1 docusaurus-plugin-sass: specifier: ^0.2.5 - version: 0.2.6(@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(sass@1.79.3)(webpack@5.98.0) + version: 0.2.6(@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(sass@1.79.3)(webpack@5.98.0) prism-react-renderer: specifier: ^2.3.0 version: 2.4.1(react@19.0.0) @@ -286,13 +291,13 @@ importers: devDependencies: '@docusaurus/module-type-aliases': specifier: 3.7.0 - version: 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/tsconfig': specifier: 3.7.0 version: 3.7.0 '@docusaurus/types': specifier: 3.7.0 - version: 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) typescript: specifier: ~5.6.2 version: 5.6.3 @@ -326,12 +331,15 @@ importers: '@nestjs/swagger': specifier: ^4.8.2 version: 4.8.2(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(@nestjs/core@6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7))(class-transformer@0.2.3)(class-validator@0.13.2)(reflect-metadata@0.1.14)(swagger-ui-express@4.6.3(express@4.21.2)) + '@nestjs/typeorm': + specifier: ^6.3.4 + version: 6.3.4(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(@nestjs/core@6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7))(reflect-metadata@0.1.14)(rxjs@6.6.7)(typeorm@0.2.45(mysql2@3.22.3(@types/node@12.20.55))) '@types/express-serve-static-core': specifier: ^4.19.5 version: 4.19.5 ali-oss: specifier: ^6.5.1 - version: 6.21.0 + version: 6.23.0 axios: specifier: ^0.23.0 version: 0.23.0 @@ -394,13 +402,13 @@ importers: version: 0.8.2 mysql2: specifier: ^3.12.0 - version: 3.12.0 + version: 3.22.3(@types/node@12.20.55) node-ip2region: specifier: ^1.0.2 version: 1.0.2 nodemailer: specifier: ^6.4.2 - version: 6.9.15 + version: 6.10.1 nuid: specifier: ^1.1.0 version: 1.1.6 @@ -436,7 +444,7 @@ importers: version: 4.6.3(express@4.21.2) typeorm: specifier: ^0.2.45 - version: 0.2.45(mysql2@3.12.0) + version: 0.2.45(mysql2@3.22.3(@types/node@12.20.55)) ua-parser-js: specifier: ^0.7.28 version: 0.7.39 @@ -447,9 +455,6 @@ importers: '@nestjs/testing': specifier: ^6.7.1 version: 6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(@nestjs/core@6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7)) - '@nestjs/typeorm': - specifier: ^6.3.4 - version: 6.3.4(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(@nestjs/core@6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7))(reflect-metadata@0.1.14)(rxjs@6.6.7)(typeorm@0.2.45(mysql2@3.12.0)) '@types/express': specifier: 4.17.18 version: 4.17.18 @@ -524,7 +529,7 @@ importers: version: 10.1.0 next: specifier: ^12.3.4 - version: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + version: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) react: specifier: 17.0.2 version: 17.0.2 @@ -549,7 +554,7 @@ importers: version: link:../../toolkit next: specifier: ^12.3.4 - version: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + version: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) react: specifier: 17.0.2 version: 17.0.2 @@ -804,10 +809,6 @@ packages: peerDependencies: ajv: '>=8' - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -974,10 +975,6 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} @@ -1006,10 +1003,6 @@ packages: resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - '@babel/parser@7.25.6': resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} engines: {node: '>=6.0.0'} @@ -2526,6 +2519,11 @@ packages: '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + '@fecommunity/reactpress-cli@0.1.0': + resolution: {integrity: sha512-pfujpBCamo5BA18II7WtitawmZofR7xs+THgILC2taH3Go4Nslf5zvWgjfBSNu1+YxYVLk6d8k+KFdL0nQ1Abw==} + engines: {node: '>=18'} + hasBin: true + '@formatjs/ecma402-abstract@1.11.4': resolution: {integrity: sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==} @@ -2581,10 +2579,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@hutson/parse-repository-url@3.0.2': - resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} - engines: {node: '>=6.9.0'} - '@inquirer/external-editor@1.0.2': resolution: {integrity: sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==} engines: {node: '>=18'} @@ -3185,6 +3179,9 @@ packages: '@rushstack/eslint-patch@1.10.4': resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + '@schematics/schematics@0.13.8': resolution: {integrity: sha512-7Bqw2DzCbt7EkR0IYDEUXJ6WQjE90NqSMFqK0Lylps0WswJfjUq5axJduz5LwbmrIDhWdDhXMtI4QimUBm4Qsw==} engines: {node: '>= 8.9.0', npm: '>= 5.5.1'} @@ -3348,8 +3345,8 @@ packages: '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} - '@types/anymatch@3.0.0': - resolution: {integrity: sha512-qLChUo6yhpQ9k905NwL74GU7TxH+9UODwwQ6ICNI+O6EDMExqH/Cv9NsbmcZ7yC/rRXJ/AHCzfgjsFRY5fKjYw==} + '@types/anymatch@3.0.4': + resolution: {integrity: sha512-9u8Rl6ILxwTPYXvLjcYZsLnrj2acjmYrmQeqtrA2iR0LN98ctu1fzR02mfWRGXouMK30GtjhPTaC7xye/MK0QQ==} deprecated: This is a stub types definition. anymatch provides its own type definitions, so you do not need this installed. '@types/aria-query@5.0.4': @@ -3475,9 +3472,6 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -3493,9 +3487,6 @@ packages: '@types/node@24.5.2': resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==} - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -3577,8 +3568,8 @@ packages: '@types/swagger-schema-official@2.0.22': resolution: {integrity: sha512-7yQiX6MWSFSvc/1wW5smJMZTZ4fHOd+hqLr3qr/HONDxHEa2bnYAsOcGBOEqFIjd4yetwMOdEDdeW+udRAQnHA==} - '@types/tapable@2.2.7': - resolution: {integrity: sha512-D6QzACV9vNX3r8HQQNTOnpG+Bv1rko+yEA82wKs3O9CQ5+XW7HI7TED17/UE7+5dIxyxZIWTxKbsBeF6uKFCwA==} + '@types/tapable@2.3.0': + resolution: {integrity: sha512-oMnbAXeVo+KUnje3hzdORXUbfnzTfqD0H92mLl19NE5hFqH9Q4ktq+xehNSxcNeeLm1COopYwa0zeP6Iz+oIXg==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -3687,6 +3678,7 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -3793,10 +3785,6 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} deprecated: Use your platform's native atob() and btoa() methods instead @@ -3809,10 +3797,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - acorn-globals@4.3.4: resolution: {integrity: sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==} @@ -3857,9 +3841,6 @@ packages: add-dom-event-listener@1.1.0: resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==} - add-stream@1.0.0: - resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} - address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} @@ -3917,8 +3898,8 @@ packages: resolution: {integrity: sha512-iNC6BGvipaalFfDfDnXUje8GUlW5asj0cTMsZJwO/0rhsyLx1L7GZFAY8wW+eQ6AM4Yge2p5GSE5hrBlfSD90Q==} engines: {node: '>= 14.0.0'} - ali-oss@6.21.0: - resolution: {integrity: sha512-dRvKWO/GJEa6dlsCnvmgHIbU5+yE/SmZsE4kZRGNU7Uotr9uIkQWGqv4szLTxRSxWv3YgL+BZgt+swIgitYGjA==} + ali-oss@6.23.0: + resolution: {integrity: sha512-FipRmyd16Pr/tEey/YaaQ/24Pc3HEpLM9S1DRakEuXlSLXNIJnu1oJtHM53eVYpvW3dXapSjrip3xylZUTIZVQ==} engines: {node: '>=8'} amp-message@0.1.2: @@ -4062,9 +4043,6 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-includes@3.1.8: resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} engines: {node: '>= 0.4'} @@ -4117,10 +4095,6 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} @@ -4314,8 +4288,8 @@ packages: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} engines: {node: '>=10.0.0'} batch@0.6.1: @@ -4352,11 +4326,11 @@ packages: bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + bn.js@4.12.3: + resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + bn.js@5.2.3: + resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} bodec@0.1.0: resolution: {integrity: sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==} @@ -4369,10 +4343,6 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} - engines: {node: '>=18'} - bonjour-service@1.3.0: resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} @@ -4425,12 +4395,13 @@ packages: browserify-des@1.0.2: resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} - browserify-rsa@4.1.0: - resolution: {integrity: sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==} + browserify-rsa@4.1.1: + resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} + engines: {node: '>= 0.10'} - browserify-sign@4.2.3: - resolution: {integrity: sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==} - engines: {node: '>= 0.12'} + browserify-sign@4.2.5: + resolution: {integrity: sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==} + engines: {node: '>= 0.10'} browserify-zlib@0.2.0: resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} @@ -4520,6 +4491,10 @@ packages: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -4546,10 +4521,6 @@ packages: camel-case@4.1.2: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - camelcase@4.1.0: resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} engines: {node: '>=4'} @@ -4673,8 +4644,9 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} - cipher-base@1.0.4: - resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} + cipher-base@1.0.7: + resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} + engines: {node: '>= 0.10'} class-transformer@0.2.3: resolution: {integrity: sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ==} @@ -4718,6 +4690,10 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + cli-highlight@2.1.11: resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} engines: {node: '>=8.0.0', npm: '>=5.0.0'} @@ -4837,6 +4813,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + commander@2.15.1: resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} @@ -4873,9 +4853,6 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - component-classes@1.2.6: resolution: {integrity: sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==} @@ -4948,10 +4925,6 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - content-disposition@1.0.0: - resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} - engines: {node: '>= 0.6'} - content-security-policy-builder@2.1.0: resolution: {integrity: sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==} engines: {node: '>=4.0.0'} @@ -4960,73 +4933,6 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} - conventional-changelog-angular@6.0.0: - resolution: {integrity: sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg==} - engines: {node: '>=14'} - - conventional-changelog-atom@3.0.0: - resolution: {integrity: sha512-pnN5bWpH+iTUWU3FaYdw5lJmfWeqSyrUkG+wyHBI9tC1dLNnHkbAOg1SzTQ7zBqiFrfo55h40VsGXWMdopwc5g==} - engines: {node: '>=14'} - - conventional-changelog-cli@3.0.0: - resolution: {integrity: sha512-3zMYi0IrfNd6AAHdPMrcgCg5DbcffiqNaEBf8cYrlntXPbBIXaELTbnRmUy5TQAe0Hkgi0J6+/VmRCkkJQflcQ==} - engines: {node: '>=14'} - hasBin: true - - conventional-changelog-codemirror@3.0.0: - resolution: {integrity: sha512-wzchZt9HEaAZrenZAUUHMCFcuYzGoZ1wG/kTRMICxsnW5AXohYMRxnyecP9ob42Gvn5TilhC0q66AtTPRSNMfw==} - engines: {node: '>=14'} - - conventional-changelog-conventionalcommits@6.1.0: - resolution: {integrity: sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==} - engines: {node: '>=14'} - - conventional-changelog-core@5.0.2: - resolution: {integrity: sha512-RhQOcDweXNWvlRwUDCpaqXzbZemKPKncCWZG50Alth72WITVd6nhVk9MJ6w1k9PFNBcZ3YwkdkChE+8+ZwtUug==} - engines: {node: '>=14'} - - conventional-changelog-ember@3.0.0: - resolution: {integrity: sha512-7PYthCoSxIS98vWhVcSphMYM322OxptpKAuHYdVspryI0ooLDehRXWeRWgN+zWSBXKl/pwdgAg8IpLNSM1/61A==} - engines: {node: '>=14'} - - conventional-changelog-eslint@4.0.0: - resolution: {integrity: sha512-nEZ9byP89hIU0dMx37JXQkE1IpMmqKtsaR24X7aM3L6Yy/uAtbb+ogqthuNYJkeO1HyvK7JsX84z8649hvp43Q==} - engines: {node: '>=14'} - - conventional-changelog-express@3.0.0: - resolution: {integrity: sha512-HqxihpUMfIuxvlPvC6HltA4ZktQEUan/v3XQ77+/zbu8No/fqK3rxSZaYeHYant7zRxQNIIli7S+qLS9tX9zQA==} - engines: {node: '>=14'} - - conventional-changelog-jquery@4.0.0: - resolution: {integrity: sha512-TTIN5CyzRMf8PUwyy4IOLmLV2DFmPtasKN+x7EQKzwSX8086XYwo+NeaeA3VUT8bvKaIy5z/JoWUvi7huUOgaw==} - engines: {node: '>=14'} - - conventional-changelog-jshint@3.0.0: - resolution: {integrity: sha512-bQof4byF4q+n+dwFRkJ/jGf9dCNUv4/kCDcjeCizBvfF81TeimPZBB6fT4HYbXgxxfxWXNl/i+J6T0nI4by6DA==} - engines: {node: '>=14'} - - conventional-changelog-preset-loader@3.0.0: - resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==} - engines: {node: '>=14'} - - conventional-changelog-writer@6.0.1: - resolution: {integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==} - engines: {node: '>=14'} - hasBin: true - - conventional-changelog@4.0.0: - resolution: {integrity: sha512-JbZjwE1PzxQCvm+HUTIr+pbSekS8qdOZzMakdFyPtdkEWwFvwEJYONzjgMm0txCb2yBcIcfKDmg8xtCKTdecNQ==} - engines: {node: '>=14'} - - conventional-commits-filter@3.0.0: - resolution: {integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==} - engines: {node: '>=14'} - - conventional-commits-parser@4.0.0: - resolution: {integrity: sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==} - engines: {node: '>=14'} - hasBin: true - convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -5036,10 +4942,6 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - cookie@0.4.0: resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==} engines: {node: '>= 0.6'} @@ -5166,16 +5068,13 @@ packages: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-browserify@3.12.0: - resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} + crypto-browserify@3.12.1: + resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} + engines: {node: '>= 0.10'} crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} @@ -5331,10 +5230,6 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dargs@7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} - dashdash@1.14.1: resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} engines: {node: '>=0.10'} @@ -5376,9 +5271,6 @@ packages: dateformat@2.2.0: resolution: {integrity: sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==} - dateformat@3.0.3: - resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} - dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -5430,10 +5322,6 @@ packages: supports-color: optional: true - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -5579,8 +5467,8 @@ packages: resolution: {integrity: sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==} engines: {node: '>= 6'} - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} diffie-hellman@5.0.3: @@ -5665,10 +5553,6 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -5676,6 +5560,10 @@ packages: dotenv-expand@5.1.0: resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -5736,8 +5624,11 @@ packages: electron-to-chromium@1.5.27: resolution: {integrity: sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==} - elliptic@6.5.7: - resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} + elliptic@6.6.1: + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@7.0.3: resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} @@ -6216,10 +6107,6 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} - engines: {node: '>= 18'} - ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} @@ -6367,10 +6254,6 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} - find-cache-dir@2.1.0: resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} engines: {node: '>=6'} @@ -6383,10 +6266,6 @@ packages: resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} engines: {node: '>=14.16'} - find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} - find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} @@ -6444,6 +6323,10 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + for-in@1.0.2: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} engines: {node: '>=0.10.0'} @@ -6487,14 +6370,6 @@ packages: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} - form-data@2.5.1: - resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} - engines: {node: '>= 0.12'} - - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - form-data@4.0.4: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} @@ -6511,8 +6386,8 @@ packages: resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==} deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau' - formstream@1.5.1: - resolution: {integrity: sha512-q7ORzFqotpwn3Y/GBK2lK7PjtZZwJHz9QE9Phv8zb5IrL9ftGLyi2zjGURON3voK8TaZ+mqJKERYN4lrHYTkUQ==} + formstream@1.5.2: + resolution: {integrity: sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==} forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} @@ -6529,10 +6404,6 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -6597,6 +6468,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -6608,11 +6483,6 @@ packages: get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - get-pkg-repo@4.2.1: - resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} - engines: {node: '>=6.9.0'} - hasBin: true - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -6651,26 +6521,9 @@ packages: js-git: optional: true - git-raw-commits@3.0.0: - resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==} - engines: {node: '>=14'} - hasBin: true - - git-remote-origin-url@2.0.0: - resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} - engines: {node: '>=4'} - - git-semver-tags@5.0.1: - resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==} - engines: {node: '>=14'} - hasBin: true - git-sha1@0.1.2: resolution: {integrity: sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==} - gitconfiglocal@1.0.0: - resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} - github-buttons@2.29.1: resolution: {integrity: sha512-TV3YgAKda5hPz75n7QXmGCsSzgVya1vvmBieebg3EB5ScmashTZ0FldViG1aU2d4V5rcAGrtQ7k5uAaCo0A4PA==} @@ -6693,11 +6546,11 @@ packages: glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -6773,11 +6626,6 @@ packages: handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - har-schema@2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} engines: {node: '>=4'} @@ -6787,10 +6635,6 @@ packages: engines: {node: '>=6'} deprecated: this library is no longer supported - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -6845,13 +6689,13 @@ packages: resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hash-base@3.0.4: - resolution: {integrity: sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==} - engines: {node: '>=4'} + hash-base@3.0.5: + resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} + engines: {node: '>= 0.10'} - hash-base@3.1.0: - resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} - engines: {node: '>=4'} + hash-base@3.1.2: + resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==} + engines: {node: '>= 0.8'} hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} @@ -6935,10 +6779,6 @@ packages: hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - hpack.js@2.1.6: resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} @@ -7073,8 +6913,8 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.0: - resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} icss-utils@5.1.0: @@ -7194,8 +7034,8 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - ip-address@10.0.1: - resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -7354,6 +7194,10 @@ packages: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -7405,10 +7249,6 @@ packages: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-plain-obj@3.0.0: resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} engines: {node: '>=10'} @@ -7424,9 +7264,6 @@ packages: is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} @@ -7466,10 +7303,6 @@ packages: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} - is-text-path@1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} - engines: {node: '>=0.10.0'} - is-type-of@1.4.0: resolution: {integrity: sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==} @@ -7477,6 +7310,10 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -7484,6 +7321,14 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -7839,10 +7684,6 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - jsonpointer@5.0.1: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} @@ -7851,8 +7692,8 @@ packages: resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} engines: {node: '>=4', npm: '>=1.4.28'} - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} jsprim@1.4.2: @@ -7866,11 +7707,17 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@3.2.3: + resolution: {integrity: sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==} - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -7948,8 +7795,8 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libphonenumber-js@1.11.8: - resolution: {integrity: sha512-0fv/YKpJBAgXKy0kaS3fnqoUVN8901vUYAKIGD/MWZaDfhJt1nZjPL3ZzdZBt/G8G8Hw2J1xOIrXWdNHFHPAvg==} + libphonenumber-js@1.13.2: + resolution: {integrity: sha512-S3kmBrptp3yRTm83NUcHy9g1vbwiWMzI8WvY22+koBJ6zkRteLnedBL2VX0MIAGwx2yiyxX4J85pceZyQ6ffgg==} lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} @@ -8003,10 +7850,6 @@ packages: resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} engines: {node: '>= 12.13.0'} - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} - locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -8048,9 +7891,6 @@ packages: lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - lodash.ismatch@4.4.0: - resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} - lodash.isnumber@3.0.3: resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} @@ -8098,6 +7938,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + log-update@4.0.0: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} engines: {node: '>=10'} @@ -8110,8 +7954,8 @@ packages: resolution: {integrity: sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==} hasBin: true - long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -8141,8 +7985,8 @@ packages: lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - lru.min@1.1.1: - resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} lz-string@1.5.0: @@ -8177,14 +8021,6 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - map-visit@1.0.0: resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} engines: {node: '>=0.10.0'} @@ -8278,10 +8114,6 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - memfs@3.5.3: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} @@ -8300,20 +8132,12 @@ packages: resolution: {integrity: sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==} engines: {node: '>=4.3.0 <5.0.0 || >=5.10'} - meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} - merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -8529,10 +8353,6 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - mime-db@1.53.0: - resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} - engines: {node: '>= 0.6'} - mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} @@ -8545,10 +8365,6 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime-types@3.0.1: - resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} - engines: {node: '>= 0.6'} - mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -8567,6 +8383,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -8598,10 +8418,6 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.0: resolution: {integrity: sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==} @@ -8625,10 +8441,6 @@ packages: engines: {node: '>=10'} hasBin: true - modify-values@1.0.1: - resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} - engines: {node: '>=0.10.0'} - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -8667,16 +8479,18 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - mysql2@3.12.0: - resolution: {integrity: sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==} + mysql2@3.22.3: + resolution: {integrity: sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==} engines: {node: '>= 8.0'} + peerDependencies: + '@types/node': '>= 8' mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} nan@2.20.0: resolution: {integrity: sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==} @@ -8715,15 +8529,11 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} engines: {node: '>= 0.4.0'} next-compose-plugins@2.2.1: @@ -8885,8 +8695,8 @@ packages: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} engines: {node: '>=0.12.0'} - nodemailer@6.9.15: - resolution: {integrity: sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==} + nodemailer@6.10.1: + resolution: {integrity: sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==} engines: {node: '>=6.0.0'} noms@0.0.0: @@ -8895,10 +8705,6 @@ packages: normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - normalize-path@2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} @@ -8936,6 +8742,7 @@ packages: nuid@1.1.6: resolution: {integrity: sha512-Eb3CPCupYscP1/S1FQcO5nxtu6l/F3k0MQ69h7f5osnsemVk5pkc8/5AyalVT+NCfra9M71U8POqF6EZa6IHvg==} engines: {node: '>= 8.16.0'} + deprecated: 'Package deprecated. Use @nats-io/nuid instead: https://www.npmjs.com/package/@nats-io/nuid' null-loader@4.0.1: resolution: {integrity: sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==} @@ -9064,6 +8871,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + open@6.4.0: resolution: {integrity: sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==} engines: {node: '>=8'} @@ -9099,6 +8910,10 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} @@ -9132,10 +8947,6 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -9148,10 +8959,6 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} - p-locate@3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} @@ -9184,10 +8991,6 @@ packages: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -9220,8 +9023,8 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-asn1@5.1.7: - resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==} + parse-asn1@5.1.9: + resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==} engines: {node: '>= 0.10'} parse-entities@4.0.2: @@ -9336,9 +9139,6 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -9353,9 +9153,9 @@ packages: pause@0.0.1: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} - pbkdf2@3.1.2: - resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} - engines: {node: '>=0.12'} + pbkdf2@3.1.5: + resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==} + engines: {node: '>= 0.10'} performance-now@0.2.0: resolution: {integrity: sha512-YHk5ez1hmMR5LOkb9iJkLKqoBlL7WD5M8ljC75ZfzXriuBIVNuecaXuU7e+hOwyqf24Wxhh7Vxgt7Hnw9288Tg==} @@ -10031,10 +9831,6 @@ packages: queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -10064,10 +9860,6 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} - raw-body@3.0.1: - resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} - engines: {node: '>= 0.10'} - rc-animate@2.11.1: resolution: {integrity: sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==} peerDependencies: @@ -10517,26 +10309,14 @@ packages: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} - read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} - engines: {node: '>=4'} - read-pkg-up@4.0.0: resolution: {integrity: sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==} engines: {node: '>=6'} - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - read@1.0.7: resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} engines: {node: '>=0.8'} @@ -10812,6 +10592,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + ret@0.1.15: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} @@ -10847,8 +10631,9 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - ripemd160@2.0.2: - resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + ripemd160@2.0.3: + resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} + engines: {node: '>= 0.8'} rollup-plugin-terser@7.0.2: resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} @@ -10861,10 +10646,6 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - rsvp@4.8.5: resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} engines: {node: 6.* || >= 7.*} @@ -11046,13 +10827,6 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - send@1.2.0: - resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} - engines: {node: '>= 18'} - - seq-queue@0.0.5: - resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - serialize-error@2.1.0: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} @@ -11078,10 +10852,6 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} - serve-static@2.2.0: - resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} - engines: {node: '>= 18'} - set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -11109,8 +10879,9 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} hasBin: true shallow-clone@3.0.1: @@ -11195,7 +10966,11 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - sirv@2.0.4: + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -11268,8 +11043,8 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} sort-css-media-queries@2.2.0: @@ -11348,21 +11123,15 @@ packages: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} - split2@3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} - - split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} sprintf-js@1.1.2: resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} - sqlstring@2.3.3: - resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} - engines: {node: '>= 0.6'} + sql-escaper@1.3.3: + resolution: {integrity: sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==} + engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'} srcset@4.0.0: resolution: {integrity: sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==} @@ -11409,6 +11178,10 @@ packages: std-env@3.8.1: resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + stealthy-require@1.1.1: resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} engines: {node: '>=0.10.0'} @@ -11471,6 +11244,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + string.prototype.includes@2.0.0: resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} @@ -11656,8 +11433,8 @@ packages: resolution: {integrity: sha512-04ZxlJzu3g15TupfPhS0Yk0jzV/MM23WU4uuOl2vSi4yHrxEwnkIsoBkP084ec61q4vr2FHcI3DKxC+Mt1u10Q==} hasBin: true - swagger-ui-dist@5.17.14: - resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + swagger-ui-dist@5.32.6: + resolution: {integrity: sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==} swagger-ui-express@4.6.3: resolution: {integrity: sha512-CDje4PndhTD2HkgyKH3pab+LKspDeB/NhPN2OF1j+piYIamQqBYwAXWESOT1Yju2xFg51bRW9sUng2WxDjzArw==} @@ -11681,8 +11458,8 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - systeminformation@5.27.10: - resolution: {integrity: sha512-jkeOerLSwLZqJrPHCYltlKHu0PisdepIuS4GwjFFtgQUG/5AQPVZekkECuULqdP0cgrrIHW8Nl8J7WQXo5ypEg==} + systeminformation@5.31.6: + resolution: {integrity: sha512-Uv2b2uGGM6ns+26czgW2cYRabYdnswM0ddSOOlryHOaelzsmDSet1iM/NT7VOYxW8x/BW+HkY+b1Ve2pLTSGSA==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -11695,6 +11472,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -11703,10 +11484,6 @@ packages: resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} engines: {node: '>=6.0.0'} - tempfile@3.0.0: - resolution: {integrity: sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw==} - engines: {node: '>=8'} - tempy@0.6.0: resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} engines: {node: '>=10'} @@ -11763,10 +11540,6 @@ packages: resolution: {integrity: sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==} engines: {node: '>=6'} - text-extensions@1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -11823,6 +11596,10 @@ packages: to-arraybuffer@1.0.1: resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + to-object-path@0.3.0: resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} engines: {node: '>=0.10.0'} @@ -11871,10 +11648,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -11975,10 +11748,6 @@ packages: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -11987,18 +11756,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - type-fest@0.7.1: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} @@ -12011,10 +11772,6 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -12022,6 +11779,10 @@ packages: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.1: resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} @@ -12120,11 +11881,6 @@ packages: resolution: {integrity: sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w==} hasBin: true - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -12309,19 +12065,22 @@ packages: uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@7.0.1: resolution: {integrity: sha512-yqjRXZzSJm9Dbl84H2VDHpM3zMjzSJQ+hn6C4zqd5ilW+7P4ZmLEEqwho9LjP+tGuZlF4xrHQXT0h9QZUS/pWA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache@2.4.0: @@ -12330,8 +12089,8 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + validator@13.15.35: + resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} value-equal@1.0.1: @@ -12487,6 +12246,7 @@ packages: whatwg-encoding@1.0.5: resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} @@ -12521,6 +12281,10 @@ packages: resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -12549,9 +12313,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - workbox-background-sync@6.6.0: resolution: {integrity: sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==} @@ -13125,11 +12886,6 @@ snapshots: jsonpointer: 5.0.1 leven: 3.1.0 - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.1 - '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -13143,7 +12899,7 @@ snapshots: '@babel/core@7.25.2': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.25.6 '@babel/helper-compilation-targets': 7.25.2 '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) @@ -13239,19 +12995,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-member-expression-to-functions': 7.25.9 - '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.25.2) - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.9 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13279,13 +13022,6 @@ snapshots: regexpu-core: 5.3.2 semver: 6.3.1 - '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.25.9 - regexpu-core: 6.2.0 - semver: 6.3.1 - '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13315,17 +13051,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-plugin-utils': 7.26.5 - debug: 4.4.3 - lodash.debounce: 4.0.8 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13375,15 +13100,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13414,15 +13130,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13441,15 +13148,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.26.5(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-member-expression-to-functions': 7.25.9 - '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13482,8 +13180,6 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-option@7.24.8': {} @@ -13516,13 +13212,6 @@ snapshots: '@babel/template': 7.26.9 '@babel/types': 7.28.4 - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/parser@7.25.6': dependencies: '@babel/types': 7.28.4 @@ -13539,14 +13228,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13560,11 +13241,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13575,11 +13251,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13594,15 +13265,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.25.2) - transitivePeerDependencies: - - supports-color - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13620,14 +13282,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13644,11 +13298,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-export-default-from@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-proposal-export-default-from@7.24.7(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.26.9) '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.26.9)': dependencies: @@ -13698,9 +13352,9 @@ snapshots: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-export-default-from@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-syntax-export-default-from@7.24.7(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.25.2)': @@ -13708,11 +13362,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-flow@7.24.7(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13723,11 +13372,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13738,11 +13382,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13833,11 +13472,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13860,11 +13494,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13880,15 +13509,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.25.2) - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13907,15 +13527,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.25.2) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13930,11 +13541,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13945,11 +13551,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13963,14 +13564,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -13988,14 +13581,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14016,18 +13601,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.25.2) - '@babel/traverse': 7.26.9 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14046,12 +13619,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/template': 7.25.0 - '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/template': 7.26.9 - '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14063,11 +13630,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14079,12 +13641,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14096,11 +13652,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14112,12 +13663,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14130,11 +13675,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14148,11 +13688,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14164,22 +13699,11 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-flow-strip-types@7.25.2(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-flow-strip-types@7.25.2(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14194,14 +13718,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14219,15 +13735,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14243,11 +13750,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14258,11 +13760,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-literals@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14274,11 +13771,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14289,11 +13781,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14307,14 +13794,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14332,14 +13811,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14358,16 +13829,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14386,14 +13847,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14408,12 +13861,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14425,11 +13872,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14441,11 +13883,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14457,11 +13894,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2) - '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14475,13 +13907,6 @@ snapshots: '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2) '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14497,14 +13922,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-replace-supers': 7.26.5(@babel/core@7.25.2) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14519,11 +13936,6 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14538,14 +13950,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14559,11 +13963,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14577,14 +13976,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14603,15 +13994,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14626,11 +14008,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14665,14 +14042,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.26.9)': dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.25.2)': @@ -14715,24 +14092,12 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 regenerator-transform: 0.15.2 - '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - regenerator-transform: 0.15.2 - '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 regenerator-transform: 0.15.2 - '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14744,28 +14109,11 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-runtime@7.26.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-plugin-utils': 7.26.5 - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.2) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.2) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-runtime@7.26.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14783,11 +14131,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14801,14 +14144,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-spread@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14817,15 +14152,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - - '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.25.2)': + '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.9)': dependencies: @@ -14837,11 +14167,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14852,11 +14177,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14873,17 +14193,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.25.2) - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-typescript@7.26.8(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14900,11 +14209,6 @@ snapshots: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14916,12 +14220,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14934,12 +14232,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14952,12 +14244,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.25.2(@babel/core@7.25.2) '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.25.2) - '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -15053,81 +14339,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-env@7.26.9(@babel/core@7.25.2)': - dependencies: - '@babel/compat-data': 7.26.8 - '@babel/core': 7.25.2 - '@babel/helper-compilation-targets': 7.26.5 - '@babel/helper-plugin-utils': 7.26.5 - '@babel/helper-validator-option': 7.25.9 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.2) - '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.25.2) - '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.25.2) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.2) - '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.25.2) - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.25.2) - '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.25.2) - '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.25.2) - '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.25.2) - '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.25.2) - '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.25.2) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.25.2) - '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.25.2) - '@babel/plugin-transform-typeof-symbol': 7.26.7(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.25.2) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.2) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.2) - babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.25.2) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.2) - core-js-compat: 3.41.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/preset-env@7.26.9(@babel/core@7.26.9)': dependencies: '@babel/compat-data': 7.26.8 @@ -15617,7 +14828,7 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/babel@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/babel@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@babel/core': 7.26.9 '@babel/generator': 7.26.9 @@ -15630,7 +14841,7 @@ snapshots: '@babel/runtime-corejs3': 7.26.9 '@babel/traverse': 7.26.9 '@docusaurus/logger': 3.7.0 - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) babel-plugin-dynamic-import-node: 2.3.3 fs-extra: 11.3.0 tslib: 2.7.0 @@ -15644,14 +14855,14 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/bundler@3.7.0(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/bundler@3.7.0(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: '@babel/core': 7.26.9 - '@docusaurus/babel': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/babel': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/cssnano-preset': 3.7.0 '@docusaurus/logger': 3.7.0 - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) clean-css: 5.3.3 copy-webpack-plugin: 11.0.0(webpack@5.98.0) @@ -15689,15 +14900,15 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/babel': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/bundler': 3.7.0(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/babel': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/bundler': 3.7.0(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/mdx-loader': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/mdx-loader': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mdx-js/react': 3.1.0(@types/react@17.0.42)(react@19.0.0) boxen: 6.2.1 chalk: 4.1.2 @@ -15768,12 +14979,12 @@ snapshots: chalk: 4.1.2 tslib: 2.7.0 - '@docusaurus/mdx-loader@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/mdx-loader@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@docusaurus/logger': 3.7.0 - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@mdx-js/mdx': 3.1.0(acorn@8.14.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mdx-js/mdx': 3.1.0(acorn@8.15.0) '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 estree-util-value-to-estree: 3.3.2 @@ -15804,9 +15015,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/module-type-aliases@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/history': 4.7.11 '@types/react': 17.0.42 '@types/react-router-config': 5.0.11 @@ -15823,17 +15034,17 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-content-blog@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/mdx-loader': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/mdx-loader': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.3.0 @@ -15867,17 +15078,17 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/mdx-loader': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/module-type-aliases': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/mdx-loader': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.3.0 @@ -15909,13 +15120,13 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-pages@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-content-pages@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/mdx-loader': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/mdx-loader': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.3.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -15942,11 +15153,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-debug@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-debug@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.3.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -15973,11 +15184,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-analytics@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-google-analytics@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) tslib: 2.7.0 @@ -16002,11 +15213,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-gtag@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-google-gtag@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/gtag.js': 0.0.12 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -16032,11 +15243,11 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-google-tag-manager@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) tslib: 2.7.0 @@ -16061,14 +15272,14 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-sitemap@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-sitemap@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.3.0 react: 19.0.0 react-dom: 19.0.0(react@19.0.0) @@ -16095,12 +15306,12 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-svgr@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/plugin-svgr@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@svgr/core': 8.1.0(typescript@5.6.3) '@svgr/webpack': 8.1.0(typescript@5.6.3) react: 19.0.0 @@ -16128,22 +15339,22 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/preset-classic@3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3)': - dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-content-pages': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-debug': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-google-analytics': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-google-gtag': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-google-tag-manager': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-sitemap': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-svgr': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/theme-classic': 3.7.0(@types/react@17.0.42)(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/theme-search-algolia': 3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3) - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/preset-classic@3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3)': + dependencies: + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-content-pages': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-debug': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-google-analytics': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-google-gtag': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-google-tag-manager': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-sitemap': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-svgr': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/theme-classic': 3.7.0(@types/react@17.0.42)(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-search-algolia': 3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) transitivePeerDependencies: @@ -16175,21 +15386,21 @@ snapshots: '@types/react': 17.0.42 react: 19.0.0 - '@docusaurus/theme-classic@3.7.0(@types/react@17.0.42)(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': + '@docusaurus/theme-classic@3.7.0(@types/react@17.0.42)(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3)': dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/mdx-loader': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/module-type-aliases': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/plugin-content-pages': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/mdx-loader': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/plugin-content-pages': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/theme-translations': 3.7.0 - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@mdx-js/react': 3.1.0(@types/react@17.0.42)(react@19.0.0) clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 @@ -16226,13 +15437,13 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@docusaurus/mdx-loader': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/module-type-aliases': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/mdx-loader': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/history': 4.7.11 '@types/react': 17.0.42 '@types/react-router-config': 5.0.11 @@ -16251,16 +15462,16 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/theme-search-algolia@3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3)': + '@docusaurus/theme-search-algolia@3.7.0(@algolia/client-search@5.20.3)(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(@types/react@17.0.42)(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.6.3)': dependencies: '@docsearch/react': 3.9.0(@algolia/client-search@5.20.3)(@types/react@17.0.42)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3) - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) '@docusaurus/logger': 3.7.0 - '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) - '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/theme-translations': 3.7.0 - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-validation': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) algoliasearch: 5.20.3 algoliasearch-helper: 3.24.1(algoliasearch@5.20.3) clsx: 2.1.1 @@ -16302,9 +15513,9 @@ snapshots: '@docusaurus/tsconfig@3.7.0': {} - '@docusaurus/types@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/types@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@mdx-js/mdx': 3.1.0(acorn@8.14.0) + '@mdx-js/mdx': 3.1.0(acorn@8.15.0) '@types/history': 4.7.11 '@types/react': 17.0.42 commander: 5.1.0 @@ -16323,9 +15534,9 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-common@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/utils-common@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) tslib: 2.7.0 transitivePeerDependencies: - '@swc/core' @@ -16337,11 +15548,11 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils-validation@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/utils-validation@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@docusaurus/logger': 3.7.0 - '@docusaurus/utils': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.3.0 joi: 17.13.3 js-yaml: 4.1.0 @@ -16357,11 +15568,11 @@ snapshots: - uglify-js - webpack-cli - '@docusaurus/utils@3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@docusaurus/utils@3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@docusaurus/logger': 3.7.0 - '@docusaurus/types': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@docusaurus/utils-common': 3.7.0(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(acorn@8.15.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) escape-string-regexp: 4.0.0 file-loader: 6.2.0(webpack@5.98.0) fs-extra: 11.3.0 @@ -16403,6 +15614,11 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@1.21.7))': dependencies: eslint: 9.36.0(jiti@1.21.7) @@ -16481,6 +15697,18 @@ snapshots: '@exodus/schemasafe@1.3.0': {} + '@fecommunity/reactpress-cli@0.1.0(@types/node@24.5.2)': + dependencies: + chalk: 5.4.1 + commander: 13.1.0 + cross-spawn: 7.0.6 + dotenv: 16.6.1 + fs-extra: 11.3.0 + mysql2: 3.22.3(@types/node@24.5.2) + ora: 8.2.0 + transitivePeerDependencies: + - '@types/node' + '@formatjs/ecma402-abstract@1.11.4': dependencies: '@formatjs/intl-localematcher': 0.2.25 @@ -16542,19 +15770,17 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@hutson/parse-repository-url@3.0.2': {} - '@inquirer/external-editor@1.0.2(@types/node@12.20.55)': dependencies: chardet: 2.1.0 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 optionalDependencies: '@types/node': 12.20.55 '@inquirer/external-editor@1.0.2(@types/node@24.5.2)': dependencies: chardet: 2.1.0 - iconv-lite: 0.7.0 + iconv-lite: 0.7.2 optionalDependencies: '@types/node': 24.5.2 @@ -16662,9 +15888,7 @@ snapshots: source-map: 0.6.1 string-length: 2.0.0 transitivePeerDependencies: - - bufferutil - supports-color - - utf-8-validate '@jest/schemas@29.6.3': dependencies: @@ -16689,9 +15913,7 @@ snapshots: jest-runner: 24.9.0 jest-runtime: 24.9.0 transitivePeerDependencies: - - bufferutil - supports-color - - utf-8-validate '@jest/transform@24.9.0': dependencies: @@ -16761,7 +15983,7 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@mdx-js/mdx@3.1.0(acorn@8.14.0)': + '@mdx-js/mdx@3.1.0(acorn@8.15.0)': dependencies: '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 @@ -16775,7 +15997,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.5 markdown-extensions: 2.0.0 recma-build-jsx: 1.0.0 - recma-jsx: 1.0.0(acorn@8.14.0) + recma-jsx: 1.0.0(acorn@8.15.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 remark-mdx: 3.1.0 @@ -16946,13 +16168,13 @@ snapshots: '@nestjs/core': 6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7) tslib: 1.11.1 - '@nestjs/typeorm@6.3.4(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(@nestjs/core@6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7))(reflect-metadata@0.1.14)(rxjs@6.6.7)(typeorm@0.2.45(mysql2@3.12.0))': + '@nestjs/typeorm@6.3.4(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(@nestjs/core@6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7))(reflect-metadata@0.1.14)(rxjs@6.6.7)(typeorm@0.2.45(mysql2@3.22.3(@types/node@12.20.55)))': dependencies: '@nestjs/common': 6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7) '@nestjs/core': 6.11.11(@nestjs/common@6.11.11(reflect-metadata@0.1.14)(rxjs@6.6.7))(encoding@0.1.13)(reflect-metadata@0.1.14)(rxjs@6.6.7) reflect-metadata: 0.1.14 rxjs: 6.6.7 - typeorm: 0.2.45(mysql2@3.12.0) + typeorm: 0.2.45(mysql2@3.22.3(@types/node@12.20.55)) uuid: 7.0.3 '@next/env@12.3.4': {} @@ -17280,84 +16502,84 @@ snapshots: '@react-native/assets-registry@0.75.3': {} - '@react-native/babel-plugin-codegen@0.75.3(@babel/preset-env@7.26.9(@babel/core@7.25.2))': + '@react-native/babel-plugin-codegen@0.75.3(@babel/preset-env@7.26.9(@babel/core@7.26.9))': dependencies: - '@react-native/codegen': 0.75.3(@babel/preset-env@7.26.9(@babel/core@7.25.2)) + '@react-native/codegen': 0.75.3(@babel/preset-env@7.26.9(@babel/core@7.26.9)) transitivePeerDependencies: - '@babel/preset-env' - supports-color - '@react-native/babel-preset@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))': + '@react-native/babel-preset@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))': dependencies: - '@babel/core': 7.25.2 - '@babel/plugin-proposal-export-default-from': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2) - '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.25.2) - '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.25.2) - '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.25.2) - '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.25.2) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.25.2) - '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-runtime': 7.26.9(@babel/core@7.25.2) - '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.25.2) - '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.25.2) - '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.25.2) + '@babel/core': 7.26.9 + '@babel/plugin-proposal-export-default-from': 7.24.7(@babel/core@7.26.9) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-export-default-from': 7.24.7(@babel/core@7.26.9) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.9) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.9) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.9) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-flow-strip-types': 7.25.2(@babel/core@7.26.9) + '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.26.9) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.9) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.9) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.26.9) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.26.9) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-runtime': 7.26.9(@babel/core@7.26.9) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.9) + '@babel/plugin-transform-typescript': 7.26.8(@babel/core@7.26.9) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.9) '@babel/template': 7.26.9 - '@react-native/babel-plugin-codegen': 0.75.3(@babel/preset-env@7.26.9(@babel/core@7.25.2)) - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.2) + '@react-native/babel-plugin-codegen': 0.75.3(@babel/preset-env@7.26.9(@babel/core@7.26.9)) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.26.9) react-refresh: 0.14.2 transitivePeerDependencies: - '@babel/preset-env' - supports-color - '@react-native/codegen@0.75.3(@babel/preset-env@7.26.9(@babel/core@7.25.2))': + '@react-native/codegen@0.75.3(@babel/preset-env@7.26.9(@babel/core@7.26.9))': dependencies: '@babel/parser': 7.26.9 - '@babel/preset-env': 7.26.9(@babel/core@7.25.2) + '@babel/preset-env': 7.26.9(@babel/core@7.26.9) glob: 7.2.3 hermes-parser: 0.22.0 invariant: 2.2.4 - jscodeshift: 0.14.0(@babel/preset-env@7.26.9(@babel/core@7.25.2)) + jscodeshift: 0.14.0(@babel/preset-env@7.26.9(@babel/core@7.26.9)) mkdirp: 0.5.6 nullthrows: 1.1.1 yargs: 17.7.2 transitivePeerDependencies: - supports-color - '@react-native/community-cli-plugin@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(encoding@0.1.13)': + '@react-native/community-cli-plugin@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(encoding@0.1.13)': dependencies: '@react-native-community/cli-server-api': 14.1.0 '@react-native-community/cli-tools': 14.1.0 '@react-native/dev-middleware': 0.75.3(encoding@0.1.13) - '@react-native/metro-babel-transformer': 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2)) + '@react-native/metro-babel-transformer': 0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9)) chalk: 4.1.2 execa: 5.1.1 metro: 0.80.12 @@ -17399,10 +16621,10 @@ snapshots: '@react-native/js-polyfills@0.75.3': {} - '@react-native/metro-babel-transformer@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))': + '@react-native/metro-babel-transformer@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))': dependencies: - '@babel/core': 7.25.2 - '@react-native/babel-preset': 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2)) + '@babel/core': 7.26.9 + '@react-native/babel-preset': 0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9)) hermes-parser: 0.22.0 nullthrows: 1.1.1 transitivePeerDependencies: @@ -17411,12 +16633,12 @@ snapshots: '@react-native/normalize-colors@0.75.3': {} - '@react-native/virtualized-lists@0.75.3(@types/react@17.0.42)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)': + '@react-native/virtualized-lists@0.75.3(@types/react@17.0.42)(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 react: 17.0.2 - react-native: 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2) + react-native: 0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2) optionalDependencies: '@types/react': 17.0.42 @@ -17443,14 +16665,14 @@ snapshots: react: 17.0.2 react-konva: 18.2.10(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) - '@react-spring/native@9.7.4(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)': + '@react-spring/native@9.7.4(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)': dependencies: '@react-spring/animated': 9.7.4(react@17.0.2) '@react-spring/core': 9.7.4(react@17.0.2) '@react-spring/shared': 9.7.4(react@17.0.2) '@react-spring/types': 9.7.4 react: 17.0.2 - react-native: 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2) + react-native: 0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2) '@react-spring/rafz@9.7.4': {} @@ -17460,13 +16682,13 @@ snapshots: '@react-spring/types': 9.7.4 react: 17.0.2 - '@react-spring/three@9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(react@17.0.2)(three@0.168.0)': + '@react-spring/three@9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(react@17.0.2)(three@0.168.0)': dependencies: '@react-spring/animated': 9.7.4(react@17.0.2) '@react-spring/core': 9.7.4(react@17.0.2) '@react-spring/shared': 9.7.4(react@17.0.2) '@react-spring/types': 9.7.4 - '@react-three/fiber': 8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0) + '@react-three/fiber': 8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0) react: 17.0.2 three: 0.168.0 @@ -17492,7 +16714,7 @@ snapshots: react-zdog: 1.2.2 zdog: 1.1.3 - '@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0)': + '@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0)': dependencies: '@babel/runtime': 7.26.0 '@types/debounce': 1.2.4 @@ -17510,7 +16732,7 @@ snapshots: zustand: 3.7.2(react@17.0.2) optionalDependencies: react-dom: 17.0.2(react@17.0.2) - react-native: 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2) + react-native: 0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2) '@rollup/plugin-babel@5.3.1(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@2.79.1)': dependencies: @@ -17550,6 +16772,8 @@ snapshots: '@rushstack/eslint-patch@1.10.4': {} + '@scarf/scarf@1.4.0': {} + '@schematics/schematics@0.13.8': dependencies: '@angular-devkit/core': 7.3.8 @@ -17742,7 +16966,7 @@ snapshots: dependencies: '@types/estree': 1.0.6 - '@types/anymatch@3.0.0': + '@types/anymatch@3.0.4': dependencies: anymatch: 3.1.3 @@ -17893,8 +17117,6 @@ snapshots: '@types/minimatch@5.1.2': {} - '@types/minimist@1.2.5': {} - '@types/ms@2.1.0': {} '@types/node-forge@1.3.11': @@ -17908,9 +17130,6 @@ snapshots: '@types/node@24.5.2': dependencies: undici-types: 7.12.0 - optional: true - - '@types/normalize-package-data@2.4.4': {} '@types/parse-json@4.0.2': {} @@ -18001,7 +17220,7 @@ snapshots: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/node': 17.0.22 - form-data: 4.0.0 + form-data: 4.0.4 '@types/supertest@2.0.16': dependencies: @@ -18009,9 +17228,9 @@ snapshots: '@types/swagger-schema-official@2.0.22': {} - '@types/tapable@2.2.7': + '@types/tapable@2.3.0': dependencies: - tapable: 2.2.1 + tapable: 2.3.3 '@types/trusted-types@2.0.7': {} @@ -18031,9 +17250,9 @@ snapshots: '@types/webpack@4.41.5': dependencies: - '@types/anymatch': 3.0.0 + '@types/anymatch': 3.0.4 '@types/node': 17.0.22 - '@types/tapable': 2.2.7 + '@types/tapable': 2.3.0 '@types/uglify-js': 3.17.5 '@types/webpack-sources': 3.2.3 source-map: 0.6.1 @@ -18396,11 +17615,6 @@ snapshots: '@xtuc/long@4.2.2': {} - JSONStream@1.3.5: - dependencies: - jsonparse: 1.3.1 - through: 2.3.8 - abab@2.0.6: {} abort-controller@3.0.0: @@ -18412,11 +17626,6 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - accepts@2.0.0: - dependencies: - mime-types: 3.0.1 - negotiator: 1.0.0 - acorn-globals@4.3.4: dependencies: acorn: 6.4.2 @@ -18438,7 +17647,7 @@ snapshots: acorn-walk@8.3.4: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 acorn@5.7.4: {} @@ -18454,8 +17663,6 @@ snapshots: dependencies: object-assign: 4.1.1 - add-stream@1.0.0: {} - address@1.2.2: {} agent-base@7.1.4: {} @@ -18528,14 +17735,14 @@ snapshots: '@algolia/requester-fetch': 5.20.3 '@algolia/requester-node-http': 5.20.3 - ali-oss@6.21.0: + ali-oss@6.23.0: dependencies: address: 1.2.2 agentkeepalive: 3.5.3 bowser: 1.9.4 copy-to: 2.0.1 dateformat: 2.2.0 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.3 destroy: 1.2.0 end-or-error: 1.0.1 get-ready: 1.0.0 @@ -18548,7 +17755,7 @@ snapshots: mime: 2.6.0 platform: 1.3.6 pump: 3.0.2 - qs: 6.13.0 + qs: 6.14.0 sdk-base: 2.0.1 stream-http: 2.8.2 stream-wormhole: 1.1.0 @@ -18724,8 +17931,6 @@ snapshots: array-flatten@1.1.1: {} - array-ify@1.0.0: {} - array-includes@3.1.8: dependencies: call-bind: 1.0.7 @@ -18808,13 +18013,11 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - arrify@1.0.1: {} - asap@2.0.6: {} asn1.js@4.10.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.3 inherits: 2.0.4 minimalistic-assert: 1.0.1 @@ -18932,9 +18135,9 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@8.4.1(@babel/core@7.25.2)(webpack@5.98.0): + babel-loader@8.4.1(@babel/core@7.26.9)(webpack@5.98.0): dependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.9 find-cache-dir: 3.3.2 loader-utils: 2.0.4 make-dir: 3.1.0 @@ -18999,14 +18202,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.25.2): - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.25.2) - core-js-compat: 3.41.0 - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.9): dependencies: '@babel/core': 7.26.9 @@ -19029,9 +18224,9 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.25.2): + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.26.9): dependencies: - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.26.9) transitivePeerDependencies: - '@babel/core' @@ -19062,7 +18257,7 @@ snapshots: mixin-deep: 1.3.2 pascalcase: 0.1.1 - basic-ftp@5.0.5: {} + basic-ftp@5.3.1: {} batch@0.6.1: {} @@ -19093,9 +18288,9 @@ snapshots: bluebird@3.7.2: {} - bn.js@4.12.0: {} + bn.js@4.12.3: {} - bn.js@5.2.1: {} + bn.js@5.2.3: {} bodec@0.1.0: {} @@ -19131,20 +18326,6 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.2.0: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.3 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.1 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - bonjour-service@1.3.0: dependencies: fast-deep-equal: 3.1.3 @@ -19217,7 +18398,7 @@ snapshots: browserify-aes@1.2.0: dependencies: buffer-xor: 1.0.3 - cipher-base: 1.0.4 + cipher-base: 1.0.7 create-hash: 1.2.0 evp_bytestokey: 1.0.3 inherits: 2.0.4 @@ -19231,26 +18412,26 @@ snapshots: browserify-des@1.0.2: dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.7 des.js: 1.1.0 inherits: 2.0.4 safe-buffer: 5.2.1 - browserify-rsa@4.1.0: + browserify-rsa@4.1.1: dependencies: - bn.js: 5.2.1 + bn.js: 5.2.3 randombytes: 2.1.0 + safe-buffer: 5.2.1 - browserify-sign@4.2.3: + browserify-sign@4.2.5: dependencies: - bn.js: 5.2.1 - browserify-rsa: 4.1.0 + bn.js: 5.2.3 + browserify-rsa: 4.1.1 create-hash: 1.2.0 create-hmac: 1.1.7 - elliptic: 6.5.7 - hash-base: 3.0.4 + elliptic: 6.6.1 inherits: 2.0.4 - parse-asn1: 5.1.7 + parse-asn1: 5.1.9 readable-stream: 2.3.8 safe-buffer: 5.2.1 @@ -19374,6 +18555,13 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -19398,12 +18586,6 @@ snapshots: pascal-case: 3.1.2 tslib: 2.7.0 - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - camelcase@4.1.0: {} camelcase@5.3.1: {} @@ -19574,10 +18756,11 @@ snapshots: ci-info@3.9.0: {} - cipher-base@1.0.4: + cipher-base@1.0.7: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + to-buffer: 1.2.2 class-transformer@0.2.3: {} @@ -19590,8 +18773,8 @@ snapshots: class-validator@0.13.2: dependencies: - libphonenumber-js: 1.11.8 - validator: 13.12.0 + libphonenumber-js: 1.13.2 + validator: 13.15.35 classnames@2.5.1: {} @@ -19625,6 +18808,10 @@ snapshots: dependencies: restore-cursor: 3.1.0 + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + cli-highlight@2.1.11: dependencies: chalk: 4.1.2 @@ -19745,6 +18932,8 @@ snapshots: commander@10.0.1: {} + commander@13.1.0: {} + commander@2.15.1: {} commander@2.20.3: {} @@ -19765,11 +18954,6 @@ snapshots: commondir@1.0.1: {} - compare-func@2.0.0: - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 - component-classes@1.2.6: dependencies: component-indexof: 0.0.3 @@ -19780,7 +18964,7 @@ snapshots: compressible@2.0.18: dependencies: - mime-db: 1.53.0 + mime-db: 1.54.0 compression@1.7.4: dependencies: @@ -19859,105 +19043,16 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - content-security-policy-builder@2.1.0: {} content-type@1.0.5: {} - conventional-changelog-angular@6.0.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-atom@3.0.0: {} - - conventional-changelog-cli@3.0.0: - dependencies: - add-stream: 1.0.0 - conventional-changelog: 4.0.0 - meow: 8.1.2 - tempfile: 3.0.0 - - conventional-changelog-codemirror@3.0.0: {} - - conventional-changelog-conventionalcommits@6.1.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-core@5.0.2: - dependencies: - add-stream: 1.0.0 - conventional-changelog-writer: 6.0.1 - conventional-commits-parser: 4.0.0 - dateformat: 3.0.3 - get-pkg-repo: 4.2.1 - git-raw-commits: 3.0.0 - git-remote-origin-url: 2.0.0 - git-semver-tags: 5.0.1 - normalize-package-data: 3.0.3 - read-pkg: 3.0.0 - read-pkg-up: 3.0.0 - - conventional-changelog-ember@3.0.0: {} - - conventional-changelog-eslint@4.0.0: {} - - conventional-changelog-express@3.0.0: {} - - conventional-changelog-jquery@4.0.0: {} - - conventional-changelog-jshint@3.0.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-preset-loader@3.0.0: {} - - conventional-changelog-writer@6.0.1: - dependencies: - conventional-commits-filter: 3.0.0 - dateformat: 3.0.3 - handlebars: 4.7.8 - json-stringify-safe: 5.0.1 - meow: 8.1.2 - semver: 7.6.3 - split: 1.0.1 - - conventional-changelog@4.0.0: - dependencies: - conventional-changelog-angular: 6.0.0 - conventional-changelog-atom: 3.0.0 - conventional-changelog-codemirror: 3.0.0 - conventional-changelog-conventionalcommits: 6.1.0 - conventional-changelog-core: 5.0.2 - conventional-changelog-ember: 3.0.0 - conventional-changelog-eslint: 4.0.0 - conventional-changelog-express: 3.0.0 - conventional-changelog-jquery: 4.0.0 - conventional-changelog-jshint: 3.0.0 - conventional-changelog-preset-loader: 3.0.0 - - conventional-commits-filter@3.0.0: - dependencies: - lodash.ismatch: 4.4.0 - modify-values: 1.0.1 - - conventional-commits-parser@4.0.0: - dependencies: - JSONStream: 1.3.5 - is-text-path: 1.0.1 - meow: 8.1.2 - split2: 3.2.2 - convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} - cookie-signature@1.2.2: {} - cookie@0.4.0: {} cookie@0.7.1: {} @@ -20074,25 +19169,25 @@ snapshots: create-ecdh@4.0.4: dependencies: - bn.js: 4.12.0 - elliptic: 6.5.7 + bn.js: 4.12.3 + elliptic: 6.6.1 create-hash@1.2.0: dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.7 inherits: 2.0.4 md5.js: 1.3.5 - ripemd160: 2.0.2 - sha.js: 2.4.11 + ripemd160: 2.0.3 + sha.js: 2.4.12 create-hmac@1.1.7: dependencies: - cipher-base: 1.0.4 + cipher-base: 1.0.7 create-hash: 1.2.0 inherits: 2.0.4 - ripemd160: 2.0.2 + ripemd160: 2.0.3 safe-buffer: 5.2.1 - sha.js: 2.4.11 + sha.js: 2.4.12 create-react-class@15.7.0: dependencies: @@ -20103,7 +19198,7 @@ snapshots: cross-env@7.0.3: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 cross-spawn@6.0.5: dependencies: @@ -20113,28 +19208,23 @@ snapshots: shebang-command: 1.2.0 which: 1.3.1 - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - crypto-browserify@3.12.0: + crypto-browserify@3.12.1: dependencies: browserify-cipher: 1.0.1 - browserify-sign: 4.2.3 + browserify-sign: 4.2.5 create-ecdh: 4.0.4 create-hash: 1.2.0 create-hmac: 1.1.7 diffie-hellman: 5.0.3 + hash-base: 3.0.5 inherits: 2.0.4 - pbkdf2: 3.1.2 + pbkdf2: 3.1.5 public-encrypt: 4.0.3 randombytes: 2.1.0 randomfill: 1.0.4 @@ -20309,8 +19399,6 @@ snapshots: damerau-levenshtein@1.0.8: {} - dargs@7.0.0: {} - dashdash@1.14.1: dependencies: assert-plus: 1.0.0 @@ -20353,8 +19441,6 @@ snapshots: dateformat@2.2.0: {} - dateformat@3.0.3: {} - dayjs@1.11.13: {} dayjs@1.8.36: {} @@ -20383,11 +19469,6 @@ snapshots: dependencies: ms: 2.1.3 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - decamelize@1.2.0: {} decode-named-character-reference@1.0.2: @@ -20547,11 +19628,11 @@ snapshots: diff-sequences@24.9.0: {} - diff@4.0.2: {} + diff@4.0.4: {} diffie-hellman@5.0.3: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.3 miller-rabin: 4.0.1 randombytes: 2.1.0 @@ -20573,9 +19654,9 @@ snapshots: dependencies: esutils: 2.0.3 - docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(sass@1.79.3)(webpack@5.98.0): + docusaurus-plugin-sass@0.2.6(@docusaurus/core@3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3))(sass@1.79.3)(webpack@5.98.0): dependencies: - '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.14.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) + '@docusaurus/core': 3.7.0(@mdx-js/react@3.1.0(@types/react@17.0.42)(react@19.0.0))(acorn@8.15.0)(eslint@9.36.0(jiti@1.21.7))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.6.3) sass: 1.79.3 sass-loader: 16.0.5(sass@1.79.3)(webpack@5.98.0) transitivePeerDependencies: @@ -20645,16 +19726,14 @@ snapshots: no-case: 3.0.4 tslib: 2.7.0 - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 - dot-prop@6.0.1: dependencies: is-obj: 2.0.0 dotenv-expand@5.1.0: {} + dotenv@16.6.1: {} + dotenv@17.2.3: {} dotenv@8.2.0: {} @@ -20717,9 +19796,9 @@ snapshots: electron-to-chromium@1.5.27: {} - elliptic@6.5.7: + elliptic@6.6.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.3 brorand: 1.1.0 hash.js: 1.1.7 hmac-drbg: 1.0.1 @@ -20727,6 +19806,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + emoji-regex@10.6.0: {} + emoji-regex@7.0.3: {} emoji-regex@8.0.0: {} @@ -20986,7 +20067,7 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-next@12.1.0(eslint@8.11.0)(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(typescript@4.6.2): + eslint-config-next@12.1.0(eslint@8.11.0)(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(typescript@4.6.2): dependencies: '@next/eslint-plugin-next': 12.1.0 '@rushstack/eslint-patch': 1.10.4 @@ -20998,7 +20079,7 @@ snapshots: eslint-plugin-jsx-a11y: 6.10.0(eslint@8.11.0) eslint-plugin-react: 7.36.1(eslint@8.11.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.11.0) - next: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + next: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) optionalDependencies: typescript: 4.6.2 transitivePeerDependencies: @@ -21217,7 +20298,7 @@ snapshots: '@humanwhocodes/config-array': 0.9.5 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 debug: 4.3.7(supports-color@9.4.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 @@ -21253,8 +20334,8 @@ snapshots: eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/eslint-utils': 4.9.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 @@ -21263,8 +20344,8 @@ snapshots: '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.7(supports-color@9.4.0) + cross-spawn: 7.0.6 + debug: 4.4.3 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -21455,7 +20536,7 @@ snapshots: execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -21538,61 +20619,29 @@ snapshots: content-type: 1.0.5 cookie: 0.7.1 cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.1 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.13.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - express@5.1.0: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.1 - cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 2.6.9 + depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 + finalhandler: 1.3.1 + fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 2.0.0 - mime-types: 3.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 on-finished: 2.4.1 - once: 1.4.0 parseurl: 1.3.3 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.0 + qs: 6.13.0 range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.0 - serve-static: 2.2.0 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 statuses: 2.0.1 - type-is: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -21776,17 +20825,6 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.0: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - find-cache-dir@2.1.0: dependencies: commondir: 1.0.1 @@ -21804,10 +20842,6 @@ snapshots: common-path-prefix: 3.0.0 pkg-dir: 7.0.0 - find-up@2.1.0: - dependencies: - locate-path: 2.0.0 - find-up@3.0.0: dependencies: locate-path: 3.0.0 @@ -21865,6 +20899,10 @@ snapshots: dependencies: is-callable: 1.2.7 + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + for-in@1.0.2: {} forever-agent@0.6.1: {} @@ -21887,7 +20925,7 @@ snapshots: fork-ts-checker-webpack-plugin@6.5.3(eslint@9.36.0(jiti@1.21.7))(typescript@5.6.3)(webpack@5.98.0): dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@types/json-schema': 7.0.15 chalk: 4.1.2 chokidar: 3.6.0 @@ -21913,18 +20951,6 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 - form-data@2.5.1: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@4.0.4: dependencies: asynckit: 0.4.0 @@ -21941,7 +20967,7 @@ snapshots: formidable@1.2.6: {} - formstream@1.5.1: + formstream@1.5.2: dependencies: destroy: 1.2.0 mime: 2.6.0 @@ -21958,8 +20984,6 @@ snapshots: fresh@0.5.2: {} - fresh@2.0.0: {} - from2@2.3.0: dependencies: inherits: 2.0.4 @@ -22031,6 +21055,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -22054,13 +21080,6 @@ snapshots: get-own-enumerable-property-symbols@3.0.2: {} - get-pkg-repo@4.2.1: - dependencies: - '@hutson/parse-repository-url': 3.0.2 - hosted-git-info: 4.1.0 - through2: 2.0.5 - yargs: 16.2.0 - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -22082,7 +21101,7 @@ snapshots: get-uri@6.0.5: dependencies: - basic-ftp: 5.0.5 + basic-ftp: 5.3.1 data-uri-to-buffer: 6.0.2 debug: 4.4.3 transitivePeerDependencies: @@ -22098,28 +21117,8 @@ snapshots: optionalDependencies: js-git: 0.7.8 - git-raw-commits@3.0.0: - dependencies: - dargs: 7.0.0 - meow: 8.1.2 - split2: 3.2.2 - - git-remote-origin-url@2.0.0: - dependencies: - gitconfiglocal: 1.0.0 - pify: 2.3.0 - - git-semver-tags@5.0.1: - dependencies: - meow: 8.1.2 - semver: 7.6.3 - git-sha1@0.1.2: {} - gitconfiglocal@1.0.0: - dependencies: - ini: 1.3.8 - github-buttons@2.29.1: {} github-slugger@1.5.0: {} @@ -22250,15 +21249,6 @@ snapshots: handle-thing@2.0.1: {} - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - har-schema@2.0.0: {} har-validator@5.1.5: @@ -22266,8 +21256,6 @@ snapshots: ajv: 6.12.6 har-schema: 2.0.0 - hard-rejection@2.1.0: {} - has-ansi@2.0.0: dependencies: ansi-regex: 2.1.1 @@ -22313,16 +21301,17 @@ snapshots: has-yarn@3.0.0: {} - hash-base@3.0.4: + hash-base@3.0.5: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 - hash-base@3.1.0: + hash-base@3.1.2: dependencies: inherits: 2.0.4 - readable-stream: 3.6.2 + readable-stream: 2.3.8 safe-buffer: 5.2.1 + to-buffer: 1.2.2 hash.js@1.1.7: dependencies: @@ -22491,10 +21480,6 @@ snapshots: hosted-git-info@2.8.9: {} - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - hpack.js@2.1.6: dependencies: inherits: 2.0.4 @@ -22666,7 +21651,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.0: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -22826,7 +21811,7 @@ snapshots: dependencies: loose-envify: 1.4.0 - ip-address@10.0.1: {} + ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -22963,6 +21948,8 @@ snapshots: is-interactive@1.0.0: {} + is-interactive@2.0.0: {} + is-map@2.0.3: {} is-module@1.0.0: {} @@ -22997,8 +21984,6 @@ snapshots: is-path-inside@3.0.3: {} - is-plain-obj@1.1.0: {} - is-plain-obj@3.0.0: {} is-plain-obj@4.1.0: {} @@ -23009,8 +21994,6 @@ snapshots: is-promise@2.2.2: {} - is-promise@4.0.0: {} - is-property@1.0.2: {} is-regex@1.1.4: @@ -23040,10 +22023,6 @@ snapshots: dependencies: has-symbols: 1.0.3 - is-text-path@1.0.1: - dependencies: - text-extensions: 1.9.0 - is-type-of@1.4.0: dependencies: core-util-is: 1.0.3 @@ -23054,10 +22033,18 @@ snapshots: dependencies: which-typed-array: 1.1.15 + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + is-typedarray@1.0.0: {} is-unicode-supported@0.1.0: {} + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.0.2: @@ -23578,7 +22565,7 @@ snapshots: jsc-safe-url@0.2.4: {} - jscodeshift@0.14.0(@babel/preset-env@7.26.9(@babel/core@7.25.2)): + jscodeshift@0.14.0(@babel/preset-env@7.26.9(@babel/core@7.26.9)): dependencies: '@babel/core': 7.26.9 '@babel/parser': 7.26.9 @@ -23586,7 +22573,7 @@ snapshots: '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.26.9) '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.26.9) '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.9) - '@babel/preset-env': 7.26.9(@babel/core@7.25.2) + '@babel/preset-env': 7.26.9(@babel/core@7.26.9) '@babel/preset-flow': 7.24.7(@babel/core@7.26.9) '@babel/preset-typescript': 7.26.0(@babel/core@7.26.9) '@babel/register': 7.24.6(@babel/core@7.26.9) @@ -23679,13 +22666,11 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 - jsonparse@1.3.1: {} - jsonpointer@5.0.1: {} jsonwebtoken@8.5.1: dependencies: - jws: 3.2.2 + jws: 3.2.3 lodash.includes: 4.3.0 lodash.isboolean: 3.0.3 lodash.isinteger: 4.0.4 @@ -23696,9 +22681,9 @@ snapshots: ms: 2.1.3 semver: 5.7.2 - jsonwebtoken@9.0.2: + jsonwebtoken@9.0.3: dependencies: - jws: 3.2.2 + jws: 4.0.1 lodash.includes: 4.3.0 lodash.isboolean: 3.0.3 lodash.isinteger: 4.0.4 @@ -23725,15 +22710,26 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 - jwa@1.4.1: + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jwa@2.0.1: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 - jws@3.2.2: + jws@3.2.3: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + + jws@4.0.1: dependencies: - jwa: 1.4.1 + jwa: 2.0.1 safe-buffer: 5.2.1 keyv@4.5.4: @@ -23811,7 +22807,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libphonenumber-js@1.11.8: {} + libphonenumber-js@1.13.2: {} lighthouse-logger@1.4.2: dependencies: @@ -23883,11 +22879,6 @@ snapshots: loader-utils@3.3.1: {} - locate-path@2.0.0: - dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 - locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -23921,8 +22912,6 @@ snapshots: lodash.isinteger@4.0.4: {} - lodash.ismatch@4.4.0: {} - lodash.isnumber@3.0.3: {} lodash.isplainobject@4.0.6: {} @@ -23958,6 +22947,11 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + log-symbols@6.0.0: + dependencies: + chalk: 5.4.1 + is-unicode-supported: 1.3.0 + log-update@4.0.0: dependencies: ansi-escapes: 4.3.2 @@ -23968,7 +22962,7 @@ snapshots: log4js@6.9.1: dependencies: date-format: 4.0.14 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.3 flatted: 3.3.1 rfdc: 1.4.1 streamroller: 3.1.5 @@ -23981,7 +22975,7 @@ snapshots: dayjs: 1.11.13 yargs: 15.4.1 - long@5.2.3: {} + long@5.3.2: {} longest-streak@3.1.0: {} @@ -24009,7 +23003,7 @@ snapshots: dependencies: es5-ext: 0.10.64 - lru.min@1.1.1: {} + lru.min@1.1.4: {} lz-string@1.5.0: {} @@ -24038,10 +23032,6 @@ snapshots: map-cache@0.2.2: {} - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - map-visit@1.0.0: dependencies: object-visit: 1.0.1 @@ -24062,7 +23052,7 @@ snapshots: md5.js@1.3.5: dependencies: - hash-base: 3.1.0 + hash-base: 3.0.5 inherits: 2.0.4 safe-buffer: 5.2.1 @@ -24260,8 +23250,6 @@ snapshots: media-typer@0.3.0: {} - media-typer@1.1.0: {} - memfs@3.5.3: dependencies: fs-monkey: 1.0.6 @@ -24289,26 +23277,10 @@ snapshots: errno: 0.1.8 readable-stream: 2.3.8 - meow@8.1.2: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - merge-descriptors@1.0.1: {} merge-descriptors@1.0.3: {} - merge-descriptors@2.0.0: {} - merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -24817,15 +23789,13 @@ snapshots: miller-rabin@4.0.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.3 brorand: 1.1.0 mime-db@1.33.0: {} mime-db@1.52.0: {} - mime-db@1.53.0: {} - mime-db@1.54.0: {} mime-types@2.1.18: @@ -24836,10 +23806,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mime-types@3.0.1: - dependencies: - mime-db: 1.54.0 - mime@1.6.0: {} mime@2.6.0: {} @@ -24848,6 +23814,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} + mimic-response@3.1.0: {} mimic-response@4.0.0: {} @@ -24872,12 +23840,6 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.0: {} minimist@1.2.8: {} @@ -24906,8 +23868,6 @@ snapshots: mkdirp@1.0.4: {} - modify-values@1.0.1: {} - module-details-from-path@1.0.4: {} monaco-editor@0.52.0: {} @@ -24949,17 +23909,29 @@ snapshots: mute-stream@0.0.8: {} - mysql2@3.12.0: + mysql2@3.22.3(@types/node@12.20.55): dependencies: + '@types/node': 12.20.55 aws-ssl-profiles: 1.1.2 denque: 2.1.0 generate-function: 2.3.1 - iconv-lite: 0.6.3 - long: 5.2.3 - lru.min: 1.1.1 - named-placeholders: 1.1.3 - seq-queue: 0.0.5 - sqlstring: 2.3.3 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + sql-escaper: 1.3.3 + + mysql2@3.22.3(@types/node@24.5.2): + dependencies: + '@types/node': 24.5.2 + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + sql-escaper: 1.3.3 mz@2.7.0: dependencies: @@ -24967,9 +23939,9 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - named-placeholders@1.1.3: + named-placeholders@1.1.6: dependencies: - lru-cache: 7.18.3 + lru.min: 1.1.4 nan@2.20.0: optional: true @@ -25014,11 +23986,9 @@ snapshots: negotiator@0.6.3: {} - negotiator@1.0.0: {} - neo-async@2.6.2: {} - netmask@2.0.2: {} + netmask@2.1.1: {} next-compose-plugins@2.2.1: {} @@ -25034,9 +24004,9 @@ snapshots: url-loader: 4.1.1(file-loader@6.2.0(webpack@5.98.0))(webpack@5.98.0) webpack: 5.98.0 - next-intl@1.5.1(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(react@17.0.2): + next-intl@1.5.1(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(react@17.0.2): dependencies: - next: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + next: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) react: 17.0.2 use-intl: 1.5.1(react@17.0.2) @@ -25047,12 +24017,12 @@ snapshots: react-dom: 17.0.2(react@17.0.2) react-transition-group: 2.9.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) - next-pwa@5.6.0(@babel/core@7.25.2)(@types/babel__core@7.20.5)(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(webpack@5.98.0): + next-pwa@5.6.0(@babel/core@7.26.9)(@types/babel__core@7.20.5)(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3))(webpack@5.98.0): dependencies: - babel-loader: 8.4.1(@babel/core@7.25.2)(webpack@5.98.0) + babel-loader: 8.4.1(@babel/core@7.26.9)(webpack@5.98.0) clean-webpack-plugin: 4.0.0(webpack@5.98.0) globby: 11.1.0 - next: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + next: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) terser-webpack-plugin: 5.3.10(webpack@5.98.0) workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.98.0) workbox-window: 6.6.0 @@ -25065,11 +24035,11 @@ snapshots: - uglify-js - webpack - next-sitemap@1.9.12(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)): + next-sitemap@1.9.12(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)): dependencies: '@corex/deepmerge': 2.6.148 minimist: 1.2.8 - next: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + next: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) next-tick@1.1.0: {} @@ -25078,14 +24048,14 @@ snapshots: enhanced-resolve: 5.17.1 escalade: 3.2.0 - next-with-less@2.0.5(less-loader@10.2.0(less@4.2.0)(webpack@5.98.0))(less@4.2.0)(next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)): + next-with-less@2.0.5(less-loader@10.2.0(less@4.2.0)(webpack@5.98.0))(less@4.2.0)(next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3)): dependencies: clone-deep: 4.0.1 less: 4.2.0 less-loader: 10.2.0(less@4.2.0)(webpack@5.98.0) - next: 12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) + next: 12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3) - next@12.3.4(@babel/core@7.25.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3): + next@12.3.4(@babel/core@7.26.9)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(sass@1.79.3): dependencies: '@next/env': 12.3.4 '@swc/helpers': 0.4.11 @@ -25093,7 +24063,7 @@ snapshots: postcss: 8.4.14 react: 17.0.2 react-dom: 17.0.2(react@17.0.2) - styled-jsx: 5.0.7(@babel/core@7.25.2)(react@17.0.2) + styled-jsx: 5.0.7(@babel/core@7.26.9)(react@17.0.2) use-sync-external-store: 1.2.0(react@17.0.2) optionalDependencies: '@next/swc-android-arm-eabi': 12.3.4 @@ -25184,7 +24154,7 @@ snapshots: buffer: 4.9.2 console-browserify: 1.2.0 constants-browserify: 1.0.0 - crypto-browserify: 3.12.0 + crypto-browserify: 3.12.1 domain-browser: 1.2.0 events: 3.3.0 https-browserify: 1.0.0 @@ -25221,7 +24191,7 @@ snapshots: node-stream-zip@1.15.0: {} - nodemailer@6.9.15: {} + nodemailer@6.10.1: {} noms@0.0.0: dependencies: @@ -25235,13 +24205,6 @@ snapshots: semver: 5.7.2 validate-npm-package-license: 3.0.4 - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.15.1 - semver: 7.6.3 - validate-npm-package-license: 3.0.4 - normalize-path@2.1.1: dependencies: remove-trailing-separator: 1.1.0 @@ -25421,6 +24384,10 @@ snapshots: dependencies: mimic-fn: 2.1.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + open@6.4.0: dependencies: is-wsl: 1.1.0 @@ -25481,6 +24448,18 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + ora@8.2.0: + dependencies: + chalk: 5.4.1 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.0 + os-browserify@0.3.0: {} os-name@1.0.3: @@ -25507,10 +24486,6 @@ snapshots: p-finally@1.0.0: {} - p-limit@1.3.0: - dependencies: - p-try: 1.0.0 - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -25523,10 +24498,6 @@ snapshots: dependencies: yocto-queue: 1.1.1 - p-locate@2.0.0: - dependencies: - p-limit: 1.3.0 - p-locate@3.0.0: dependencies: p-limit: 2.3.0 @@ -25556,8 +24527,6 @@ snapshots: '@types/retry': 0.12.0 retry: 0.13.1 - p-try@1.0.0: {} - p-try@2.2.0: {} pac-proxy-agent@7.2.0: @@ -25576,7 +24545,7 @@ snapshots: pac-resolver@7.0.1: dependencies: degenerator: 5.0.1 - netmask: 2.0.2 + netmask: 2.1.1 package-json@8.1.1: dependencies: @@ -25604,13 +24573,12 @@ snapshots: dependencies: callsites: 3.1.0 - parse-asn1@5.1.7: + parse-asn1@5.1.9: dependencies: asn1.js: 4.10.1 browserify-aes: 1.2.0 evp_bytestokey: 1.0.3 - hash-base: 3.0.4 - pbkdf2: 3.1.2 + pbkdf2: 3.1.5 safe-buffer: 5.2.1 parse-entities@4.0.2: @@ -25669,7 +24637,7 @@ snapshots: passport-jwt@4.0.1: dependencies: - jsonwebtoken: 9.0.2 + jsonwebtoken: 9.0.3 passport-strategy: 1.0.0 passport-strategy@1.0.0: {} @@ -25713,8 +24681,6 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@8.3.0: {} - path-type@3.0.0: dependencies: pify: 3.0.0 @@ -25727,13 +24693,14 @@ snapshots: pause@0.0.1: {} - pbkdf2@3.1.2: + pbkdf2@3.1.5: dependencies: create-hash: 1.2.0 create-hmac: 1.1.7 - ripemd160: 2.0.2 + ripemd160: 2.0.3 safe-buffer: 5.2.1 - sha.js: 2.4.11 + sha.js: 2.4.12 + to-buffer: 1.2.2 performance-now@0.2.0: {} @@ -25815,7 +24782,7 @@ snapshots: async: 3.2.6 debug: 4.4.3 pidusage: 2.0.21 - systeminformation: 5.27.10 + systeminformation: 5.31.6 tx2: 1.0.5 transitivePeerDependencies: - supports-color @@ -26426,10 +25393,10 @@ snapshots: public-encrypt@4.0.3: dependencies: - bn.js: 4.12.0 - browserify-rsa: 4.1.0 + bn.js: 4.12.3 + browserify-rsa: 4.1.1 create-hash: 1.2.0 - parse-asn1: 5.1.7 + parse-asn1: 5.1.9 randombytes: 2.1.0 safe-buffer: 5.2.1 @@ -26461,7 +25428,7 @@ snapshots: qs@6.13.0: dependencies: - side-channel: 1.0.6 + side-channel: 1.1.0 qs@6.14.0: dependencies: @@ -26479,8 +25446,6 @@ snapshots: dependencies: inherits: 2.0.4 - quick-lru@4.0.1: {} - quick-lru@5.1.1: {} raf@3.4.1: @@ -26514,13 +25479,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-body@3.0.1: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.7.0 - unpipe: 1.0.0 - rc-animate@2.11.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: babel-runtime: 6.26.0 @@ -26924,11 +25882,11 @@ snapshots: react-dev-utils@12.0.1(eslint@9.36.0(jiti@1.21.7))(typescript@5.6.3)(webpack@5.98.0): dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 address: 1.2.2 browserslist: 4.23.3 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 detect-port-alt: 1.1.6 escape-string-regexp: 4.0.0 filesize: 8.0.7 @@ -27038,19 +25996,19 @@ snapshots: raf: 3.4.1 react: 17.0.2 - react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2): + react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2): dependencies: '@jest/create-cache-key-function': 29.7.0 '@react-native-community/cli': 14.1.0(typescript@4.6.2) '@react-native-community/cli-platform-android': 14.1.0 '@react-native-community/cli-platform-ios': 14.1.0 '@react-native/assets-registry': 0.75.3 - '@react-native/codegen': 0.75.3(@babel/preset-env@7.26.9(@babel/core@7.25.2)) - '@react-native/community-cli-plugin': 0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(encoding@0.1.13) + '@react-native/codegen': 0.75.3(@babel/preset-env@7.26.9(@babel/core@7.26.9)) + '@react-native/community-cli-plugin': 0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(encoding@0.1.13) '@react-native/gradle-plugin': 0.75.3 '@react-native/js-polyfills': 0.75.3 '@react-native/normalize-colors': 0.75.3 - '@react-native/virtualized-lists': 0.75.3(@types/react@17.0.42)(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2) + '@react-native/virtualized-lists': 0.75.3(@types/react@17.0.42)(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -27143,12 +26101,12 @@ snapshots: react: 17.0.2 react-dom: 17.0.2(react@17.0.2) - react-spring@9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react-konva@18.2.10(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react-zdog@1.2.2)(react@17.0.2)(three@0.168.0)(zdog@1.1.3): + react-spring@9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react-konva@18.2.10(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react-zdog@1.2.2)(react@17.0.2)(three@0.168.0)(zdog@1.1.3): dependencies: '@react-spring/core': 9.7.4(react@17.0.2) '@react-spring/konva': 9.7.4(konva@9.3.15)(react-konva@18.2.10(konva@9.3.15)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react@17.0.2) - '@react-spring/native': 9.7.4(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2) - '@react-spring/three': 9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.25.2)(@babel/preset-env@7.26.9(@babel/core@7.25.2))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(react@17.0.2)(three@0.168.0) + '@react-spring/native': 9.7.4(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2) + '@react-spring/three': 9.7.4(@react-three/fiber@8.17.7(react-dom@17.0.2(react@17.0.2))(react-native@0.75.3(@babel/core@7.26.9)(@babel/preset-env@7.26.9(@babel/core@7.26.9))(@types/react@17.0.42)(encoding@0.1.13)(react@17.0.2)(typescript@4.6.2))(react@17.0.2)(three@0.168.0))(react@17.0.2)(three@0.168.0) '@react-spring/web': 9.7.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@react-spring/zdog': 9.7.4(react-dom@17.0.2(react@17.0.2))(react-zdog@1.2.2)(react@17.0.2)(zdog@1.1.3) react: 17.0.2 @@ -27202,35 +26160,17 @@ snapshots: react@19.0.0: {} - read-pkg-up@3.0.0: - dependencies: - find-up: 2.1.0 - read-pkg: 3.0.0 - read-pkg-up@4.0.0: dependencies: find-up: 3.0.0 read-pkg: 3.0.0 - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 normalize-package-data: 2.5.0 path-type: 3.0.0 - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - read@1.0.7: dependencies: mute-stream: 0.0.8 @@ -27304,9 +26244,9 @@ snapshots: estree-util-build-jsx: 3.0.1 vfile: 6.0.3 - recma-jsx@1.0.0(acorn@8.14.0): + recma-jsx@1.0.0(acorn@8.15.0): dependencies: - acorn-jsx: 5.3.2(acorn@8.14.0) + acorn-jsx: 5.3.2(acorn@8.15.0) estree-util-to-js: 2.0.0 recma-parse: 1.0.0 recma-stringify: 1.0.0 @@ -27613,6 +26553,11 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + ret@0.1.15: {} retry@0.13.1: {} @@ -27637,14 +26582,14 @@ snapshots: dependencies: glob: 7.2.3 - ripemd160@2.0.2: + ripemd160@2.0.3: dependencies: - hash-base: 3.1.0 + hash-base: 3.1.2 inherits: 2.0.4 rollup-plugin-terser@7.0.2(rollup@2.79.1): dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 jest-worker: 26.6.2 rollup: 2.79.1 serialize-javascript: 4.0.0 @@ -27654,16 +26599,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.3.0 - transitivePeerDependencies: - - supports-color - rsvp@4.8.5: {} rtlcss@4.3.0: @@ -27875,24 +26810,6 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.2.0: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.0 - mime-types: 3.0.1 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - seq-queue@0.0.5: {} - serialize-error@2.1.0: {} serialize-javascript@4.0.0: @@ -27943,15 +26860,6 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.0 - transitivePeerDependencies: - - supports-color - set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -27985,10 +26893,11 @@ snapshots: setprototypeof@1.2.0: {} - sha.js@2.4.11: + sha.js@2.4.12: dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + to-buffer: 1.2.2 shallow-clone@3.0.1: dependencies: @@ -28087,6 +26996,8 @@ snapshots: signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.28 @@ -28177,13 +27088,13 @@ snapshots: dependencies: agent-base: 7.1.4 debug: 4.4.3 - socks: 2.8.7 + socks: 2.8.9 transitivePeerDependencies: - supports-color - socks@2.8.7: + socks@2.8.9: dependencies: - ip-address: 10.0.1 + ip-address: 10.2.0 smart-buffer: 4.2.0 sort-css-media-queries@2.2.0: {} @@ -28264,19 +27175,11 @@ snapshots: dependencies: extend-shallow: 3.0.2 - split2@3.2.2: - dependencies: - readable-stream: 3.6.2 - - split@1.0.1: - dependencies: - through: 2.3.8 - sprintf-js@1.0.3: {} sprintf-js@1.1.2: {} - sqlstring@2.3.3: {} + sql-escaper@1.3.3: {} srcset@4.0.0: {} @@ -28323,6 +27226,8 @@ snapshots: std-env@3.8.1: {} + stdin-discarder@0.2.2: {} + stealthy-require@1.1.1: {} stop-iteration-iterator@1.0.0: @@ -28401,6 +27306,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.1.0 + string.prototype.includes@2.0.0: dependencies: define-properties: 1.2.1 @@ -28514,11 +27425,11 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.0.7(@babel/core@7.25.2)(react@17.0.2): + styled-jsx@5.0.7(@babel/core@7.26.9)(react@17.0.2): dependencies: react: 17.0.2 optionalDependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.26.9 stylehacks@6.1.1(postcss@8.5.3): dependencies: @@ -28536,11 +27447,11 @@ snapshots: cookiejar: 2.1.4 debug: 3.2.7 extend: 3.0.2 - form-data: 2.5.1 + form-data: 2.3.3 formidable: 1.2.6 methods: 1.1.2 mime: 1.6.0 - qs: 6.13.0 + qs: 6.14.0 readable-stream: 2.3.8 transitivePeerDependencies: - supports-color @@ -28613,12 +27524,14 @@ snapshots: transitivePeerDependencies: - encoding - swagger-ui-dist@5.17.14: {} + swagger-ui-dist@5.32.6: + dependencies: + '@scarf/scarf': 1.4.0 swagger-ui-express@4.6.3(express@4.21.2): dependencies: express: 4.21.2 - swagger-ui-dist: 5.17.14 + swagger-ui-dist: 5.32.6 swagger2openapi@7.0.8(encoding@0.1.13): dependencies: @@ -28646,24 +27559,21 @@ snapshots: symbol-tree@3.2.4: {} - systeminformation@5.27.10: + systeminformation@5.31.6: optional: true tapable@1.1.3: {} tapable@2.2.1: {} + tapable@2.3.3: {} + temp-dir@2.0.0: {} temp@0.8.4: dependencies: rimraf: 2.6.3 - tempfile@3.0.0: - dependencies: - temp-dir: 2.0.0 - uuid: 3.4.0 - tempy@0.6.0: dependencies: is-stream: 2.0.1 @@ -28704,7 +27614,7 @@ snapshots: terser@4.8.1: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 commander: 2.20.3 source-map: 0.6.1 source-map-support: 0.5.21 @@ -28723,8 +27633,6 @@ snapshots: read-pkg-up: 4.0.0 require-main-filename: 2.0.0 - text-extensions@1.9.0: {} - text-table@0.2.0: {} thenify-all@1.6.0: @@ -28773,6 +27681,12 @@ snapshots: to-arraybuffer@1.0.1: {} + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + to-object-path@0.3.0: dependencies: kind-of: 3.2.2 @@ -28816,8 +27730,6 @@ snapshots: trim-lines@3.0.1: {} - trim-newlines@3.0.1: {} - trough@2.2.0: {} ts-jest@24.3.0(jest@24.9.0): @@ -28846,7 +27758,7 @@ snapshots: ts-node@8.10.2(typescript@4.1.6): dependencies: arg: 4.1.3 - diff: 4.0.2 + diff: 4.0.4 make-error: 1.3.6 source-map-support: 0.5.21 typescript: 4.1.6 @@ -28890,11 +27802,11 @@ snapshots: tslint@5.20.1(typescript@4.1.6): dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 builtin-modules: 1.1.1 chalk: 2.4.2 commander: 2.20.3 - diff: 4.0.2 + diff: 4.0.4 glob: 7.2.3 js-yaml: 3.14.1 minimatch: 3.1.2 @@ -28947,18 +27859,12 @@ snapshots: type-fest@0.16.0: {} - type-fest@0.18.1: {} - type-fest@0.20.2: {} type-fest@0.21.3: {} - type-fest@0.6.0: {} - type-fest@0.7.1: {} - type-fest@0.8.1: {} - type-fest@1.4.0: {} type-fest@2.19.0: {} @@ -28968,12 +27874,6 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.1 - type@2.7.3: {} typed-array-buffer@1.0.2: @@ -28982,6 +27882,12 @@ snapshots: es-errors: 1.3.0 is-typed-array: 1.1.13 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + typed-array-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -29014,27 +27920,27 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.2.45(mysql2@3.12.0): + typeorm@0.2.45(mysql2@3.22.3(@types/node@12.20.55)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 buffer: 6.0.3 chalk: 4.1.2 cli-highlight: 2.1.11 - debug: 4.3.7(supports-color@9.4.0) + debug: 4.4.3 dotenv: 8.6.0 glob: 7.2.3 js-yaml: 4.1.0 mkdirp: 1.0.4 reflect-metadata: 0.1.14 - sha.js: 2.4.11 + sha.js: 2.4.12 tslib: 2.7.0 uuid: 8.3.2 xml2js: 0.4.23 yargs: 17.7.2 zen-observable-ts: 1.1.0 optionalDependencies: - mysql2: 3.12.0 + mysql2: 3.22.3(@types/node@12.20.55) transitivePeerDependencies: - supports-color @@ -29050,9 +27956,6 @@ snapshots: ua-parser-js@0.7.39: {} - uglify-js@3.19.3: - optional: true - unbox-primitive@1.0.2: dependencies: call-bind: 1.0.7 @@ -29060,8 +27963,7 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - undici-types@7.12.0: - optional: true + undici-types@7.12.0: {} unescape@1.0.1: dependencies: @@ -29200,7 +28102,7 @@ snapshots: url@0.11.4: dependencies: punycode: 1.4.1 - qs: 6.13.0 + qs: 6.14.0 urllib@2.44.0: dependencies: @@ -29209,11 +28111,11 @@ snapshots: default-user-agent: 1.0.0 digest-header: 1.1.0 ee-first: 1.1.1 - formstream: 1.5.1 + formstream: 1.5.2 humanize-ms: 1.2.1 iconv-lite: 0.6.3 pump: 3.0.2 - qs: 6.13.0 + qs: 6.14.0 statuses: 1.5.0 utility: 1.18.0 @@ -29281,7 +28183,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - validator@13.12.0: {} + validator@13.15.35: {} value-equal@1.0.1: {} @@ -29602,6 +28504,16 @@ snapshots: gopd: 1.0.1 has-tostringtag: 1.0.2 + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@1.3.1: dependencies: isexe: 2.0.0 @@ -29626,8 +28538,6 @@ snapshots: word-wrap@1.2.5: {} - wordwrap@1.0.0: {} - workbox-background-sync@6.6.0: dependencies: idb: 7.1.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5d4fcaf..7b048f6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,11 +1,13 @@ packages: + # CLI 工具链 + - 'cli' + # 后端 API + - 'server' # 客户端 - 'client' - # 服务器 - - 'server' # 文档 - 'docs' # 工具包 - 'toolkit' # 模板 - - 'templates/*' \ No newline at end of file + - 'templates/*' diff --git a/scripts/benchmark-cold-start.mjs b/scripts/benchmark-cold-start.mjs new file mode 100644 index 0000000..c66c4f3 --- /dev/null +++ b/scripts/benchmark-cold-start.mjs @@ -0,0 +1,82 @@ +#!/usr/bin/env node +/** + * 3.0 验收:二次冷启动耗时(需已执行过 reactpress init,且 Docker/MySQL 可用)。 + * 用法: node scripts/benchmark-cold-start.mjs + * 环境: BENCHMARK_MAX_MS=60000(默认 60s) + */ +import { spawn } from 'node:child_process'; +import http from 'node:http'; + +const MAX_MS = Number(process.env.BENCHMARK_MAX_MS || 60_000); +const CLIENT_URL = process.env.CLIENT_SITE_URL || 'http://127.0.0.1:3001'; +const HEALTH_URL = + process.env.HEALTH_URL || 'http://127.0.0.1:3002/api/health'; + +function probe(url, timeoutMs = 2000) { + return new Promise((resolve) => { + let parsed; + try { + parsed = new URL(url); + } catch { + resolve(false); + return; + } + const port = parsed.port || (parsed.protocol === 'https:' ? 443 : 80); + const req = http.request( + { + hostname: parsed.hostname, + port, + path: parsed.pathname + (parsed.search || ''), + method: 'GET', + timeout: timeoutMs, + }, + (res) => resolve(res.statusCode > 0) + ); + req.on('timeout', () => { + req.destroy(); + resolve(false); + }); + req.on('error', () => resolve(false)); + req.end(); + }); +} + +async function waitReady(url, deadline) { + while (Date.now() < deadline) { + if (await probe(url)) return true; + await new Promise((r) => setTimeout(r, 500)); + } + return false; +} + +const t0 = Date.now(); +const child = spawn('node', ['./cli/bin/reactpress.js', 'dev'], { + stdio: 'inherit', + env: { ...process.env, REACTPRESS_SUPPRESS_DEPRECATION: '1' }, +}); + +const deadline = t0 + MAX_MS; +let clientOk = false; +let healthOk = false; + +const poll = setInterval(async () => { + if (!healthOk) healthOk = await waitReady(HEALTH_URL, Date.now() + 500); + if (!clientOk) clientOk = await probe(CLIENT_URL); + if (healthOk && clientOk) { + clearInterval(poll); + const elapsed = Date.now() - t0; + console.log(`\n[benchmark] Ready in ${(elapsed / 1000).toFixed(1)}s (limit ${MAX_MS / 1000}s)`); + child.kill('SIGINT'); + process.exit(elapsed <= MAX_MS ? 0 : 1); + } + if (Date.now() >= deadline) { + clearInterval(poll); + console.error(`\n[benchmark] Timeout ${MAX_MS / 1000}s (health=${healthOk} client=${clientOk})`); + child.kill('SIGINT'); + process.exit(1); + } +}, 1000); + +child.on('close', (code) => { + if (!healthOk || !clientOk) process.exit(code ?? 1); +}); diff --git a/scripts/bundled-server-path.js b/scripts/bundled-server-path.js new file mode 100644 index 0000000..55a5fd4 --- /dev/null +++ b/scripts/bundled-server-path.js @@ -0,0 +1,2 @@ +/** @deprecated Import from `cli/lib/paths` */ +module.exports = require('../cli/lib/paths'); diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 40f38e7..763c8b4 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,59 +1,44 @@ #!/bin/bash -# Exit on any error +# ReactPress production deploy (monorepo: server + client on host with PM2) set -e -# Logging function log() { - echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" } -# Error handling error_exit() { - log "ERROR: $1" - exit 1 + log "ERROR: $1" + exit 1 } -# Check if repository is already cloned -if [ ! -d ".git" ]; then - log "Cloning repository..." - # Assuming the script is in the root of the repo, this would be adjusted based on actual needs - # For now, we'll assume the script is run from within the repo - log "WARNING: This script should be run from within the cloned repository" -else - log "Repository already cloned, proceeding with update..." -fi - -log "Starting deployment process..." +log "Starting deployment..." -# Update code -log "Updating code from repository..." -git checkout master || error_exit "Failed to checkout master branch" -git pull || error_exit "Failed to pull latest changes" +if [ ! -f "package.json" ]; then + error_exit "Run this script from the repository root" +fi -# Install dependencies log "Installing dependencies..." -pnpm install || error_exit "Failed to install dependencies" +pnpm install || error_exit "pnpm install failed" -# Build all packages using the unified build command -log "Building all packages..." -pnpm run build || error_exit "Failed to build packages" +log "Building toolkit, server, and client..." +pnpm run build || error_exit "pnpm build failed" -# Kill existing PM2 processes -log "Stopping existing PM2 processes..." -pm2 kill || log "No existing PM2 processes found" +log "Stopping existing ReactPress PM2 apps (if any)..." +pm2 delete reactpress-server 2>/dev/null || true +pm2 delete @fecommunity/reactpress-server 2>/dev/null || true +pm2 delete @fecommunity/reactpress-client 2>/dev/null || true +pm2 delete reactpress-client 2>/dev/null || true -# Start services with PM2 -log "Starting services with PM2..." -pnpm run pm2 || error_exit "Failed to start services with PM2" +log "Starting API and client with PM2..." +pnpm run pm2 || error_exit "Failed to start PM2 processes" -# Set up PM2 startup script -log "Setting up PM2 startup script..." -pm2 startup || log "Failed to setup PM2 startup script" -pm2 save || error_exit "Failed to save PM2 configuration" +log "Saving PM2 process list..." +pm2 save || log "WARN: pm2 save failed (run pm2 startup manually if needed)" -log "Deployment completed successfully!" -log "Frontend available at http://localhost:3001" -log "Backend API available at http://localhost:3002" +log "Deployment completed." +log " Client: http://localhost:3001" +log " API: http://localhost:3002" +log " Status: pnpm run status" -exit 0 \ No newline at end of file +exit 0 diff --git a/scripts/docker-dev.js b/scripts/docker-dev.js index c9af1b5..e027040 100755 --- a/scripts/docker-dev.js +++ b/scripts/docker-dev.js @@ -1,217 +1,6 @@ #!/usr/bin/env node - -// Enhanced Docker Development Script -const { spawn, execSync } = require('child_process'); -const path = require('path'); -const fs = require('fs'); - -// 获取当前工作目录 -const currentWorkingDir = process.cwd(); - -// 设置环境变量 -process.env.REACTPRESS_ORIGINAL_CWD = currentWorkingDir; - -console.log(`[ReactPress] Setting REACTPRESS_ORIGINAL_CWD: ${currentWorkingDir}`); - -// Helper function to check if Docker is running -function isDockerRunning() { - try { - execSync('docker info', { stdio: 'ignore' }); - return true; - } catch (error) { - return false; - } -} - -// Helper function to check if containers are running -function areContainersRunning() { - try { - const output = execSync('docker-compose -f docker-compose.dev.yml ps --services --filter "status=running"', { - encoding: 'utf-8' - }); - const services = output.trim().split('\n').filter(line => line.length > 0); - return services.length >= 2; // db and nginx - } catch (error) { - return false; - } -} - -// Helper function to stop Docker services -function stopDockerServices() { - console.log('[ReactPress] Stopping Docker services...'); - try { - execSync('docker-compose -f docker-compose.dev.yml down', { - stdio: 'inherit' - }); - console.log('[ReactPress] Docker services stopped successfully.'); - } catch (error) { - console.error('[ReactPress] Error stopping Docker services:', error.message); - } -} - -// Helper function to start Docker services -function startDockerServices() { - console.log('[ReactPress] Starting Docker services...'); - - if (!isDockerRunning()) { - console.error('[ReactPress] Docker is not running. Please start Docker first.'); - process.exit(1); - } - - try { - execSync('docker-compose -f docker-compose.dev.yml up -d', { - stdio: 'inherit' - }); - console.log('[ReactPress] Docker services started successfully.'); - return true; - } catch (error) { - console.error('[ReactPress] Error starting Docker services:', error.message); - return false; - } -} - -// Helper function to wait for services to be ready -async function waitForServices() { - console.log('[ReactPress] Waiting for services to be ready...'); - - // Check MySQL connection - let attempts = 0; - const maxAttempts = 30; // 30 seconds max wait - - while (attempts < maxAttempts) { - try { - execSync('docker exec reactpress_db mysql -u reactpress -preactpress -e "SELECT 1"', { - stdio: 'ignore' - }); - console.log('[ReactPress] MySQL database is ready!'); - return true; - } catch (error) { - attempts++; - if (attempts % 5 === 0) { - console.log(`[ReactPress] Waiting for MySQL... (${attempts}/${maxAttempts})`); - } - await new Promise(resolve => setTimeout(resolve, 1000)); - } - } - - console.error('[ReactPress] MySQL database failed to start within timeout period.'); - return false; -} - -// Parse command line arguments -const args = process.argv.slice(2); -const command = args[0] || 'start'; - -switch (command) { - case 'start': - // Start Docker services and development server - console.log('[ReactPress] Starting development environment...'); - - if (!startDockerServices()) { - process.exit(1); - } - - waitForServices().then((ready) => { - if (ready) { - console.log('[ReactPress] Starting development server...'); - - // 执行构建命令 - const build = spawn('pnpm', ['build:toolkit'], { - stdio: 'inherit', - shell: true - }); - - build.on('close', (code) => { - if (code !== 0) { - console.error(`[ReactPress] Build failed with exit code: ${code}`); - process.exit(code); - } - - console.log('[ReactPress] Toolkit built successfully.'); - console.log('[ReactPress] Starting client and server in development mode...'); - console.log('[ReactPress] Access your application at: http://localhost:8080'); - - // 执行并发命令 - const concurrently = spawn('npx', [ - 'concurrently', - 'pnpm:dev:server', - 'pnpm:dev:client' - ], { - stdio: 'inherit', - shell: true, - cwd: currentWorkingDir - }); - - concurrently.on('close', (code) => { - process.exit(code); - }); - }); - } else { - console.error('[ReactPress] Failed to start services.'); - process.exit(1); - } - }); - break; - - case 'stop': - // Stop Docker services - stopDockerServices(); - break; - - case 'restart': - // Restart Docker services - console.log('[ReactPress] Restarting development environment...'); - stopDockerServices(); - - setTimeout(() => { - if (!startDockerServices()) { - process.exit(1); - } - }, 2000); - break; - - case 'status': - // Show status of Docker services - console.log('[ReactPress] Checking Docker services status...'); - try { - execSync('docker-compose -f docker-compose.dev.yml ps', { - stdio: 'inherit' - }); - } catch (error) { - console.error('[ReactPress] Error checking Docker services status:', error.message); - } - break; - - case 'logs': - // Show Docker logs - console.log('[ReactPress] Showing Docker services logs...'); - try { - const logService = args[1] || ''; - execSync(`docker-compose -f docker-compose.dev.yml logs -f ${logService}`, { - stdio: 'inherit' - }); - } catch (error) { - console.error('[ReactPress] Error showing Docker services logs:', error.message); - } - break; - - default: - console.log(` -ReactPress Docker Development Environment - -Usage: node scripts/docker-dev.js [command] - -Commands: - start Start Docker services and development server (default) - stop Stop Docker services - restart Restart Docker services - status Show status of Docker services - logs Show Docker services logs (optionally specify service: db, nginx) - -Examples: - node scripts/docker-dev.js - node scripts/docker-dev.js stop - node scripts/docker-dev.js logs db - `); - break; -} \ No newline at end of file +/** @deprecated Use `reactpress docker ` */ +const cmd = process.argv[2] || 'start'; +const map = { stop: 'down' }; +process.argv = [process.argv[0], process.argv[1], 'docker', map[cmd] || cmd, ...process.argv.slice(3)]; +require('../cli/bin/reactpress'); diff --git a/scripts/install.sh b/scripts/install.sh index f6bd8f1..5dbe7ad 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -55,6 +55,14 @@ check_dependencies() { if ! docker info > /dev/null 2>&1; then error "Docker daemon is not running. Please start Docker and run this script again." fi + + if ! command -v node &> /dev/null; then + error "Node.js >= 18 is required for @fecommunity/reactpress-cli. See https://nodejs.org/" + fi + + if ! command -v pnpm &> /dev/null; then + error "pnpm is required. Install: npm install -g pnpm" + fi log "All dependencies found!" } @@ -122,18 +130,7 @@ NEXT_PUBLIC_SITE_URL=http://localhost:8080 NODE_ENV=production EOF - # Create server-specific .env file - cat > server/.env << 'EOF' -# Server Environment Variables -DB_HOST=db -DB_PORT=3306 -DB_USER=reactpress -DB_PASSWD=reactpress -DB_DATABASE=reactpress -NODE_ENV=production -PORT=3002 -CORS_ORIGIN=http://client:3001 -EOF + # API 由 @fecommunity/reactpress-cli 管理(.reactpress/config.json + 根目录 .env) # Create toolkit-specific .env file to prevent build errors mkdir -p toolkit @@ -170,9 +167,9 @@ server { proxy_connect_timeout 300; } - # Server API (NestJS) + # Server API (host: reactpress-cli start) location /api/ { - proxy_pass http://server:3002; + proxy_pass http://host.docker.internal:3002/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; @@ -331,16 +328,28 @@ EOF info "toolkit/package.json already exists" fi - # Check client and server packages + # Check client package and CLI dependency at repo root if [ ! -f "client/package.json" ]; then error "client/package.json not found" fi - - if [ ! -f "server/package.json" ]; then - error "server/package.json not found" + + if ! grep -q '@fecommunity/reactpress-cli' package.json 2>/dev/null; then + error "Root package.json must depend on @fecommunity/reactpress-cli" fi } +# Install deps and start API via reactpress-cli on the host +start_api_via_cli() { + log "Installing Node dependencies..." + pnpm install || error "pnpm install failed" + + log "Initializing ReactPress (reactpress-cli init)..." + pnpm exec reactpress-cli init . --force 2>/dev/null || pnpm exec reactpress-cli init . + + log "Starting API (reactpress-cli start)..." + pnpm exec reactpress-cli start || warn "reactpress-cli start failed — ensure port 3002 is free and Docker MySQL (db) is reachable" +} + # Create optimized Dockerfiles that handle environment files properly update_dockerfiles() { log "Creating optimized Dockerfiles with environment handling..." @@ -399,55 +408,7 @@ USER nextjs CMD ["pnpm", "run", "start"] EOF - # Server Dockerfile - cat > server/Dockerfile << 'EOF' -# Use Node.js 18 as the base image -FROM node:18-alpine - -# Set working directory -WORKDIR /app - -# Install pnpm globally -RUN npm install -g pnpm - -# Copy ALL files from the project root (including .env files) -COPY . . - -# Debug: Show environment files -RUN echo "=== Environment Files ===" && \ - find . -name ".env*" | head -10 - -# Install all dependencies -RUN pnpm install --no-frozen-lockfile - -# Build toolkit first with environment safety -RUN echo "=== Building Toolkit ===" && \ - cd toolkit && \ - pnpm run build || (echo "Toolkit build failed, creating basic structure..." && \ - mkdir -p dist && \ - echo 'exports.config = {}; exports.messages = {}; exports.emptyConfig = {}; exports.emptyMessages = {};' > dist/index.js && \ - echo 'export const config = {}; export const messages = {}; export const emptyConfig = {}; export const emptyMessages = {};' > dist/index.d.ts) - -# Build server application -WORKDIR /app/server -RUN pnpm run build - -# Expose port -EXPOSE 3002 - -# Create a non-root user -RUN addgroup -g 1001 -S nodejs && \ - adduser -S nestjs -u 1001 - -# Change ownership of the app directory -RUN chown -R nestjs:nodejs /app -USER nestjs - -# Start the application -CMD ["pnpm", "run", "start"] -EOF - - log "Dockerfiles updated with comprehensive environment handling" + log "Dockerfiles updated (API runs on host via reactpress-cli, not in a server container)" } # Clean Docker cache @@ -489,13 +450,13 @@ build_and_start_services() { while [ $elapsed -lt $wait_time ]; do running_count=$($DOCKER_COMPOSE_CMD -f docker-compose.prod.yml ps --services --filter "status=running" | wc -l) - if [ "$running_count" -eq 4 ]; then - log "✅ All 4 services are running!" + if [ "$running_count" -ge 3 ]; then + log "✅ Docker services are running (db, client, nginx)!" break fi sleep 10 elapsed=$((elapsed + 10)) - log "Waited ${elapsed}s for services... ($running_count/4 running)" + log "Waited ${elapsed}s for services... ($running_count/3+ running)" done if [ $elapsed -ge $wait_time ]; then @@ -518,6 +479,9 @@ deploy_full_application() { # Update Dockerfiles update_dockerfiles + + # API on host (before nginx proxies /api to host.docker.internal:3002) + start_api_via_cli # Build and start services build_and_start_services @@ -545,7 +509,7 @@ show_help() { echo " Or run: ./install.sh [directory]" echo "" echo "This script automatically handles environment file issues and provides" - echo "a complete ReactPress installation with MySQL, NestJS server, Next.js client, and nginx." + echo "a complete ReactPress installation with MySQL, reactpress-cli API, Next.js client, and nginx." echo "" echo "Access your application at: http://localhost:8080" } diff --git a/scripts/reactpress-api-dev.js b/scripts/reactpress-api-dev.js new file mode 100644 index 0000000..d0213e0 --- /dev/null +++ b/scripts/reactpress-api-dev.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +/** @deprecated Use `reactpress dev --api-only` */ +process.argv = [process.argv[0], process.argv[1], 'dev', '--api-only']; +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress-api-lifecycle.js b/scripts/reactpress-api-lifecycle.js new file mode 100644 index 0000000..1417fc4 --- /dev/null +++ b/scripts/reactpress-api-lifecycle.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +/** @deprecated Use `reactpress server ` */ +const cmd = process.argv[2] || 'start'; +const map = { 'start:bg': ['server', 'start', '--bg'] }; +const extra = map[cmd] || ['server', cmd]; +process.argv = [process.argv[0], process.argv[1], ...extra]; +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress-api-pm2.js b/scripts/reactpress-api-pm2.js new file mode 100644 index 0000000..2dd49d4 --- /dev/null +++ b/scripts/reactpress-api-pm2.js @@ -0,0 +1,4 @@ +#!/usr/bin/env node +/** @deprecated Use `reactpress server start --pm2` */ +process.argv = [process.argv[0], process.argv[1], 'server', 'start', '--pm2']; +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress-bootstrap.js b/scripts/reactpress-bootstrap.js new file mode 100644 index 0000000..0305718 --- /dev/null +++ b/scripts/reactpress-bootstrap.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node +/** @deprecated Use `reactpress init` */ +const force = process.argv.includes('--force'); +process.argv = [process.argv[0], process.argv[1], 'init', '.', ...(force ? ['--force'] : [])]; +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress-cli.js b/scripts/reactpress-cli.js index 07cf15e..5b5b902 100755 --- a/scripts/reactpress-cli.js +++ b/scripts/reactpress-cli.js @@ -1,99 +1,3 @@ #!/usr/bin/env node - -/** - * ReactPress CLI - Unified entry point for ReactPress commands - * This script allows users to run reactpress commands after installing the package globally - */ - -const { Command } = require('commander'); -const { spawn } = require('child_process'); -const path = require('path'); -const chalk = require('chalk'); - -// Create the CLI program -const program = new Command(); - -// Get the directory where this script is located -const binDir = __dirname; -const rootDir = path.join(binDir, '..'); - -// Server command with subcommands -const serverCmd = program - .command('server') - .description('Manage the ReactPress server'); - -serverCmd - .command('start') - .description('Start the ReactPress server (equivalent to running npx @fecommunity/reactpress-server)') - .option('--pm2', 'Start server with PM2 process manager') - .action((options) => { - const serverScript = path.join(rootDir, 'server', 'bin', 'reactpress-server.js'); - - let args = []; - if (options.pm2) { - args.push('--pm2'); - } - - const serverProcess = spawn('node', [serverScript, ...args], { - stdio: 'inherit' - }); - - serverProcess.on('error', (error) => { - console.error(chalk.red('[ReactPress CLI] Failed to start server:'), error); - process.exit(1); - }); - }); - -// Client command with subcommands -const clientCmd = program - .command('client') - .description('Manage the ReactPress client'); - -clientCmd - .command('start') - .description('Start the ReactPress client (equivalent to running npx @fecommunity/reactpress-client)') - .option('--pm2', 'Start client with PM2 process manager') - .action((options) => { - const clientScript = path.join(rootDir, 'client', 'bin', 'reactpress-client.js'); - - let args = []; - if (options.pm2) { - args.push('--pm2'); - } - - const clientProcess = spawn('node', [clientScript, ...args], { - stdio: 'inherit' - }); - - clientProcess.on('error', (error) => { - console.error(chalk.red('[ReactPress CLI] Failed to start client:'), error); - process.exit(1); - }); - }); - -// Set the version from package.json -const packageJson = require(path.join(rootDir, 'package.json')); -program.version(packageJson.version); - -// Configure help text -program.on('--help', () => { - console.log(''); - console.log('Examples:'); - console.log(' $ reactpress server start # Start the ReactPress server'); - console.log(' $ reactpress client start # Start the ReactPress client'); - console.log(' $ reactpress server start --pm2 # Start server with PM2'); - console.log(' $ reactpress client start --pm2 # Start client with PM2'); - console.log(''); - console.log('Note: The "start" command is implicit in the individual bin scripts.'); - console.log('The CLI provides a consistent interface for all commands.'); - console.log(''); - console.log('For more information, visit: https://github.com/fecommunity/reactpress'); -}); - -// Parse the command line arguments -program.parse(process.argv); - -// If no command is provided, show help -if (!process.argv.slice(2).length) { - program.outputHelp(); -} \ No newline at end of file +/** @deprecated Use `reactpress` or `node ./cli/bin/reactpress.js` */ +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress-dev.js b/scripts/reactpress-dev.js index 667983a..354c002 100644 --- a/scripts/reactpress-dev.js +++ b/scripts/reactpress-dev.js @@ -1,39 +1,4 @@ -// scripts/dev.js -const { spawn } = require('child_process'); -const path = require('path'); - -// 获取当前工作目录 -const currentWorkingDir = process.cwd(); - -// 设置环境变量 -process.env.REACTPRESS_ORIGINAL_CWD = currentWorkingDir; - -console.log(`设置 REACTPRESS_ORIGINAL_CWD: ${currentWorkingDir}`); - -// 执行构建命令 -const build = spawn('pnpm', ['build:toolkit'], { - stdio: 'inherit', - shell: true -}); - -build.on('close', (code) => { - if (code !== 0) { - console.error(`构建失败,退出码: ${code}`); - process.exit(code); - } - - // 执行并发命令 - const concurrently = spawn('npx', [ - 'concurrently', - 'pnpm:dev:server', - 'pnpm:dev:client' - ], { - stdio: 'inherit', - shell: true, - cwd: currentWorkingDir - }); - - concurrently.on('close', (code) => { - process.exit(code); - }); -}); \ No newline at end of file +#!/usr/bin/env node +/** @deprecated Use `reactpress dev` */ +process.argv = [process.argv[0], process.argv[1], 'dev']; +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress-publish.js b/scripts/reactpress-publish.js index 1b236b4..95e2349 100644 --- a/scripts/reactpress-publish.js +++ b/scripts/reactpress-publish.js @@ -1,945 +1,6 @@ #!/usr/bin/env node - -const { execSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const chalk = require('chalk'); -const inquirer = require('inquirer'); -const crypto = require('crypto'); - -// Package build order (dependencies first) -const packages = [ - { - name: '@fecommunity/reactpress', - path: '.', - description: 'Main ReactPress package' - }, - { - name: '@fecommunity/reactpress-server', - path: 'server', - description: 'Backend API server package' - }, - { - name: '@fecommunity/reactpress-client', - path: 'client', - description: 'Frontend application package' - }, - { - name: '@fecommunity/reactpress-toolkit', - path: 'toolkit', - description: 'API client and utilities toolkit' - }, - { - name: '@fecommunity/reactpress-template-hello-world', - path: 'templates/hello-world', - description: 'Hello World template for ReactPress' - }, - { - name: '@fecommunity/reactpress-template-twentytwentyfive', - path: 'templates/twentytwentyfive', - description: 'Twenty Twenty Five blog template for ReactPress' - } -]; - -// Generate a hash for a file or directory -function generateHash(filePath) { - try { - if (fs.statSync(filePath).isDirectory()) { - const files = fs.readdirSync(filePath); - const hashes = files - .filter(file => !file.startsWith('.') && file !== 'node_modules' && file !== 'dist' && file !== '.next') // Ignore hidden files and build directories - .map(file => generateHash(path.join(filePath, file))) - .sort(); - return crypto.createHash('md5').update(hashes.join('')).digest('hex'); - } else { - const content = fs.readFileSync(filePath); - return crypto.createHash('md5').update(content).digest('hex'); - } - } catch (error) { - return ''; - } -} - -// Get package content hash -function getPackageHash(packagePath) { - const fullPath = path.join(process.cwd(), packagePath); - return generateHash(fullPath); -} - -// Check if package has meaningful changes (for build) -function hasMeaningfulChangesForBuild(packagePath, packageName) { - try { - // Create a hash file path to store previous hash in node_modules - const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`); - - // Generate current hash - const currentHash = getPackageHash(packagePath); - - // Check if we have a previous hash - if (fs.existsSync(hashFilePath)) { - const previousHash = fs.readFileSync(hashFilePath, 'utf8').trim(); - return currentHash !== previousHash; - } - - // If no previous hash, consider it as having changes - return true; - } catch (error) { - console.log(chalk.yellow(`⚠️ Could not check changes for ${packageName}, assuming changes exist`)); - return true; - } -} - -// Check if package has meaningful changes (for publish) -function hasMeaningfulChangesForPublish(packagePath, packageName) { - try { - // Create a hash file path to store previous hash in node_modules - const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`); - - // Generate current hash - const currentHash = getPackageHash(packagePath); - - // Check if we have a previous hash - if (fs.existsSync(hashFilePath)) { - const previousHash = fs.readFileSync(hashFilePath, 'utf8').trim(); - return currentHash !== previousHash; - } - - // If no previous hash, consider it as having changes - return true; - } catch (error) { - console.log(chalk.yellow(`⚠️ Could not check changes for ${packageName}, assuming changes exist`)); - return true; - } -} - -// Save package hash (for build) -function savePackageHashForBuild(packagePath, packageName) { - try { - const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`); - - // Ensure cache directory exists - const cacheDir = path.dirname(hashFilePath); - if (!fs.existsSync(cacheDir)) { - fs.mkdirSync(cacheDir, { recursive: true }); - } - - // Generate and save current hash - const currentHash = getPackageHash(packagePath); - fs.writeFileSync(hashFilePath, currentHash); - } catch (error) { - console.log(chalk.yellow(`⚠️ Could not save hash for ${packageName}`)); - } -} - -// Save package hash (for publish) -function savePackageHashForPublish(packagePath, packageName) { - try { - const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`); - - // Ensure cache directory exists - const cacheDir = path.dirname(hashFilePath); - if (!fs.existsSync(cacheDir)) { - fs.mkdirSync(cacheDir, { recursive: true }); - } - - // Generate and save current hash - const currentHash = getPackageHash(packagePath); - fs.writeFileSync(hashFilePath, currentHash); - } catch (error) { - console.log(chalk.yellow(`⚠️ Could not save hash for ${packageName}`)); - } -} - -// Get current versions -function getCurrentVersion(packagePath) { - try { - const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - return pkg.version; - } catch (error) { - return 'unknown'; - } -} - -// Increment version based on type -function incrementVersion(version, type) { - const parts = version.split('-')[0].split('.'); - const major = parseInt(parts[0]); - const minor = parseInt(parts[1]); - const patch = parseInt(parts[2]); - - switch (type) { - case 'major': - return `${major + 1}.0.0`; - case 'minor': - return `${major}.${minor + 1}.0`; - case 'patch': - return `${major}.${minor}.${patch + 1}`; - case 'beta': - // For beta, we increment the beta number or add beta.1 if not present - const match = version.match(/^(.*)-beta\.(\d+)$/); - if (match) { - const baseVersion = match[1]; - const betaNumber = parseInt(match[2]); - return `${baseVersion}-beta.${betaNumber + 1}`; - } else { - // If no beta version exists, add beta.1 - return `${version}-beta.1`; - } - case 'alpha': - // For alpha, we increment the alpha number or add alpha.1 if not present - const alphaMatch = version.match(/^(.*)-alpha\.(\d+)$/); - if (alphaMatch) { - const baseVersion = alphaMatch[1]; - const alphaNumber = parseInt(alphaMatch[2]); - return `${baseVersion}-alpha.${alphaNumber + 1}`; - } else { - // If no alpha version exists, add alpha.1 - return `${version}-alpha.1`; - } - default: - return version; - } -} - -// Get next available version from npm registry -function getNextAvailableVersion(packageName, currentVersion, versionType) { - try { - // First, increment the version locally - let nextVersion = incrementVersion(currentVersion, versionType); - - // Check if this version already exists on npm - let versionExists = true; - let attempts = 0; - const maxAttempts = 100; // Prevent infinite loop - - while (versionExists && attempts < maxAttempts) { - try { - execSync(`npm view ${packageName}@${nextVersion} version`, { stdio: 'ignore' }); - // If we get here, the version exists, so we need to increment again - nextVersion = incrementVersion(nextVersion, versionType); - attempts++; - } catch (error) { - // If we get an error, the version doesn't exist, which is what we want - versionExists = false; - } - } - - if (attempts >= maxAttempts) { - throw new Error('Too many attempts to find available version'); - } - - return nextVersion; - } catch (error) { - // Fallback to simple increment if npm view fails - console.log(chalk.yellow(`⚠️ Could not check npm registry, using local increment for ${packageName}`)); - return incrementVersion(currentVersion, versionType); - } -} - -// Update package version -function updateVersion(packagePath, newVersion) { - console.log(chalk.blue(`\n✏️ Updating version to ${newVersion}...`)); - - const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - - const oldVersion = pkg.version; - pkg.version = newVersion; - - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log(chalk.green(`✅ Version updated from ${oldVersion} to ${newVersion}`)); -} - -// Fix workspace dependencies for build -function fixWorkspaceDependenciesForBuild(packagePath) { - console.log(chalk.blue(`🔧 Fixing workspace dependencies for build: ${packagePath}...`)); - - const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - - // Fix dependencies - const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; - - depTypes.forEach(depType => { - if (pkg[depType]) { - Object.keys(pkg[depType]).forEach(depName => { - // Check if it's a workspace dependency - if (pkg[depType][depName] === 'workspace:*' || pkg[depType][depName].startsWith('workspace:')) { - // For build purposes, we'll use the file: protocol to reference local packages - const depPackage = packages.find(p => p.name === depName); - if (depPackage) { - console.log(chalk.gray(` Replacing ${depName} workspace dependency with file reference`)); - pkg[depType][depName] = `file:../${depPackage.path}`; - } - } - }); - } - }); - - // Write the updated package.json - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log(chalk.green(`✅ Workspace dependencies fixed for build: ${packagePath}`)); -} - -// Restore workspace dependencies after build -function restoreWorkspaceDependenciesAfterBuild(packagePath) { - console.log(chalk.blue(`🔄 Restoring workspace dependencies after build: ${packagePath}...`)); - - const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - - // Restore dependencies - const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; - - depTypes.forEach(depType => { - if (pkg[depType]) { - Object.keys(pkg[depType]).forEach(depName => { - // Check if this is one of our internal packages referenced with file: - const depPackage = packages.find(p => p.name === depName); - if (depPackage && pkg[depType][depName].startsWith('file:')) { - console.log(chalk.gray(` Restoring ${depName} to workspace dependency`)); - pkg[depType][depName] = 'workspace:*'; - } - }); - } - }); - - // Write the updated package.json - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log(chalk.green(`✅ Workspace dependencies restored after build: ${packagePath}`)); -} - -// Fix workspace dependencies for publish -function fixWorkspaceDependenciesForPublish(packagePath, packageVersions) { - console.log(chalk.blue(`🔧 Fixing workspace dependencies for publish: ${packagePath}...`)); - - const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - - // Fix dependencies - const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; - - depTypes.forEach(depType => { - if (pkg[depType]) { - Object.keys(pkg[depType]).forEach(depName => { - // Check if it's a workspace dependency - if (pkg[depType][depName] === 'workspace:*' || pkg[depType][depName].startsWith('workspace:')) { - // Replace with actual version - const depPackage = packages.find(p => p.name === depName); - if (depPackage && packageVersions[depName]) { - console.log(chalk.gray(` Replacing ${depName} workspace dependency with version ${packageVersions[depName]}`)); - pkg[depType][depName] = packageVersions[depName]; - } - } - }); - } - }); - - // Write the updated package.json - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log(chalk.green(`✅ Workspace dependencies fixed for publish: ${packagePath}`)); -} - -// Restore workspace dependencies after publish -function restoreWorkspaceDependenciesAfterPublish(packagePath) { - console.log(chalk.blue(`🔄 Restoring workspace dependencies for ${packagePath}...`)); - - const pkgPath = path.join(process.cwd(), packagePath, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - - // Restore dependencies - const depTypes = ['dependencies', 'devDependencies', 'peerDependencies']; - - depTypes.forEach(depType => { - if (pkg[depType]) { - Object.keys(pkg[depType]).forEach(depName => { - // Check if this is one of our internal packages - const depPackage = packages.find(p => p.name === depName); - if (depPackage) { - console.log(chalk.gray(` Restoring ${depName} to workspace dependency`)); - pkg[depType][depName] = 'workspace:*'; - } - }); - } - }); - - // Write the updated package.json - fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log(chalk.green(`✅ Workspace dependencies restored for ${packagePath}`)); -} - -// Build package -function buildPackage(pkg) { - console.log(chalk.blue(`\n🔨 Building ${pkg.name} (${pkg.description})...`)); - - try { - // Fix workspace dependencies for build - fixWorkspaceDependenciesForBuild(pkg.path); - - try { - if (pkg.path === 'config') { - execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); - } else if (pkg.path === 'server') { - execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); - } else if (pkg.path === 'client') { - execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); - } else if (pkg.path === 'toolkit') { - execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' }); - } else if (pkg.path === 'templates/hello-world' || pkg.path === 'templates/twentytwentyfive') { - // Templates don't need to be built, just validate package.json - console.log(chalk.gray(' Templates do not require building, skipping...')); - } - console.log(chalk.green(`✅ ${pkg.name} built successfully`)); - } finally { - // Always restore workspace dependencies - restoreWorkspaceDependenciesAfterBuild(pkg.path); - } - } catch (error) { - console.log(chalk.red(`❌ Failed to build ${pkg.name}`)); - throw error; - } -} - -// Publish package -function publishPackage(packagePath, packageName, tag = 'latest') { - console.log(chalk.blue(`\n🚀 Publishing ${packageName} with tag ${tag}...`)); - - try { - const command = `pnpm publish --access public --tag ${tag} --registry https://registry.npmjs.org --no-git-checks`; - execSync(command, { cwd: path.join(process.cwd(), packagePath), stdio: 'inherit' }); - console.log(chalk.green(`✅ ${packageName} published successfully!`)); - } catch (error) { - console.log(chalk.red(`❌ Failed to publish ${packageName}`)); - throw error; - } -} - -// Create GitHub release -function createGitHubRelease(tagName, releaseNotes) { - console.log(chalk.blue(`\n📝 Creating GitHub release ${tagName}...`)); - - try { - // Create release using GitHub CLI if available - const command = `gh release create ${tagName} --title "${tagName}" --notes "${releaseNotes}"`; - execSync(command, { stdio: 'inherit' }); - console.log(chalk.green(`✅ GitHub release ${tagName} created successfully!`)); - } catch (error) { - console.log(chalk.yellow(`⚠️ Failed to create GitHub release (GitHub CLI may not be installed or configured)`)); - console.log(chalk.gray('You can manually create the release at: https://github.com/fecommunity/reactpress/releases/new')); - } -} - -// Check environment -function checkEnvironment() { - // Check if pnpm is installed - try { - execSync('pnpm --version', { stdio: 'ignore' }); - } catch (error) { - console.log(chalk.red('❌ pnpm is not installed. Please install pnpm first.')); - return false; - } - - // Check if logged in to npm - try { - execSync('pnpm whoami --registry https://registry.npmjs.org', { stdio: 'ignore' }); - } catch (error) { - console.log(chalk.red('❌ Not logged in to npm. Please run "pnpm login --registry https://registry.npmjs.org" first.')); - return false; - } - - return true; -} - -// Build packages function -async function buildPackages() { - console.log(chalk.blue('🏗️ ReactPress Package Builder\n')); - - // Show current versions - console.log(chalk.cyan('📋 Current package versions:')); - packages.forEach(pkg => { - const version = getCurrentVersion(pkg.path); - console.log(chalk.gray(` ${pkg.name}: ${version}`)); - }); - console.log(); - - try { - // Track which packages actually need to be built - const packagesToBuild = []; - - // Check for meaningful changes in each package - for (const pkg of packages) { - if (fs.existsSync(path.join(process.cwd(), pkg.path))) { - if (hasMeaningfulChangesForBuild(pkg.path, pkg.name)) { - packagesToBuild.push(pkg); - console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be built`)); - } else { - console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); - } - } else { - console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); - } - } - - if (packagesToBuild.length === 0) { - console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to build!')); - return; - } - - // Build packages that have changes - for (const pkg of packagesToBuild) { - await buildPackage(pkg); - // Save the hash after successful build - savePackageHashForBuild(pkg.path, pkg.name); - } - - console.log(chalk.green(`\n🎉 ${packagesToBuild.length} package(s) built successfully!`)); - } catch (error) { - console.error(chalk.red('❌ Build failed:'), error); - process.exit(1); - } -} - -// Publish packages function -async function publishPackages() { - // Check if called with --no-build flag - const noBuild = process.argv.includes('--no-build'); - - console.log(chalk.blue('📦 ReactPress Package Publisher\n')); - - // Run environment checks - if (!checkEnvironment()) { - process.exit(1); - } - - // Show current versions - console.log(chalk.cyan('📋 Current package versions:')); - packages.forEach(pkg => { - const version = getCurrentVersion(pkg.path); - console.log(chalk.gray(` ${pkg.name}: ${version}`)); - }); - console.log(); - - // Ask for publishing options - const { action } = await inquirer.prompt([ - { - type: 'list', - name: 'action', - message: 'What would you like to do?', - choices: [ - { name: '🚀 Publish all packages with version bump', value: 'publish-all' }, - { name: '📦 Publish specific package', value: 'publish-one' }, - { name: '🔨 Build all packages only', value: 'build-all' }, - { name: '🏷️ Publish as beta/alpha', value: 'publish-prerelease' }, - { name: '❌ Cancel', value: 'cancel' } - ] - } - ]); - - if (action === 'cancel') { - console.log(chalk.yellow('Operation cancelled.')); - return; - } - - if (action === 'build-all') { - console.log(chalk.blue('🔨 Building all packages...\n')); - - // Track which packages actually need to be built - const packagesToBuild = []; - - // Check for meaningful changes in each package - for (const pkg of packages) { - if (fs.existsSync(path.join(process.cwd(), pkg.path))) { - if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) { - packagesToBuild.push(pkg); - console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be built`)); - } else { - console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); - } - } else { - console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); - } - } - - if (packagesToBuild.length === 0) { - console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to build!')); - return; - } - - // Build packages that have changes - for (const pkg of packagesToBuild) { - buildPackage(pkg); - // Save the hash after successful build - savePackageHashForPublish(pkg.path, pkg.name); - } - - console.log(chalk.green(`\n🎉 ${packagesToBuild.length} package(s) built successfully!`)); - return; - } - - if (action === 'publish-one') { - const { selectedPackage } = await inquirer.prompt([ - { - type: 'list', - name: 'selectedPackage', - message: 'Which package would you like to publish?', - choices: packages.map(pkg => ({ - name: `${pkg.name} (${pkg.description})`, - value: pkg - })) - } - ]); - - // Check if the selected package has meaningful changes - if (!hasMeaningfulChangesForPublish(selectedPackage.path, selectedPackage.name)) { - console.log(chalk.gray(`\n⏭️ ${selectedPackage.name} has no meaningful changes, skipping...`)); - console.log(chalk.green('✅ Nothing to publish!')); - return; - } - - const { versionType } = await inquirer.prompt([ - { - type: 'list', - name: 'versionType', - message: 'Version bump type:', - choices: [ - { name: 'Beta (1.0.0-beta.1 -> 1.0.0-beta.2)', value: 'beta' }, - { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' }, - { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' }, - { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' }, - { name: 'Custom version', value: 'custom' } - ] - } - ]); - - let newVersion; - const currentVersion = getCurrentVersion(selectedPackage.path); - - if (versionType === 'custom') { - const { customVersion } = await inquirer.prompt([ - { - type: 'input', - name: 'customVersion', - message: `Enter new version for ${selectedPackage.name} (current: ${currentVersion}):`, - validate: (input) => { - const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/; - return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)'; - } - } - ]); - newVersion = customVersion; - } else { - newVersion = getNextAvailableVersion(selectedPackage.name, currentVersion, versionType); - } - - // Get all package versions for dependency resolution - const packageVersions = {}; - packages.forEach(pkg => { - packageVersions[pkg.name] = getCurrentVersion(pkg.path); - }); - // Update the selected package version - packageVersions[selectedPackage.name] = newVersion; - - // Fix workspace dependencies before publishing - fixWorkspaceDependenciesForPublish(selectedPackage.path, packageVersions); - - try { - updateVersion(selectedPackage.path, newVersion); - // Only build if not disabled - if (!noBuild) { - buildPackage(selectedPackage); - } - - // Determine tag based on version type - const tag = versionType === 'beta' ? 'beta' : 'latest'; - publishPackage(selectedPackage.path, selectedPackage.name, tag); - - // Save the hash after successful publish - savePackageHashForPublish(selectedPackage.path, selectedPackage.name); - - console.log(chalk.green(`\n🎉 ${selectedPackage.name} v${newVersion} published successfully!`)); - } finally { - // Always restore workspace dependencies - restoreWorkspaceDependenciesAfterPublish(selectedPackage.path); - } - - return; - } - - if (action === 'publish-prerelease') { - const { tag } = await inquirer.prompt([ - { - type: 'list', - name: 'tag', - message: 'Select prerelease tag:', - choices: ['beta', 'alpha', 'rc', 'next'] - } - ]); - - const { versionType } = await inquirer.prompt([ - { - type: 'list', - name: 'versionType', - message: 'Version bump type:', - choices: [ - { name: `Prerelease (${tag})`, value: tag }, - { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' }, - { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' }, - { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' }, - { name: 'Custom version', value: 'custom' } - ] - } - ]); - - // Get all package versions for dependency resolution - const packageVersions = {}; - packages.forEach(pkg => { - const currentVersion = getCurrentVersion(pkg.path); - packageVersions[pkg.name] = currentVersion; - }); - - // Track which packages actually need to be published - const packagesToPublish = []; - - // Check for meaningful changes in each package - for (const pkg of packages) { - if (!fs.existsSync(path.join(process.cwd(), pkg.path))) { - console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); - continue; - } - - if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) { - packagesToPublish.push(pkg); - console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be published`)); - } else { - console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); - } - } - - if (packagesToPublish.length === 0) { - console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to publish!')); - return; - } - - // Process each package that has changes - for (const pkg of packagesToPublish) { - let newVersion; - const currentVersion = getCurrentVersion(pkg.path); - - if (versionType === 'custom') { - const { customVersion } = await inquirer.prompt([ - { - type: 'input', - name: 'customVersion', - message: `Enter version for ${pkg.name} (current: ${currentVersion}):`, - default: currentVersion - } - ]); - newVersion = customVersion; - } else { - newVersion = getNextAvailableVersion(pkg.name, currentVersion, versionType); - } - - // Update package version in our tracking - packageVersions[pkg.name] = newVersion; - - // Fix workspace dependencies before publishing - fixWorkspaceDependenciesForPublish(pkg.path, packageVersions); - - try { - updateVersion(pkg.path, newVersion); - // Only build if not disabled - if (!noBuild) { - buildPackage(pkg); - } - publishPackage(pkg.path, pkg.name, tag); - - // Save the hash after successful publish - savePackageHashForPublish(pkg.path, pkg.name); - } finally { - // Always restore workspace dependencies - restoreWorkspaceDependenciesAfterPublish(pkg.path); - } - } - - console.log(chalk.green(`\n🎉 ${packagesToPublish.length} package(s) published with ${tag} tag!`)); - return; - } - - if (action === 'publish-all') { - // Check if we're on master branch for final release - let isMasterBranch = false; - try { - const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim(); - isMasterBranch = branch === 'master' || branch === 'main'; - } catch (error) { - console.log(chalk.yellow('⚠️ Unable to determine current branch')); - } - - const { versionType } = await inquirer.prompt([ - { - type: 'list', - name: 'versionType', - message: 'Version bump type:', - choices: [ - { name: `Beta ${isMasterBranch ? '(will publish as final)' : '(will publish as beta)'}`, value: 'beta' }, - { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' }, - { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' }, - { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' }, - { name: 'Custom version', value: 'custom' } - ] - } - ]); - - // Get all package versions for dependency resolution - const packageVersions = {}; - const originalVersions = {}; - - packages.forEach(pkg => { - const currentVersion = getCurrentVersion(pkg.path); - originalVersions[pkg.name] = currentVersion; - packageVersions[pkg.name] = currentVersion; - }); - - let baseVersion; - if (versionType === 'custom') { - const { customVersion } = await inquirer.prompt([ - { - type: 'input', - name: 'customVersion', - message: 'Enter new version for all packages:', - validate: (input) => { - const semverRegex = /^\d+\.\d+\.\d+$/; - return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)'; - } - } - ]); - baseVersion = customVersion; - } else { - // Use the highest current version as base and increment - const nextVersion = getNextAvailableVersion(packages[0].name, originalVersions[packages[0].name], versionType); - baseVersion = nextVersion; - } - - console.log(chalk.cyan(`\n📋 Will publish all packages with version: ${baseVersion}\n`)); - - const { confirm } = await inquirer.prompt([ - { - type: 'confirm', - name: 'confirm', - message: 'Are you sure you want to proceed?', - default: false - } - ]); - - if (!confirm) { - console.log(chalk.yellow('Operation cancelled.')); - return; - } - - // Track which packages actually need to be published - const packagesToPublish = []; - - // Check for meaningful changes in each package - for (const pkg of packages) { - if (!fs.existsSync(path.join(process.cwd(), pkg.path))) { - console.log(chalk.yellow(`⚠️ Package ${pkg.name} directory not found, skipping...`)); - continue; - } - - if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) { - packagesToPublish.push(pkg); - console.log(chalk.blue(`\n📦 ${pkg.name} has changes, will be published`)); - } else { - console.log(chalk.gray(`\n⏭️ ${pkg.name} has no meaningful changes, skipping...`)); - } - } - - if (packagesToPublish.length === 0) { - console.log(chalk.green('\n✅ No packages have meaningful changes. Nothing to publish!')); - return; - } - - // Update versions, build and publish only packages with changes - for (const pkg of packagesToPublish) { - console.log(chalk.blue(`\n📦 Processing ${pkg.name}...`)); - - // For publish-all, we use the same version for all packages - const pkgVersion = baseVersion; - packageVersions[pkg.name] = pkgVersion; - - // Fix workspace dependencies before publishing - fixWorkspaceDependenciesForPublish(pkg.path, packageVersions); - - try { - updateVersion(pkg.path, pkgVersion); - // Only build if not disabled - if (!noBuild) { - buildPackage(pkg); - } - - // Determine tag based on version type and branch - const tag = (versionType === 'beta' && !isMasterBranch) ? 'beta' : 'latest'; - publishPackage(pkg.path, pkg.name, tag); - - // Save the hash after successful publish - savePackageHashForPublish(pkg.path, pkg.name); - } finally { - // Always restore workspace dependencies - restoreWorkspaceDependenciesAfterPublish(pkg.path); - } - } - - // Create GitHub release if on master and we actually published something - if (isMasterBranch && packagesToPublish.length > 0) { - const tagName = `v${baseVersion}`; - const releaseNotes = `Release ${baseVersion} - -Packages released: -${Object.entries(packageVersions).map(([name, version]) => `- ${name}@${version}`).join('\n')}`; - createGitHubRelease(tagName, releaseNotes); - } - - console.log(chalk.green(`\n🎉 ${packagesToPublish.length} package(s) published successfully with version ${baseVersion}!`)); - console.log(chalk.cyan('\n📋 Next steps:')); - console.log(chalk.gray('1. Create a git tag: git tag v' + baseVersion)); - console.log(chalk.gray('2. Push changes: git push && git push --tags')); - } -} - -// Main function -async function main() { - // Check command line arguments - const args = process.argv.slice(2); - - if (args.includes('--publish')) { - // When called with --publish, start the publish process - console.log(chalk.blue('🏗️ ReactPress Package Builder\n')); - - // Show current versions - console.log(chalk.cyan('📋 Current package versions:')); - packages.forEach(pkg => { - const version = getCurrentVersion(pkg.path); - console.log(chalk.gray(` ${pkg.name}: ${version}`)); - }); - console.log(); - - // Start the publish process - await publishPackages(); - } else if (args.includes('--build')) { - // When called with --build, just build packages - await buildPackages(); - } else { - // Default behavior - show help - console.log(chalk.blue('🏗️ ReactPress CLI\n')); - console.log('Usage:'); - console.log(' node scripts/reactpress-cli.js --build Build all packages'); - console.log(' node scripts/reactpress-cli.js --publish Publish packages (interactive)'); - console.log(''); - } -} - -main().catch(error => { - console.error(chalk.red('❌ Operation failed:'), error); - process.exit(1); -}); \ No newline at end of file +/** @deprecated Use `reactpress publish` */ +const args = process.argv.slice(2); +const flag = args.includes('--build') ? '--build' : '--publish'; +process.argv = [process.argv[0], process.argv[1], 'publish', flag]; +require('../cli/bin/reactpress'); diff --git a/scripts/reactpress.js b/scripts/reactpress.js deleted file mode 100755 index eccf8df..0000000 --- a/scripts/reactpress.js +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env node - -/** - * ReactPress CLI - Main entry point for all ReactPress commands - * This script allows users to run reactpress commands after installing the package globally - */ - -const { Command } = require('commander'); -const { spawn } = require('child_process'); -const path = require('path'); -const chalk = require('chalk'); - -// Create the CLI program -const program = new Command(); - -// Get the directory where this script is located -const binDir = __dirname; -const rootDir = path.join(binDir, '..'); - -// Server command -program - .command('server') - .description('Manage the ReactPress server') - .option('install', 'Launch the installation wizard') - .option('start', 'Start the ReactPress server') - .option('--pm2', 'Start server with PM2 process manager') - .action((options) => { - const serverScript = path.join(rootDir, 'scripts', 'reactpress-server.js'); - - let args = []; - if (options.install) { - args.push('install'); - } else if (options.start) { - args.push('start'); - } - - if (options.pm2) { - args.push('--pm2'); - } - - const serverProcess = spawn('node', [serverScript, ...args], { - stdio: 'inherit' - }); - - serverProcess.on('error', (error) => { - console.error(chalk.red('[ReactPress CLI] Failed to start server:'), error); - process.exit(1); - }); - }); - -// Client command -program - .command('client') - .description('Manage the ReactPress client') - .option('--pm2', 'Start client with PM2 process manager') - .action((options) => { - const clientScript = path.join(rootDir, 'client', 'bin', 'reactpress-client.js'); - - let args = []; - if (options.pm2) { - args.push('--pm2'); - } - - const clientProcess = spawn('node', [clientScript, ...args], { - stdio: 'inherit' - }); - - clientProcess.on('error', (error) => { - console.error(chalk.red('[ReactPress CLI] Failed to start client:'), error); - process.exit(1); - }); - }); - -// Set the version from package.json -const packageJson = require(path.join(rootDir, 'package.json')); -program.version(packageJson.version); - -// Configure help text -program.on('--help', () => { - console.log(''); - console.log('For more information, visit: https://github.com/fecommunity/reactpress'); -}); - -// Parse the command line arguments -program.parse(process.argv); - -// If no command is provided, show help -if (!process.argv.slice(2).length) { - program.outputHelp(); -} \ No newline at end of file diff --git a/scripts/smoke-api-health.mjs b/scripts/smoke-api-health.mjs new file mode 100644 index 0000000..1d0b8ac --- /dev/null +++ b/scripts/smoke-api-health.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * API 冒烟:要求 API 已启动且 .env 已配置 + * 用法: node scripts/smoke-api-health.mjs + */ +import http from 'http'; + +const base = (process.env.SERVER_SITE_URL || 'http://127.0.0.1:3002').replace(/\/$/, ''); +const prefix = (process.env.SERVER_API_PREFIX || '/api').replace(/\/$/, ''); +const url = `${base}${prefix}/health`; + +const body = await new Promise((resolve, reject) => { + const req = http.get(url, (res) => { + let data = ''; + res.on('data', (c) => (data += c)); + res.on('end', () => resolve({ statusCode: res.statusCode, data })); + }); + req.on('error', reject); + req.setTimeout(5000, () => { + req.destroy(); + reject(new Error('timeout')); + }); +}); + +if (body.statusCode !== 200) { + console.error(`[smoke] HTTP ${body.statusCode} from ${url}`); + process.exit(1); +} + +let json; +try { + json = JSON.parse(body.data); +} catch { + console.error('[smoke] invalid JSON'); + process.exit(1); +} + +const health = json.data || json; +if (health.status !== 'ok' && health.status !== 'degraded') { + console.error('[smoke] unexpected health payload', json); + process.exit(1); +} + +console.log(`[smoke] OK ${url} → status=${health.status} database=${health.database}`); +process.exit(0); diff --git a/server/bin/reactpress-server.js b/server/bin/reactpress-server.js index ca6c136..c6121a1 100755 --- a/server/bin/reactpress-server.js +++ b/server/bin/reactpress-server.js @@ -26,16 +26,16 @@ if (showHelp) { ReactPress Server - NestJS-based backend API for ReactPress CMS Usage: - npx @fecommunity/reactpress-server [options] + reactpress-server [options] + (通常由 reactpress-cli start 调用) Options: --pm2 Start server with PM2 process manager --help, -h Show this help message Examples: - npx @fecommunity/reactpress-server # Start server normally - npx @fecommunity/reactpress-server --pm2 # Start server with PM2 - npx @fecommunity/reactpress-server --help # Show this help message + node bin/reactpress-server.js # Start server normally + node bin/reactpress-server.js --pm2 # Start server with PM2 `); process.exit(0); } diff --git a/server/package.json b/server/package.json index 3b1ae03..c5bcf32 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,7 @@ { "name": "@fecommunity/reactpress-server", - "version": "1.0.0-beta.16", + "version": "3.0.0", + "deprecated": "请使用 @fecommunity/reactpress-cli 内置 API,3.1 起停止发布", "description": "ReactPress Server - NestJS-based backend API for ReactPress CMS", "author": "fecommunity", "license": "MIT", @@ -67,6 +68,7 @@ "@nestjs/passport": "^6.1.1", "@nestjs/platform-express": "^6.11.5", "@nestjs/swagger": "^4.8.2", + "@nestjs/typeorm": "^6.3.4", "@types/express-serve-static-core": "^4.19.5", "ali-oss": "^6.5.1", "axios": "^0.23.0", @@ -109,7 +111,6 @@ "devDependencies": { "@nestjs/schematics": "^6.7.0", "@nestjs/testing": "^6.7.1", - "@nestjs/typeorm": "^6.3.4", "@types/express": "4.17.18", "@types/jest": "^24.0.18", "@types/node": "^12.7.5", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 8cb3919..e7c035b 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -42,6 +42,12 @@ import { UserModule } from './modules/user/user.module'; import { View } from './modules/view/view.entity'; // 访问统计模块 import { ViewModule } from './modules/view/view.module'; +import { ApiKey } from './modules/api-key/api-key.entity'; +import { ApiKeyModule } from './modules/api-key/api-key.module'; +import { ArticleRevision } from './modules/article/article-revision.entity'; +import { HealthModule } from './modules/health/health.module'; +import { Webhook } from './modules/webhook/webhook.entity'; +import { WebhookModule } from './modules/webhook/webhook.module'; @Module({ imports: [ @@ -51,7 +57,23 @@ import { ViewModule } from './modules/view/view.module'; inject: [ConfigService], useFactory: async (configService: ConfigService) => ({ type: 'mysql', - entities: [User, File, Knowledge, Article, Category, Tag, Comment, Setting, SMTP, Page, View, Search], + entities: [ + User, + File, + Knowledge, + Article, + ArticleRevision, + Category, + Tag, + Comment, + Setting, + SMTP, + Page, + View, + Search, + ApiKey, + Webhook, + ], host: configService.get('DB_HOST', '0.0.0.0'), port: configService.get('DB_PORT', 3306), username: configService.get('DB_USER', 'root'), @@ -75,6 +97,9 @@ import { ViewModule } from './modules/view/view.module'; PageModule, ViewModule, SearchModule, + HealthModule, + ApiKeyModule, + WebhookModule, ], controllers: [], providers: [], diff --git a/server/src/common/api-messages.ts b/server/src/common/api-messages.ts new file mode 100644 index 0000000..98f712d --- /dev/null +++ b/server/src/common/api-messages.ts @@ -0,0 +1,38 @@ +/** API error and notification messages — English first */ +export const ApiMsg = { + AUTH_FAILED: 'Authentication failed', + MISSING_API_KEY: 'Missing API Key', + INVALID_API_KEY: 'Invalid API Key', + API_KEY_NOT_FOUND: 'API Key not found', + API_KEY_MISSING_READ: 'API Key missing read permission', + MISSING_PARAMS: 'Missing required parameters', + EMAIL_SEND_FAILED: 'Failed to send email', + OSS_CONFIG_INCOMPLETE: 'OSS configuration is incomplete', + WEBHOOK_NOT_FOUND: 'Webhook not found', + + USERNAME_PASSWORD_REQUIRED: 'Username and password are required', + USERNAME_INVALID: 'Username cannot contain spaces or invalid characters', + USER_EXISTS: 'User already exists', + INVALID_CREDENTIALS: 'Invalid username or password', + USER_LOCKED: 'User is locked and cannot sign in', + FORBIDDEN: 'You do not have permission for this action', + UNAUTHORIZED: 'Unauthorized', + FORBIDDEN_ACTION: 'Forbidden', + + ARTICLE_TITLE_EXISTS: 'Article title already exists', + REVISION_NOT_FOUND: 'Revision not found', + + TAG_EXISTS: 'Tag already exists', + CATEGORY_EXISTS: 'Category already exists', + PAGE_EXISTS: 'Page already exists', + KNOWLEDGE_EXISTS: 'Knowledge base already exists', + INVALID_KNOWLEDGE_SECTION: 'Invalid knowledge section', + DELETE_HAS_ARTICLES: 'Delete failed: related articles may exist', + + GITHUB_CODE_REQUIRED: 'GitHub authorization code is required', + GITHUB_EMAIL_REQUIRED: 'Public email is required for GitHub sign-in', + + EMAIL_NEW_COMMENT: 'New comment notification', + EMAIL_COMMENT_REPLY: 'Comment reply notification', + EMAIL_GITHUB_LOGIN: 'GitHub sign-in notification', +} as const; diff --git a/server/src/modules/api-key/api-key.controller.ts b/server/src/modules/api-key/api-key.controller.ts new file mode 100644 index 0000000..5a9e297 --- /dev/null +++ b/server/src/modules/api-key/api-key.controller.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { Roles, RolesGuard } from '../auth/roles.guard'; +import { ApiKeyService } from './api-key.service'; + +@ApiTags('ApiKey') +@Controller('api-key') +@UseGuards(RolesGuard, JwtAuthGuard) +@Roles('admin') +export class ApiKeyController { + constructor(private readonly apiKeyService: ApiKeyService) {} + + @Get() + list() { + return this.apiKeyService.findAll(); + } + + @Post() + create(@Body() body: { name: string; scopes?: string }) { + return this.apiKeyService.create(body.name, body.scopes || 'read'); + } + + @Delete(':id') + revoke(@Param('id') id: string) { + return this.apiKeyService.revoke(id); + } +} diff --git a/server/src/modules/api-key/api-key.entity.ts b/server/src/modules/api-key/api-key.entity.ts new file mode 100644 index 0000000..00b27e4 --- /dev/null +++ b/server/src/modules/api-key/api-key.entity.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('api_keys') +export class ApiKey { + @ApiProperty() + @PrimaryGeneratedColumn('uuid') + id: string; + + @ApiProperty() + @Column() + name: string; + + @ApiProperty({ description: 'bcrypt hash of the raw key' }) + @Column({ select: false }) + keyHash: string; + + @ApiProperty({ description: 'Comma-separated scopes: read, write' }) + @Column({ default: 'read' }) + scopes: string; + + @ApiProperty() + @Column({ type: 'varchar', length: 8, nullable: true }) + keyPrefix: string; + + @ApiProperty() + @Column({ default: true }) + enabled: boolean; + + @CreateDateColumn({ name: 'create_at' }) + createAt: Date; + + @UpdateDateColumn({ name: 'update_at' }) + updateAt: Date; + + @Column({ type: 'datetime', nullable: true }) + lastUsedAt: Date; +} diff --git a/server/src/modules/api-key/api-key.guard.ts b/server/src/modules/api-key/api-key.guard.ts new file mode 100644 index 0000000..2774f6e --- /dev/null +++ b/server/src/modules/api-key/api-key.guard.ts @@ -0,0 +1,28 @@ +import { ApiMsg } from '../../common/api-messages'; +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; + +import { ApiKeyService } from './api-key.service'; + +@Injectable() +export class ApiKeyGuard implements CanActivate { + constructor(private readonly apiKeyService: ApiKeyService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const rawKey = + request.headers['x-api-key'] || + (request.headers.authorization || '').replace(/^Bearer\s+/i, '').trim(); + + if (!rawKey) { + throw new UnauthorizedException(ApiMsg.MISSING_API_KEY); + } + + const apiKey = await this.apiKeyService.validateRawKey(rawKey); + if (!apiKey) { + throw new UnauthorizedException(ApiMsg.INVALID_API_KEY); + } + + request.apiKey = apiKey; + return true; + } +} diff --git a/server/src/modules/api-key/api-key.module.ts b/server/src/modules/api-key/api-key.module.ts new file mode 100644 index 0000000..900e1fc --- /dev/null +++ b/server/src/modules/api-key/api-key.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { AuthModule } from '../auth/auth.module'; +import { ApiKeyController } from './api-key.controller'; +import { ApiKey } from './api-key.entity'; +import { ApiKeyGuard } from './api-key.guard'; +import { ApiKeyService } from './api-key.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([ApiKey]), AuthModule], + controllers: [ApiKeyController], + providers: [ApiKeyService, ApiKeyGuard], + exports: [ApiKeyService, ApiKeyGuard], +}) +export class ApiKeyModule {} diff --git a/server/src/modules/api-key/api-key.service.ts b/server/src/modules/api-key/api-key.service.ts new file mode 100644 index 0000000..36c3cd1 --- /dev/null +++ b/server/src/modules/api-key/api-key.service.ts @@ -0,0 +1,70 @@ +import { ApiMsg } from '../../common/api-messages'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import * as bcrypt from 'bcryptjs'; +import * as crypto from 'crypto'; +import { Repository } from 'typeorm'; + +import { ApiKey } from './api-key.entity'; + +@Injectable() +export class ApiKeyService { + constructor( + @InjectRepository(ApiKey) + private readonly apiKeyRepository: Repository + ) {} + + private generateRawKey() { + return `rp_${crypto.randomBytes(24).toString('hex')}`; + } + + async create(name: string, scopes = 'read'): Promise<{ apiKey: ApiKey; rawKey: string }> { + const rawKey = this.generateRawKey(); + const keyHash = await bcrypt.hash(rawKey, 10); + const keyPrefix = rawKey.slice(0, 10); + const entity = this.apiKeyRepository.create({ name, keyHash, keyPrefix, scopes, enabled: true }); + const saved = await this.apiKeyRepository.save(entity); + return { apiKey: saved, rawKey }; + } + + async findAll(): Promise { + return this.apiKeyRepository.find({ order: { createAt: 'DESC' } }); + } + + async revoke(id: string) { + const key = await this.apiKeyRepository.findOne(id); + if (!key) { + throw new HttpException(ApiMsg.API_KEY_NOT_FOUND, HttpStatus.NOT_FOUND); + } + key.enabled = false; + return this.apiKeyRepository.save(key); + } + + async validateRawKey(rawKey: string): Promise { + if (!rawKey || !rawKey.startsWith('rp_')) { + return null; + } + const prefix = rawKey.slice(0, 10); + const candidates = await this.apiKeyRepository + .createQueryBuilder('api_key') + .addSelect('api_key.keyHash') + .where('api_key.enabled = :enabled', { enabled: true }) + .andWhere('api_key.keyPrefix = :prefix', { prefix }) + .getMany(); + + for (const candidate of candidates) { + const match = await bcrypt.compare(rawKey, candidate.keyHash); + if (match) { + candidate.lastUsedAt = new Date(); + await this.apiKeyRepository.update(candidate.id, { lastUsedAt: candidate.lastUsedAt }); + return candidate; + } + } + return null; + } + + hasScope(apiKey: ApiKey, scope: string) { + const scopes = (apiKey.scopes || 'read').split(',').map((s) => s.trim()); + return scopes.includes(scope) || scopes.includes('*'); + } +} diff --git a/server/src/modules/article/article-revision.entity.ts b/server/src/modules/article/article-revision.entity.ts new file mode 100644 index 0000000..feac36d --- /dev/null +++ b/server/src/modules/article/article-revision.entity.ts @@ -0,0 +1,30 @@ +import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; + +import { Article } from './article.entity'; + +@Entity('article_revisions') +export class ArticleRevision { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Article, { onDelete: 'CASCADE' }) + article: Article; + + @Column() + articleId: string; + + @Column() + title: string; + + @Column({ type: 'mediumtext', nullable: true }) + content: string; + + @Column({ type: 'mediumtext', nullable: true }) + html: string; + + @Column('simple-enum', { enum: ['draft', 'publish'] }) + status: string; + + @CreateDateColumn({ name: 'create_at' }) + createAt: Date; +} diff --git a/server/src/modules/article/article.controller.ts b/server/src/modules/article/article.controller.ts index 0445142..5f91179 100644 --- a/server/src/modules/article/article.controller.ts +++ b/server/src/modules/article/article.controller.ts @@ -1,7 +1,9 @@ +import { ApiMsg } from '../../common/api-messages'; import { Body, Controller, Delete, + ForbiddenException, Get, HttpCode, HttpStatus, @@ -15,6 +17,8 @@ import { import { JwtService } from '@nestjs/jwt'; import { ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiKeyGuard } from '../api-key/api-key.guard'; +import { ApiKeyService } from '../api-key/api-key.service'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { Roles, RolesGuard } from '../auth/roles.guard'; import { User } from '../user/user.entity'; @@ -28,7 +32,7 @@ import { ArticleService } from './article.service'; export class ArticleController { constructor( private readonly articleService: ArticleService, - + private readonly apiKeyService: ApiKeyService, private readonly jwtService: JwtService, private readonly userService: UserService ) {} @@ -54,6 +58,33 @@ export class ArticleController { return this.articleService.findAll(queryParams); } + /** + * Headless 只读列表(需 X-API-Key,read 权限) + */ + @Get('headless/list') + @UseGuards(ApiKeyGuard) + @HttpCode(HttpStatus.OK) + findHeadless(@Request() req, @Query() queryParams) { + if (!this.apiKeyService.hasScope(req.apiKey, 'read')) { + throw new ForbiddenException(ApiMsg.API_KEY_MISSING_READ); + } + return this.articleService.findAll({ ...queryParams, status: 'publish' }); + } + + @Get(':id/revisions') + @Roles('admin') + @UseGuards(JwtAuthGuard) + listRevisions(@Param('id') id: string) { + return this.articleService.listRevisions(id); + } + + @Post(':id/revisions/:revisionId/restore') + @Roles('admin') + @UseGuards(JwtAuthGuard) + restoreRevision(@Param('id') id: string, @Param('revisionId') revisionId: string) { + return this.articleService.restoreRevision(id, revisionId); + } + /** * 获取标签下所有文章 */ diff --git a/server/src/modules/article/article.entity.ts b/server/src/modules/article/article.entity.ts index 7b25d0a..7b42c8a 100644 --- a/server/src/modules/article/article.entity.ts +++ b/server/src/modules/article/article.entity.ts @@ -85,6 +85,10 @@ export class Article { @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) publishAt: Date; // 发布日期 + @ApiProperty({ description: '定时发布时间(草稿状态下生效)' }) + @Column({ type: 'timestamp', nullable: true }) + scheduledPublishAt: Date; + @ApiProperty() @CreateDateColumn({ type: 'datetime', diff --git a/server/src/modules/article/article.module.ts b/server/src/modules/article/article.module.ts index ac1ccc7..d25a700 100644 --- a/server/src/modules/article/article.module.ts +++ b/server/src/modules/article/article.module.ts @@ -1,18 +1,30 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ApiKeyModule } from '../api-key/api-key.module'; import { AuthModule } from '../auth/auth.module'; import { CategoryModule } from '../category/category.module'; import { TagModule } from '../tag/tag.module'; import { UserModule } from '../user/user.module'; +import { WebhookModule } from '../webhook/webhook.module'; +import { ArticleRevision } from './article-revision.entity'; import { ArticleController } from './article.controller'; import { Article } from './article.entity'; import { ArticleService } from './article.service'; +import { ScheduledPublishService } from './scheduled-publish.service'; @Module({ - imports: [TypeOrmModule.forFeature([Article]), CategoryModule, TagModule, UserModule, AuthModule], + imports: [ + TypeOrmModule.forFeature([Article, ArticleRevision]), + CategoryModule, + TagModule, + UserModule, + AuthModule, + ApiKeyModule, + WebhookModule, + ], exports: [ArticleService], - providers: [ArticleService], + providers: [ArticleService, ScheduledPublishService], controllers: [ArticleController], }) export class ArticleModule {} diff --git a/server/src/modules/article/article.service.ts b/server/src/modules/article/article.service.ts index 3493129..45fbd79 100644 --- a/server/src/modules/article/article.service.ts +++ b/server/src/modules/article/article.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -5,6 +6,8 @@ import { Repository } from 'typeorm'; import { dateFormat } from '../../utils/date.util'; import { CategoryService } from '../category/category.service'; import { TagService } from '../tag/tag.service'; +import { WebhookService } from '../webhook/webhook.service'; +import { ArticleRevision } from './article-revision.entity'; import { Article } from './article.entity'; import { extractProtectedArticle } from './article.util'; @@ -18,10 +21,36 @@ export class ArticleService { constructor( @InjectRepository(Article) private readonly articleRepository: Repository
, + @InjectRepository(ArticleRevision) + private readonly revisionRepository: Repository, private readonly tagService: TagService, - private readonly categoryService: CategoryService + private readonly categoryService: CategoryService, + private readonly webhookService: WebhookService ) {} + private async saveRevision(article: Article) { + const revision = this.revisionRepository.create({ + articleId: article.id, + article, + title: article.title, + content: article.content, + html: article.html, + status: article.status, + }); + await this.revisionRepository.save(revision); + } + + private async notifyPublished(article: Article, extra: Record = {}) { + if (article.status !== 'publish') return; + await this.webhookService.dispatch('article.published', { + id: article.id, + title: article.title, + status: article.status, + publishAt: article.publishAt, + ...extra, + }); + } + /** * 创建文章 * @param article @@ -31,7 +60,7 @@ export class ArticleService { const exist = await this.articleRepository.findOne({ where: { title } }); if (exist) { - throw new HttpException('文章标题已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.ARTICLE_TITLE_EXISTS, HttpStatus.BAD_REQUEST); } let { tags, category, status } = article; // eslint-disable-line prefer-const @@ -51,6 +80,7 @@ export class ArticleService { needPassword: !!article.password, }); await this.articleRepository.save(newArticle); + await this.notifyPublished(newArticle); return newArticle; } @@ -258,6 +288,7 @@ export class ArticleService { */ async updateById(id, article: Partial
): Promise
{ const oldArticle = await this.articleRepository.findOne(id); + await this.saveRevision(oldArticle); let { tags, category, status } = article; // eslint-disable-line prefer-const if (tags) { @@ -266,12 +297,15 @@ export class ArticleService { const existCategory = await this.categoryService.findById(category); + const becomingPublish = oldArticle.status === 'draft' && status === 'publish'; const newArticle = { ...article, views: oldArticle.views, category: existCategory, needPassword: !!article.password, - publishAt: oldArticle.status === 'draft' && status === 'publish' ? dateFormat() : oldArticle.publishAt, + publishAt: becomingPublish ? dateFormat() : oldArticle.publishAt, + scheduledPublishAt: + status === 'publish' ? null : article.scheduledPublishAt ?? oldArticle.scheduledPublishAt, }; if (tags) { @@ -279,7 +313,35 @@ export class ArticleService { } const updatedArticle = await this.articleRepository.merge(oldArticle, newArticle); - return this.articleRepository.save(updatedArticle); + const saved = await this.articleRepository.save(updatedArticle); + if (becomingPublish) { + await this.notifyPublished(saved); + } + return saved; + } + + async listRevisions(articleId: string) { + return this.revisionRepository.find({ + where: { articleId }, + order: { createAt: 'DESC' }, + take: 50, + }); + } + + async restoreRevision(articleId: string, revisionId: string) { + const revision = await this.revisionRepository.findOne(revisionId); + if (!revision || revision.articleId !== articleId) { + throw new HttpException(ApiMsg.REVISION_NOT_FOUND, HttpStatus.NOT_FOUND); + } + const article = await this.articleRepository.findOne(articleId); + await this.saveRevision(article); + const restored = await this.articleRepository.merge(article, { + title: revision.title, + content: revision.content, + html: revision.html, + status: revision.status, + }); + return this.articleRepository.save(restored); } /** diff --git a/server/src/modules/article/scheduled-publish.service.ts b/server/src/modules/article/scheduled-publish.service.ts new file mode 100644 index 0000000..9711609 --- /dev/null +++ b/server/src/modules/article/scheduled-publish.service.ts @@ -0,0 +1,50 @@ +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { LessThanOrEqual, Repository } from 'typeorm'; + +import { WebhookService } from '../webhook/webhook.service'; +import { Article } from './article.entity'; + +@Injectable() +export class ScheduledPublishService implements OnModuleInit, OnModuleDestroy { + private timer: NodeJS.Timeout | null = null; + + constructor( + @InjectRepository(Article) + private readonly articleRepository: Repository
, + private readonly webhookService: WebhookService + ) {} + + onModuleInit() { + this.timer = setInterval(() => this.tick().catch((e) => console.error('[ScheduledPublish]', e)), 60_000); + this.tick().catch(() => {}); + } + + onModuleDestroy() { + if (this.timer) clearInterval(this.timer); + } + + async tick() { + const now = new Date(); + const due = await this.articleRepository.find({ + where: { + status: 'draft', + scheduledPublishAt: LessThanOrEqual(now), + }, + }); + + for (const article of due) { + article.status = 'publish'; + article.publishAt = new Date(); + article.scheduledPublishAt = null; + const saved = await this.articleRepository.save(article); + await this.webhookService.dispatch('article.published', { + id: saved.id, + title: saved.title, + status: saved.status, + publishAt: saved.publishAt, + scheduled: true, + }); + } + } +} diff --git a/server/src/modules/auth/auth.service.ts b/server/src/modules/auth/auth.service.ts index 0149af6..7af0ad0 100644 --- a/server/src/modules/auth/auth.service.ts +++ b/server/src/modules/auth/auth.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; @@ -57,7 +58,7 @@ export class AuthService { async loginWithGithub(code) { if (!code) { - throw new HttpException('请输入Gitub授权码', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.GITHUB_CODE_REQUIRED, HttpStatus.BAD_REQUEST); } try { @@ -99,7 +100,7 @@ export class AuthService { const emailMessage = { from: setting.smtpFromUser, to: result.data.email, - subject: 'Github 用户登录通知', + subject: ApiMsg.EMAIL_GITHUB_LOGIN, html: `您好,您使用了 Github 登录了 reactpress。reactpress 已为您创建用户,用户名称:${result.data.name}, 用户密码:${password},请及时登录系统修改密码`, }; this.smtpService.create(emailMessage).catch(() => { @@ -110,7 +111,7 @@ export class AuthService { const res = await this.loginWithoutPasswd(user); return res; } else { - throw new HttpException('未获取到您的公开邮件地址,无法使用Github登录', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.GITHUB_EMAIL_REQUIRED, HttpStatus.BAD_REQUEST); } } catch (e) { throw new HttpException(e.message || e, HttpStatus.BAD_REQUEST); diff --git a/server/src/modules/auth/jwt-auth.guard.ts b/server/src/modules/auth/jwt-auth.guard.ts index 3c77b65..b6b5be5 100644 --- a/server/src/modules/auth/jwt-auth.guard.ts +++ b/server/src/modules/auth/jwt-auth.guard.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @@ -11,7 +12,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') { handleRequest(err, user: User): User { if (err || !user) { - throw new UnauthorizedException('身份验证失败'); + throw new UnauthorizedException(ApiMsg.AUTH_FAILED); } return user; } diff --git a/server/src/modules/auth/jwt.strategy.ts b/server/src/modules/auth/jwt.strategy.ts index 34baa61..1cd4326 100644 --- a/server/src/modules/auth/jwt.strategy.ts +++ b/server/src/modules/auth/jwt.strategy.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; @@ -18,7 +19,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) { const user = await this.authService.validateUser(payload); if (!user) { - throw new UnauthorizedException('身份验证失败'); + throw new UnauthorizedException(ApiMsg.AUTH_FAILED); } return user; } diff --git a/server/src/modules/category/category.service.ts b/server/src/modules/category/category.service.ts index 8439e57..7c0ff37 100644 --- a/server/src/modules/category/category.service.ts +++ b/server/src/modules/category/category.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -22,7 +23,7 @@ export class CategoryService { }); if (existCategory) { - throw new HttpException('分类已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.CATEGORY_EXISTS, HttpStatus.BAD_REQUEST); } const newCategory = await this.categoryRepository.create(Category); @@ -98,7 +99,7 @@ export class CategoryService { await this.categoryRepository.remove(category); return true; } catch (e) { - throw new HttpException('删除失败,可能存在关联文章', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.DELETE_HAS_ARTICLES, HttpStatus.BAD_REQUEST); } } } diff --git a/server/src/modules/comment/comment.module.ts b/server/src/modules/comment/comment.module.ts index fe128eb..f744a14 100644 --- a/server/src/modules/comment/comment.module.ts +++ b/server/src/modules/comment/comment.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ArticleModule } from '../article/article.module'; import { AuthModule } from '../auth/auth.module'; +import { WebhookModule } from '../webhook/webhook.module'; import { SettingModule } from '../setting/setting.module'; import { SMTPModule } from '../smtp/smtp.module'; import { UserModule } from '../user/user.module'; @@ -11,7 +12,15 @@ import { Comment } from './comment.entity'; import { CommentService } from './comment.service'; @Module({ - imports: [TypeOrmModule.forFeature([Comment]), AuthModule, ArticleModule, SettingModule, SMTPModule, UserModule], + imports: [ + TypeOrmModule.forFeature([Comment]), + AuthModule, + ArticleModule, + SettingModule, + SMTPModule, + UserModule, + WebhookModule, + ], providers: [CommentService], controllers: [CommentController], }) diff --git a/server/src/modules/comment/comment.service.ts b/server/src/modules/comment/comment.service.ts index c6ba2a2..0499544 100644 --- a/server/src/modules/comment/comment.service.ts +++ b/server/src/modules/comment/comment.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -8,6 +9,7 @@ import { ArticleService } from '../article/article.service'; import { SettingService } from '../setting/setting.service'; import { SMTPService } from '../smtp/smtp.service'; import { UserService } from '../user/user.service'; +import { WebhookService } from '../webhook/webhook.service'; import { Comment } from './comment.entity'; import { getNewCommentHTML, getReplyCommentHTML } from './html'; @@ -22,7 +24,8 @@ export class CommentService { private readonly articleService: ArticleService, private readonly smtpService: SMTPService, private readonly settingService: SettingService, - private readonly userService: UserService + private readonly userService: UserService, + private readonly webhookService: WebhookService ) {} /** @@ -33,7 +36,7 @@ export class CommentService { const { hostId, name, email, content, createByAdmin = false } = comment; if (!hostId || !name || !email || !content) { - throw new HttpException('缺失参数', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.MISSING_PARAMS, HttpStatus.BAD_REQUEST); } comment.pass = false; @@ -50,7 +53,7 @@ export class CommentService { const emailMessage = { from: setting.smtpFromUser, to: adminEmail, - subject: '新评论通知', + subject: ApiMsg.EMAIL_NEW_COMMENT, html: getNewCommentHTML({ ...setting, adminName, comment }), }; this.smtpService.create(emailMessage).catch(() => { @@ -70,6 +73,13 @@ export class CommentService { } } + await this.webhookService.dispatch('comment.created', { + id: newComment.id, + hostId: newComment.hostId, + name: newComment.name, + email: newComment.email, + }); + return newComment; } @@ -165,7 +175,7 @@ export class CommentService { const emailMessage = { from: setting.smtpFromUser, to: replyUserEmail, - subject: '评论回复通知', + subject: ApiMsg.EMAIL_COMMENT_REPLY, html: getReplyCommentHTML({ ...setting, replyUserName, diff --git a/server/src/modules/health/health.controller.ts b/server/src/modules/health/health.controller.ts new file mode 100644 index 0000000..edf1482 --- /dev/null +++ b/server/src/modules/health/health.controller.ts @@ -0,0 +1,31 @@ +import { Controller, Get, HttpCode, HttpStatus } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { InjectConnection } from '@nestjs/typeorm'; +import { Connection } from 'typeorm'; + +@ApiTags('Health') +@Controller('health') +export class HealthController { + constructor(@InjectConnection() private readonly connection: Connection) {} + + @Get() + @HttpCode(HttpStatus.OK) + async check() { + let database: 'up' | 'down' = 'down'; + try { + if (this.connection.isConnected) { + await this.connection.query('SELECT 1'); + database = 'up'; + } + } catch { + database = 'down'; + } + + return { + status: database === 'up' ? 'ok' : 'degraded', + version: '3.0.0', + database, + timestamp: new Date().toISOString(), + }; + } +} diff --git a/server/src/modules/health/health.module.ts b/server/src/modules/health/health.module.ts new file mode 100644 index 0000000..90d1fe0 --- /dev/null +++ b/server/src/modules/health/health.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; + +import { HealthController } from './health.controller'; + +@Module({ + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/server/src/modules/knowledge/knowledge.service.ts b/server/src/modules/knowledge/knowledge.service.ts index 5770ee0..2b23c16 100644 --- a/server/src/modules/knowledge/knowledge.service.ts +++ b/server/src/modules/knowledge/knowledge.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -22,7 +23,7 @@ export class KnowledgeService { const exist = await this.repository.findOne({ where: { title } }); if (exist && !parentId) { // 章节不考虑重名 - throw new HttpException('知识库已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.KNOWLEDGE_EXISTS, HttpStatus.BAD_REQUEST); } if (status === 'publish') { @@ -43,7 +44,7 @@ export class KnowledgeService { knowledges = [knowledges]; } if (knowledges.some((knowledge) => !knowledge.parentId)) { - throw new HttpException('无效的知识库章节', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.INVALID_KNOWLEDGE_SECTION, HttpStatus.BAD_REQUEST); } const result = []; for (const knowledge of knowledges) { diff --git a/server/src/modules/page/page.service.ts b/server/src/modules/page/page.service.ts index 1863cc4..912c396 100644 --- a/server/src/modules/page/page.service.ts +++ b/server/src/modules/page/page.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -21,7 +22,7 @@ export class PageService { const exist = await this.pageRepository.findOne({ where: { path } }); if (exist) { - throw new HttpException('页面已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.PAGE_EXISTS, HttpStatus.BAD_REQUEST); } const newPage = await this.pageRepository.create({ diff --git a/server/src/modules/smtp/smtp.service.ts b/server/src/modules/smtp/smtp.service.ts index da53532..e690435 100644 --- a/server/src/modules/smtp/smtp.service.ts +++ b/server/src/modules/smtp/smtp.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -30,7 +31,7 @@ export class SMTPService { user: smtpUser, pass: smtpPass, }).catch(() => { - throw new HttpException('邮件发送失败', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.EMAIL_SEND_FAILED, HttpStatus.BAD_REQUEST); }); const newSMTP = await this.smtpRepository.create(data); await this.smtpRepository.save(newSMTP); diff --git a/server/src/modules/tag/tag.service.ts b/server/src/modules/tag/tag.service.ts index 35d347a..8b4eaa1 100644 --- a/server/src/modules/tag/tag.service.ts +++ b/server/src/modules/tag/tag.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -20,7 +21,7 @@ export class TagService { const existTag = await this.tagRepository.findOne({ where: { label } }); if (existTag) { - throw new HttpException('标签已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.TAG_EXISTS, HttpStatus.BAD_REQUEST); } const newTag = await this.tagRepository.create(tag); @@ -116,7 +117,7 @@ export class TagService { await this.tagRepository.remove(tag); return true; } catch (e) { - throw new HttpException('删除失败,可能存在关联文章', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.DELETE_HAS_ARTICLES, HttpStatus.BAD_REQUEST); } } } diff --git a/server/src/modules/user/user.controller.ts b/server/src/modules/user/user.controller.ts index e4344f6..43459a6 100644 --- a/server/src/modules/user/user.controller.ts +++ b/server/src/modules/user/user.controller.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { Body, ClassSerializerInterceptor, @@ -53,7 +54,7 @@ export class UserController { let token = req.headers.authorization; if (!token) { - throw new HttpException('未认证', HttpStatus.UNAUTHORIZED); + throw new HttpException(ApiMsg.UNAUTHORIZED, HttpStatus.UNAUTHORIZED); } if (/Bearer/.test(token)) { @@ -64,12 +65,12 @@ export class UserController { const id = tokenUser.id; if (!id) { - throw new HttpException('未认证', HttpStatus.UNAUTHORIZED); + throw new HttpException(ApiMsg.UNAUTHORIZED, HttpStatus.UNAUTHORIZED); } const exist = await this.userService.findById(id); if (exist.id !== user.id && exist.role !== 'admin') { - throw new HttpException('无权处理', HttpStatus.FORBIDDEN); + throw new HttpException(ApiMsg.FORBIDDEN_ACTION, HttpStatus.FORBIDDEN); } req.user = tokenUser; diff --git a/server/src/modules/user/user.service.ts b/server/src/modules/user/user.service.ts index 28fa4e6..0e93834 100644 --- a/server/src/modules/user/user.service.ts +++ b/server/src/modules/user/user.service.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../../common/api-messages'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; @@ -67,19 +68,19 @@ export class UserService { // 如果忽略了名称校验,来自于系统内部默认的注入 if (!ignoreValidator) { if (!name || !password) { - throw new HttpException('请输入用户名和密码', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.USERNAME_PASSWORD_REQUIRED, HttpStatus.BAD_REQUEST); } // 防止用户名称非法 if (!isValidUsername(name)) { - throw new HttpException('用户名不能包含空格和非法字符', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.USERNAME_INVALID, HttpStatus.BAD_REQUEST); } } const existUser = await this.userRepository.findOne({ where: { name } }); if (existUser) { - throw new HttpException('用户已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.USER_EXISTS, HttpStatus.BAD_REQUEST); } const newUser = await this.userRepository.create(user); @@ -97,7 +98,7 @@ export class UserService { if (!existUser || !(await User.comparePassword(password, existUser.password))) { throw new HttpException( - '用户名或密码错误', + ApiMsg.INVALID_CREDENTIALS, // tslint:disable-next-line: trailing-comma HttpStatus.BAD_REQUEST ); @@ -105,7 +106,7 @@ export class UserService { if (existUser.status === 'locked') { throw new HttpException( - '用户已锁定,无法登录', + ApiMsg.USER_LOCKED, // tslint:disable-next-line: trailing-comma HttpStatus.BAD_REQUEST ); @@ -126,7 +127,7 @@ export class UserService { if (existUser.status === 'locked') { throw new HttpException( - '用户已锁定,无法登录', + ApiMsg.USER_LOCKED, // tslint:disable-next-line: trailing-comma HttpStatus.BAD_REQUEST ); @@ -153,7 +154,7 @@ export class UserService { if (user.role === 'admin') { if (!currentUser || currentUser.role !== 'admin') { throw new HttpException( - '您无权操作', + ApiMsg.FORBIDDEN, // tslint:disable-next-line: trailing-comma HttpStatus.FORBIDDEN ); @@ -167,7 +168,7 @@ export class UserService { const existUser = await this.userRepository.findOne({ where: { name: user.name } }); if (existUser) { - throw new HttpException('用户已存在', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.USER_EXISTS, HttpStatus.BAD_REQUEST); } } @@ -185,7 +186,7 @@ export class UserService { if (!existUser || !(await User.comparePassword(oldPassword, existUser.password))) { throw new HttpException( - '用户名或密码错误', + ApiMsg.INVALID_CREDENTIALS, // tslint:disable-next-line: trailing-comma HttpStatus.BAD_REQUEST ); diff --git a/server/src/modules/webhook/webhook.controller.ts b/server/src/modules/webhook/webhook.controller.ts new file mode 100644 index 0000000..0799a88 --- /dev/null +++ b/server/src/modules/webhook/webhook.controller.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { Roles, RolesGuard } from '../auth/roles.guard'; +import { WebhookService } from './webhook.service'; + +@ApiTags('Webhook') +@Controller('webhook') +@UseGuards(RolesGuard, JwtAuthGuard) +@Roles('admin') +export class WebhookController { + constructor(private readonly webhookService: WebhookService) {} + + @Get() + list() { + return this.webhookService.findAll(); + } + + @Post() + create(@Body() body: { url: string; events: string; secret?: string }) { + return this.webhookService.create(body); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.webhookService.remove(id); + } +} diff --git a/server/src/modules/webhook/webhook.entity.ts b/server/src/modules/webhook/webhook.entity.ts new file mode 100644 index 0000000..c56d290 --- /dev/null +++ b/server/src/modules/webhook/webhook.entity.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('webhooks') +export class Webhook { + @ApiProperty() + @PrimaryGeneratedColumn('uuid') + id: string; + + @ApiProperty() + @Column() + url: string; + + @ApiProperty() + @Column({ select: false }) + secret: string; + + @ApiProperty({ description: 'Comma-separated: article.published, comment.created' }) + @Column() + events: string; + + @ApiProperty() + @Column({ default: true }) + enabled: boolean; + + @CreateDateColumn({ name: 'create_at' }) + createAt: Date; + + @UpdateDateColumn({ name: 'update_at' }) + updateAt: Date; +} diff --git a/server/src/modules/webhook/webhook.module.ts b/server/src/modules/webhook/webhook.module.ts new file mode 100644 index 0000000..1044f31 --- /dev/null +++ b/server/src/modules/webhook/webhook.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { AuthModule } from '../auth/auth.module'; +import { WebhookController } from './webhook.controller'; +import { Webhook } from './webhook.entity'; +import { WebhookService } from './webhook.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Webhook]), AuthModule], + controllers: [WebhookController], + providers: [WebhookService], + exports: [WebhookService], +}) +export class WebhookModule {} diff --git a/server/src/modules/webhook/webhook.service.ts b/server/src/modules/webhook/webhook.service.ts new file mode 100644 index 0000000..3e0af8a --- /dev/null +++ b/server/src/modules/webhook/webhook.service.ts @@ -0,0 +1,91 @@ +import { ApiMsg } from '../../common/api-messages'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import axios from 'axios'; +import * as crypto from 'crypto'; +import { Repository } from 'typeorm'; + +import { Webhook } from './webhook.entity'; + +export type WebhookEvent = 'article.published' | 'comment.created'; + +@Injectable() +export class WebhookService { + constructor( + @InjectRepository(Webhook) + private readonly webhookRepository: Repository + ) {} + + async create(data: Partial & { secret?: string }) { + const secret = data.secret || crypto.randomBytes(16).toString('hex'); + const entity = this.webhookRepository.create({ ...data, secret }); + return this.webhookRepository.save(entity); + } + + findAll() { + return this.webhookRepository.find({ order: { createAt: 'DESC' } }); + } + + async remove(id: string) { + const hook = await this.webhookRepository.findOne(id); + if (!hook) { + throw new HttpException(ApiMsg.WEBHOOK_NOT_FOUND, HttpStatus.NOT_FOUND); + } + return this.webhookRepository.remove(hook); + } + + private signPayload(secret: string, body: string) { + return crypto.createHmac('sha256', secret).update(body).digest('hex'); + } + + private async deliverOnce(webhook: Webhook, event: WebhookEvent, payload: Record) { + const body = JSON.stringify({ + event, + timestamp: new Date().toISOString(), + data: payload, + }); + const signature = this.signPayload(webhook.secret, body); + await axios.post(webhook.url, body, { + headers: { + 'Content-Type': 'application/json', + 'X-ReactPress-Event': event, + 'X-ReactPress-Signature': `sha256=${signature}`, + }, + timeout: 10000, + }); + } + + private async deliverWithRetry(webhook: Webhook, event: WebhookEvent, payload: Record) { + const delays = [0, 2000, 5000]; + let lastError: Error | null = null; + for (let attempt = 0; attempt < delays.length; attempt++) { + if (delays[attempt] > 0) { + await new Promise((r) => setTimeout(r, delays[attempt])); + } + try { + await this.deliverOnce(webhook, event, payload); + return; + } catch (err) { + lastError = err; + } + } + console.error(`[Webhook] 投递失败 ${webhook.url} (${event}):`, lastError?.message || lastError); + } + + async dispatch(event: WebhookEvent, payload: Record) { + const hooks = await this.webhookRepository + .createQueryBuilder('webhook') + .addSelect('webhook.secret') + .where('webhook.enabled = :enabled', { enabled: true }) + .getMany(); + + const targets = hooks.filter((h) => + h.events + .split(',') + .map((e) => e.trim()) + .includes(event) + ); + + await Promise.all(targets.map((hook) => this.deliverWithRetry(hook, event, payload))); + } +} diff --git a/server/src/starter.ts b/server/src/starter.ts index c3d02b0..7c839d0 100644 --- a/server/src/starter.ts +++ b/server/src/starter.ts @@ -41,7 +41,7 @@ export async function bootstrap() { const swaggerConfig = new DocumentBuilder() .setTitle('ReactPress API Documentation') .setDescription('Comprehensive API documentation for ReactPress - A modern content management system built with NestJS') - .setVersion('2.0') + .setVersion('3.0') .setContact('ReactPress Team', 'https://github.com/fecommunity/reactpress', 'admin@gaoredu.com') .setLicense('MIT', 'https://github.com/fecommunity/reactpress/blob/main/LICENSE') .addServer(configService.get('SERVER_SITE_URL', 'http://localhost:3002'), 'API Server') diff --git a/server/src/utils/oss.util.ts b/server/src/utils/oss.util.ts index 6ed91f5..ef1bc22 100644 --- a/server/src/utils/oss.util.ts +++ b/server/src/utils/oss.util.ts @@ -1,3 +1,4 @@ +import { ApiMsg } from '../common/api-messages'; import { HttpException, HttpStatus } from '@nestjs/common'; import { SettingService } from '../modules/setting/setting.service'; @@ -30,7 +31,7 @@ export class Oss { const data = await this.settingService.findAll(true); const config = JSON.parse(data.oss); if (!config) { - throw new HttpException('OSS 配置不完善,无法进行操作', HttpStatus.BAD_REQUEST); + throw new HttpException(ApiMsg.OSS_CONFIG_INCOMPLETE, HttpStatus.BAD_REQUEST); } return config as Record; } diff --git a/templates/hello-world/package.json b/templates/hello-world/package.json index 717dc09..9901aa1 100644 --- a/templates/hello-world/package.json +++ b/templates/hello-world/package.json @@ -1,6 +1,6 @@ { "name": "@fecommunity/reactpress-template-hello-world", - "version": "1.0.0-beta.4", + "version": "3.0.0", "description": "A minimal hello-world template for ReactPress using Next.js Pages Router", "main": "index.js", "bin": { diff --git a/templates/twentytwentyfive/package.json b/templates/twentytwentyfive/package.json index b85da73..2c03817 100644 --- a/templates/twentytwentyfive/package.json +++ b/templates/twentytwentyfive/package.json @@ -1,6 +1,6 @@ { "name": "@fecommunity/reactpress-template-twentytwentyfive", - "version": "1.0.0-beta.4", + "version": "3.0.0", "description": "A modern blog template for ReactPress using Next.js Pages Router", "main": "index.js", "bin": { diff --git a/toolkit/package.json b/toolkit/package.json index 50838cf..8d13b6a 100644 --- a/toolkit/package.json +++ b/toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@fecommunity/reactpress-toolkit", - "version": "1.0.0-beta.4", + "version": "3.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/toolkit/scripts/generate-api-types.js b/toolkit/scripts/generate-api-types.js index ebf1ca3..b0c20a5 100755 --- a/toolkit/scripts/generate-api-types.js +++ b/toolkit/scripts/generate-api-types.js @@ -5,7 +5,7 @@ const fs = require('fs-extra'); // 配置常量 const CONFIG = { - input: path.resolve(__dirname, '../../server/public/swagger.json'), + input: require('./resolve-swagger-input').getSwaggerInputPath(), output: path.resolve(__dirname, '../src'), templates: path.resolve(__dirname, '../node_modules/swagger-typescript-api/templates/base'), }; diff --git a/toolkit/scripts/generate-api.js b/toolkit/scripts/generate-api.js index 01b21ca..81bea79 100644 --- a/toolkit/scripts/generate-api.js +++ b/toolkit/scripts/generate-api.js @@ -1,10 +1,11 @@ const { generateApi } = require('swagger-typescript-api'); const path = require('path'); const fs = require('fs-extra'); +const { getSwaggerInputPath } = require('./resolve-swagger-input'); // 配置常量 const CONFIG = { - input: path.resolve(__dirname, '../../server/public/swagger.json'), + input: getSwaggerInputPath(), output: path.resolve(__dirname, '../src'), templates: path.resolve(__dirname, '../node_modules/swagger-typescript-api/templates/base'), utilsDir: path.resolve(__dirname, '../src/utils'), diff --git a/toolkit/scripts/generate-swagger.js b/toolkit/scripts/generate-swagger.js index f1f0e17..fec4cc3 100755 --- a/toolkit/scripts/generate-swagger.js +++ b/toolkit/scripts/generate-swagger.js @@ -1,48 +1,43 @@ const { execSync } = require('child_process'); const { unlinkSync, existsSync } = require('fs'); const { join } = require('path'); +const { getSwaggerInputPath, getBundledServerPathForGenerate } = require('./resolve-swagger-input'); console.log('🚀 Starting API types generation process...\n'); +const bundledServerDir = getBundledServerPathForGenerate(); +const swaggerPath = getSwaggerInputPath(); + try { - // Step 1: Enter server directory and generate swagger.json - console.log('1️⃣ Generating Swagger JSON in server directory...'); + console.log('1️⃣ Generating Swagger JSON from monorepo server/ …'); try { - execSync('cd ../server && npm run generate:swagger', { stdio: 'inherit' }); - console.log('✅ Swagger JSON generated successfully in server directory!\n'); + execSync('npm run generate:swagger', { cwd: bundledServerDir, stdio: 'inherit' }); + console.log('✅ Swagger JSON generated successfully!\n'); } catch (error) { console.log('⚠️ Failed to generate new swagger.json, checking for existing file...\n'); } - // Step 2: Check if swagger.json exists in server directory - const serverSwaggerPath = join(__dirname, '../../server/public/swagger.json'); - if (!existsSync(serverSwaggerPath)) { - console.error('❌ No swagger.json file available in server directory'); + if (!existsSync(swaggerPath)) { + console.error(`❌ No swagger.json at ${swaggerPath}`); + console.error(' Run: pnpm run generate:swagger (requires server/ and .env).'); process.exit(1); } - console.log('✅ Using swagger.json from server directory!\n'); + console.log(`✅ Using swagger.json from ${swaggerPath}\n`); - // Step 3: Generate TypeScript definitions (only updates api and types directories) console.log('📝 Note: Only api and types directories will be regenerated. Other src files will remain untouched.\n'); console.log('2️⃣ Generating TypeScript definitions (api and types only)...'); - execSync('node scripts/generate-api.js', { stdio: 'inherit' }); + execSync('node scripts/generate-api.js', { stdio: 'inherit', cwd: join(__dirname, '..') }); console.log('✅ TypeScript definitions generated successfully!\n'); - // Step 4: Clean up - remove swagger.json from server directory - console.log('3️⃣ Cleaning up swagger.json files...'); - - // Remove from server directory - if (existsSync(serverSwaggerPath)) { - unlinkSync(serverSwaggerPath); - console.log('✅ Removed swagger.json from server directory'); + console.log('3️⃣ Cleaning up swagger.json...'); + if (existsSync(swaggerPath)) { + unlinkSync(swaggerPath); + console.log('✅ Removed swagger.json from bundled server public/'); } - console.log('✅ Cleanup completed!\n'); console.log('🎉 API generation process completed successfully!'); - console.log('📁 Only api and types directories were updated. Other src files remain untouched.'); - } catch (error) { console.error('❌ Error during generation process:', error.message); process.exit(1); -} \ No newline at end of file +} diff --git a/toolkit/scripts/resolve-swagger-input.js b/toolkit/scripts/resolve-swagger-input.js new file mode 100644 index 0000000..545f971 --- /dev/null +++ b/toolkit/scripts/resolve-swagger-input.js @@ -0,0 +1,11 @@ +const { getBundledSwaggerPath, getBundledServerDir } = require('../../scripts/bundled-server-path'); + +function getSwaggerInputPath() { + return getBundledSwaggerPath(); +} + +function getBundledServerPathForGenerate() { + return getBundledServerDir(); +} + +module.exports = { getSwaggerInputPath, getBundledServerPathForGenerate }; diff --git a/toolkit/src/config/global.ts b/toolkit/src/config/global.ts index b89e402..1c4ad33 100644 --- a/toolkit/src/config/global.ts +++ b/toolkit/src/config/global.ts @@ -402,11 +402,20 @@ interface LanguageConfig { globalConfig: GlobalConfig; } -// TODO: 需要为英文环境创建专门的配置 -// 当前英文配置只是复制了中文配置,需要根据实际需求调整 +const navConfigEn: NavConfig = { + categories: [ + { label: 'Site', key: 'local' }, + { label: 'Search', key: 'search' }, + { label: 'Tools', key: 'tools' }, + { label: 'Community', key: 'community' }, + { label: 'Jobs', key: 'job' }, + ], + subCategories: navConfig.subCategories, +}; + const en: LanguageConfig = { globalConfig: { - navConfig, + navConfig: navConfigEn, urlConfig, }, }; diff --git a/toolkit/src/config/i18n.ts b/toolkit/src/config/i18n.ts index e45b46b..46359b6 100644 --- a/toolkit/src/config/i18n.ts +++ b/toolkit/src/config/i18n.ts @@ -27,7 +27,7 @@ function parseI18n(): I18nResult { return i18n; }, {} as I18nMessages); const locales = Object.keys(messages); - const defaultLocale = 'zh' in messages ? 'zh' : locales[0]; + const defaultLocale = 'en' in messages ? 'en' : locales[0]; return { messages, locales, defaultLocale }; }