Compare commits

..

82 Commits

Author SHA1 Message Date
xiaojunnuo 9ffea32176 chore: 批量升级各包依赖版本并统一代码工具链
本次提交统一升级了多个包的依赖版本,包括:
1. 升级prettier到3.3.3、eslint及其相关插件到最新兼容版本
2. 更新rollup、typescript、typeorm等构建和运行时依赖
3. 调整部分包的依赖移除冗余依赖项,统一项目开发构建工具版本
2026-06-22 00:13:13 +08:00
xiaojunnuo f32c99d4a1 refactor(plugins): 部分插件改用运行时导入依赖替代静态导入
将所有插件内的静态依赖导入改为通过access.importRuntime动态导入,并调整package.json依赖顺序
2026-06-21 23:43:19 +08:00
xiaojunnuo 1f1b1858c7 chore: 增加清理依赖缓存提示 2026-06-21 22:32:51 +08:00
xiaojunnuo 42fcb91f2e chore: 完善第三方依赖动态加载 2026-06-20 00:35:13 +08:00
xiaojunnuo 01568ca148 feat: 通过插件配置懒加载依赖,动态加载第三方依赖包,精简安装镜像大小 2026-06-19 17:44:57 +08:00
xiaojunnuo 0d97ad67c5 chore: 1 2026-06-17 00:39:04 +08:00
xiaojunnuo c94a5537a3 chore: 1 2026-06-17 00:26:50 +08:00
xiaojunnuo d88dfc197e chore: 1 2026-06-17 00:04:24 +08:00
xiaojunnuo f709c05c0d build(ui): 重构Dockerfile支持多架构 2026-06-16 23:41:40 +08:00
xiaojunnuo 2c609da7a1 build: release 2026-06-16 23:15:03 +08:00
xiaojunnuo d770c7bd08 chore: 1 2026-06-16 23:09:33 +08:00
xiaojunnuo b9ccd4a8a0 chore: 1 2026-06-16 22:30:21 +08:00
xiaojunnuo 8875e8059f chore: ncurses-base 退格键 ^H的问题 2026-06-16 00:27:32 +08:00
xiaojunnuo 6490366c68 chore: 1 2026-06-16 00:21:24 +08:00
xiaojunnuo c278946771 chore(nettest): 修复跨平台端口测试匹配逻辑 2026-06-16 00:15:42 +08:00
xiaojunnuo 1562d9de36 chore: 1 2026-06-15 23:39:47 +08:00
xiaojunnuo 5bb0990abb chore: 1 2026-06-15 23:32:32 +08:00
xiaojunnuo bfd3cacc68 perf: 优化ACME账号字段的选择提示 2026-06-15 23:30:13 +08:00
xiaojunnuo c7e1163d59 chore: 改回dnsResultOrder不使用默认ipv4 2026-06-15 23:26:20 +08:00
xiaojunnuo fba7aeb71b Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-06-15 23:24:46 +08:00
xiaojunnuo c66a2bd77a perf: 基础镜像改成node:22-trixie-slim,对网络兼容性更好 2026-06-15 23:24:06 +08:00
xiaojunnuo ed58ae3c53 perf: 优化阿里云API网关增加翻页查询 2026-06-15 10:02:02 +08:00
xiaojunnuo 194463bea9 perf: dns默认ipv4first 2026-06-14 23:18:37 +08:00
xiaojunnuo 260f5ae777 fix: 修复jdk证书格式的问题 2026-06-14 23:18:17 +08:00
xiaojunnuo e85d824337 chore:1 2026-06-14 22:21:07 +08:00
xiaojunnuo e17fc39709 chore: 1 2026-06-14 22:14:59 +08:00
xiaojunnuo da9b297b12 chore: 1 2026-06-14 21:46:06 +08:00
xiaojunnuo 807dfcd57a chore: 尝试使用 node:22.22-trixie-slim 2026-06-14 21:43:18 +08:00
xiaojunnuo 0a410db52a build: publish 2026-06-14 21:30:16 +08:00
xiaojunnuo 4501095106 build: trigger build image 2026-06-14 21:30:05 +08:00
xiaojunnuo bc731e4fb1 v1.41.4 2026-06-14 21:29:12 +08:00
xiaojunnuo 53561d2755 build: prepare to build 2026-06-14 21:25:11 +08:00
xiaojunnuo e913fe509c chore: 优化代码格式 2026-06-14 20:59:38 +08:00
xiaojunnuo a3a215b7ae perf(plugin): 增加 Dynadot DNS and access 插件
新增Dynadot域名解析提供商插件,包含API授权配置和DNS记录增删查功能
2026-06-14 17:23:24 +08:00
xiaojunnuo 56f2949ac5 chore: 1 2026-06-14 15:07:38 +08:00
xiaojunnuo c1b5a35f90 fix: 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug 2026-06-14 14:57:47 +08:00
xiaojunnuo 48ab1fbffe build: release 2026-06-12 00:17:31 +08:00
xiaojunnuo 5f078273b3 build: publish 2026-06-11 23:57:37 +08:00
xiaojunnuo 636338f9ed build: trigger build image 2026-06-11 23:57:24 +08:00
xiaojunnuo 6cbd629777 v1.41.3 2026-06-11 23:56:12 +08:00
xiaojunnuo 5a07dce759 build: prepare to build 2026-06-11 23:53:30 +08:00
xiaojunnuo 15484bc119 perf: 首页夜间模式主图切换为黑色背景 2026-06-11 23:51:45 +08:00
xiaojunnuo d6cd9d136d fix: 修复litessl无法申请证书,报authorization must be pending 错误的问题 2026-06-11 23:40:44 +08:00
xiaojunnuo c76815756b chore: 1 2026-06-11 01:17:48 +08:00
xiaojunnuo eef93250ac build: release 2026-06-11 00:25:37 +08:00
xiaojunnuo a1c6cf0477 build: release 2026-06-11 00:20:14 +08:00
xiaojunnuo 14a0ccac93 chore: popularize agents.md 2026-06-11 00:07:02 +08:00
xiaojunnuo 9439743b7e build: publish 2026-06-10 23:39:06 +08:00
xiaojunnuo acbac6a9c3 build: trigger build image 2026-06-10 23:38:54 +08:00
xiaojunnuo cc38ccd0e9 v1.41.2 2026-06-10 23:37:43 +08:00
xiaojunnuo 7d0cf846ac build: prepare to build 2026-06-10 23:34:12 +08:00
xiaojunnuo f9541fab70 perf: 新增站点证书监控从DNS解析记录批量导入功能
本次提交新增了从DNS解析记录批量导入站点监控的完整功能:
1. 扩展Registrable类型新增icon字段支持
2. 新增DNS解析记录获取接口和基础实现
3. 为阿里云、腾讯云、Cloudflare等DNS提供商添加解析记录分页获取支持
4. 新增站点监控导入任务管理功能,支持保存、启动、删除导入任务
5. 新增中文/英文多语言支持
6. 优化暗黑模式表格样式
7. 修复ACME账户访问修复逻辑中项目ID可选的问题
8. 优化HiPM DNS提供商的域名获取逻辑
2026-06-10 23:32:39 +08:00
xiaojunnuo 8d9870e9c6 Merge branch 'v2' into v2-dev 2026-06-09 23:10:45 +08:00
HINS 0f3f8519e0 perf: 优化 HiPM DNSMgr 插件,添加域名查询双层策略 (#744) @WUHINS
- 新增 getDomainId() 方法,首选 keyword 直接查询(O(1))
- 列表匹配作为降级方案(向后兼容)
- 性能提升 99%,减少 99% 数据传输
- 与 ddns-go hipmdnsmgr.go 实现保持一致
2026-06-09 23:10:15 +08:00
xiaojunnuo 016ae865b1 fix(cert-plugin): 修复DNS提供商授权无法回显的bug 2026-06-09 23:08:45 +08:00
xiaojunnuo 3e9953a74a chore: 1 2026-06-08 16:54:33 +08:00
xiaojunnuo 1fc80d2b93 chore: 1 2026-06-08 16:52:36 +08:00
xiaojunnuo b55fe2ef19 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-06-08 16:48:39 +08:00
xiaojunnuo 71030b7e27 chore: handsfree.work 域名 修改 2026-06-08 16:48:30 +08:00
greper 5e8bdac008 Revert "perf: 添加AWS Rate Limit应对措施 (#748)" (#749)
This reverts commit 56b8c689ec.
2026-06-08 11:43:10 +08:00
Steven Zhu 56b8c689ec perf: 添加AWS Rate Limit应对措施 (#748)
* Parse PEM chain and import certificate chain

Split the PEM in certInfo.crt into a leaf certificate and intermediate chain (using a lookbehind regex), trim the blocks, and pass the chain to ImportCertificateCommand only when present. Replace console.log with this.logger.info and log the returned CertificateArn. This ensures the leaf cert is uploaded separately from its chain and avoids sending an empty CertificateChain.

* Add AWS retry & CloudFront deployment wait

Introduce robust retry and polling helpers to handle AWS throttling and CloudFront propagation. Added AwsClient.withRetry (exponential backoff, handles common throttling errors, default 5 attempts/base 2s) and waitForDistributionDeployed (polls until distribution Status is "Deployed", default 10min timeout/15s interval). Update deploy-to-cloudfront plugin to use withRetry for Get/UpdateDistribution and importCertificate, pass AwsClient into uploadToACM, and wait for each distribution to finish deploying before continuing to avoid PreconditionFailed errors. Improves reliability when facing rate limits and global CloudFront propagation delays; adds informative logging for retry and deployment status.
2026-06-08 10:29:11 +08:00
Steven Zhu 454912d314 fix: Parse PEM chain and import certificate chain (#747)
Split the PEM in certInfo.crt into a leaf certificate and intermediate chain (using a lookbehind regex), trim the blocks, and pass the chain to ImportCertificateCommand only when present. Replace console.log with this.logger.info and log the returned CertificateArn. This ensures the leaf cert is uploaded separately from its chain and avoids sending an empty CertificateChain.
2026-06-08 10:28:39 +08:00
xiaojunnuo 61e3f5761c build: release 2026-06-06 03:06:48 +08:00
xiaojunnuo 2908569841 chore: 1 2026-06-06 03:02:47 +08:00
xiaojunnuo 775226b49f build: publish 2026-06-06 02:38:20 +08:00
xiaojunnuo e3dacb5b3f build: trigger build image 2026-06-06 02:38:08 +08:00
xiaojunnuo cdea411136 v1.41.1 2026-06-06 02:36:50 +08:00
xiaojunnuo fdb000ee7c build: prepare to build 2026-06-06 02:33:06 +08:00
xiaojunnuo 4a0be1c29d build: prepare to build 2026-06-06 02:31:59 +08:00
xiaojunnuo 892d22e225 chore: 1 2026-06-06 02:30:57 +08:00
xiaojunnuo 4958a48b92 build: prepare to build 2026-06-06 02:21:11 +08:00
xiaojunnuo 28bbea85f0 chore: 1 2026-06-06 02:20:09 +08:00
xiaojunnuo 73b3a29cfc build: prepare to build 2026-06-06 02:15:39 +08:00
xiaojunnuo 77b8024453 perf(volcengine-vke): 火山VKE集群证书支持两种类型的证书保密字典 2026-06-06 02:12:47 +08:00
xiaojunnuo 1175e1164b refactor(ui): 统一使用useMounted钩子简化页面初始化逻辑 2026-06-06 00:50:59 +08:00
xiaojunnuo 5546af518e perf: 优化列表页面请求两次的问题 2026-06-05 00:23:08 +08:00
xiaojunnuo 99fd3083f2 perf: 流水线、监控站点支持导出 2026-06-04 23:43:25 +08:00
xiaojunnuo c0df8be832 perf(settings): 新增NO_PROXY代理排除配置 2026-06-04 23:24:29 +08:00
xiaojunnuo 73cab6a6ee chore(auth): 清理已使用的邀请码缓存 2026-06-04 18:28:15 +08:00
xiaojunnuo 7a71e45799 perf: 优化邀请注册流程 2026-06-04 18:22:21 +08:00
xiaojunnuo fdb1d1e6dd build: publish 2026-06-04 12:32:41 +08:00
xiaojunnuo 6dd4d6adeb build: trigger build image 2026-06-04 12:32:29 +08:00
278 changed files with 5602 additions and 1401 deletions
-36
View File
@@ -1,36 +0,0 @@
# 后端规则
主包:`packages/ui/certd-server`。后端使用 Node.js、ESM、TypeScript、MidwayJS 3、Koa、TypeORM,默认 better-sqlite3,同时支持 PostgreSQL 和 MySQL,并通过 `@certd/midway-flyway-js` 使用类似 Flyway 的 SQL 迁移机制。
详细入口、模块和验证命令见 `.codex/repo-map.md`
## 默认开发配置
- HTTP 端口:`7001`
- HTTPS 端口:`7002`
- 默认 SQLite 数据库:`./data/db.sqlite`
- 默认文件根目录:`./data/files`
## 数据与迁移
- 后端使用 TypeORM 实体加 SQL 迁移。
- 重点查看 `packages/ui/certd-server/src/modules/**/entity/*.ts``packages/ui/certd-server/db/migration/*.sql`
- 默认配置中 `synchronize: false`,涉及表结构变更时应添加或更新迁移脚本,不要依赖 TypeORM 自动同步。
## 文件上传
使用 `/basic/file/upload` 上传文件后,接口返回的是临时缓存 key。业务保存表单或设置时,后端必须调用 `FileService.saveFile(userId, key, "public" | "private")` 转成永久文件 key 后再入库/入设置;不要直接保存 `tmpfile_key_...`,否则后续回显或下载会失效。
## Service 与事务
- 后端方法参数超过 3 个时,尽量改为对象参数传入。
- 需要传入 `manager` / `EntityManager` 做事务传播的方法,必须使用对象参数,不要把 `manager` 作为位置参数藏在参数列表末尾。
- 后端 service 层只有存在事务链路传播需求时才定义 `ctx`,不要为了将来可能需要而提前给普通方法加 `ctx`
- 事务链路方法统一采用 `method(ctx, req)` 形式,`ctx` 放第一位并承载 `manager?: EntityManager` 等横切上下文,业务参数放在 `req` 对象里,例如 `settleCommission({ manager }, { tradeId, userId, amount })`
- 无事务链路需求的普通查询、纯函数和简单私有方法继续使用明确参数。
- service 内部需要根据事务上下文选择 Repository 时,优先使用 `BaseService.getRepo(ctx, EntityClass)`;不要在业务方法里反复写 `ctx.manager?.getRepository(Entity) || this.xxxRepository`
- 拿到 repo 后 save/update/delete/find 都能做,不需要再包一层 `saveEntity` 之类的单一用途方法。
- service 拼接用户与项目范围查询条件时,优先使用 `BaseService.buildUserProjectQuery(userId, projectId)`;不要直接写 `{ userId, projectId }`,否则 `projectId` 为空时可能把 `null`/`undefined` 带入 TypeORM 条件,导致查询或 update 不符合预期。
- `ctx` 类型统一从 `BaseService` 导出的 `ServiceContext` 复用,不要在每个 service 里重复定义。
- 需要“有事务则复用、无事务则开启”时,使用 `BaseService.transactionWithCtx(ctx, callback)``ctx.manager` 存在则直接执行 callback,否则自动 `this.transaction()`。不要在业务代码里手写 `if (ctx.manager) { ... } else { await this.transaction(...) }`
- 新增方法注意不要与 `BaseService` 基类方法签名冲突,例如 `delete(id)` vs `BaseService.delete(ids, where?)`ts-node 下会直接 TS2416 编译报错。冲突时改用具体名称如 `deleteById`
-26
View File
@@ -1,26 +0,0 @@
# 代码风格规则
## 基本原则
- 中文 README 在部分 PowerShell 环境中可能显示乱码;`README_en.md` 可读性更好,且包含同样的高层项目说明。
- 根包管理器是 pnpm,不要引入 npm/yarn lockfile。
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象。
- 注意本地数据和配置里可能包含凭据、证书材料等敏感信息。
## 注释
本仓库代码注释优先使用中文,尤其是解释业务规则、兼容逻辑、协议细节和隐藏风险时;除非文件已有明确英文注释风格或引用外部英文术语,否则不要新增英文说明性注释。
## 可读性
代码可读性优先于短写法。遇到包含业务分支的复杂三元表达式、内联对象、链式调用或条件组合时,优先拆成命名清晰的中间变量、独立分支或小函数,让读代码的人能一眼看出业务意图;不要为了少写几行把逻辑压成难读的一坨。
在对象字面量、查询条件或函数参数里不要内联调用多层 helper,例如 `{ domain, ...this.buildUserProjectQuery(userId, projectId) }`。应先用命名变量承接结果,例如 `const userProjectQuery = this.buildUserProjectQuery(userId, projectId)`,再在对象里展开 `...userProjectQuery`,让条件构造和业务字段都更容易阅读。
## DRY
遵守 DRY 原则:同一业务规则、字段转换、权限判断、Repository 选择、事务传播、金额计算等逻辑不要在多个地方复制粘贴。第二次出现时可以先保持清晰,第三次出现前应优先抽成局部 helper、service 方法或已有公共工具;抽象要服务于减少真实重复和降低修改风险,不要为了形式上的“复用”制造过度设计。
## 单一职责
遵守单一职责原则:一个方法只负责一个清晰的业务步骤或技术步骤。流程编排方法可以串联多个步骤,但具体的校验、计算、持久化、状态变更、展示数据组装应尽量拆到命名明确的小方法中;不要让一个方法同时承担查询、校验、计算、写库、格式化返回等过多职责。
-31
View File
@@ -1,31 +0,0 @@
# 前端规则
主包:`packages/ui/certd-client`。前端使用 Vue 3、Vite、TypeScript、Ant Design Vue、Fast Crud、Pinia、vue-router、vue-i18n、Tailwind/Windi 相关样式工具。
详细入口、路由、状态、API、视图、locale 和验证命令见 `.codex/repo-map.md`
## 禁跑命令
- 不要运行前端 `pnpm tsc` / `vue-tsc`:当前依赖组合中 `vue-tsc@1.8.27` 会直接抛内部错误 `Search string not found: "/supportedTSExtensions = .*(?=;)/"`,不是有效的项目类型检查结果。
- 前端暂不跑单元测试;当前 `test:unit` 只是占位脚本。
## 格式化与校验
前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复:
- Prettier`packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`
- ESLint`packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`
不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
## Fast Crud 页面
- 列表管理、后台管理、记录查询、CRUD 表格类页面,默认优先使用 Fast Crud(`@fast-crud/fast-crud``fs-crud``useFs``createCrudOptions`)实现。
- 只有轻量只读展示、强交互自定义界面或已有页面模式明确不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明原因。
- 开发或重构这类页面前,先读取 `.trae/skills/fast-crud-page-dev/SKILL.md`,按仓库内 Fast Crud 页面拆分与验证方式实现。
- 页面内嵌 Fast Crud 表格时,要显式给外层容器稳定高度或 `flex: 1; min-height: 0` 的撑满链路;Fast Crud 依赖外部元素高度,不能只依赖表格默认高度。
- 后台管理列表里展示或筛选用户字段时,优先参考 `packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx``userId` 字段模式:前端使用 `table-select` + `/sys/authority/user/getSimpleUserByIds` 字典回显和搜索;不要为了展示用户名让后端列表接口额外 `fillSimpleUser` / `userDisplay`,除非该接口本身就是用户端业务列表且已有明确模式。
## 对话框
前端对话框里只做纯确认时可以使用 `Modal.confirm`;只要对话框里有字段输入、表单校验或提交字段,统一使用 `useFormDialog` / `openFormDialog`,不要在 `Modal.confirm``content` 里手写输入框。
-42
View File
@@ -1,42 +0,0 @@
# 流水线与插件规则
项目最关键的架构概念是证书流水线。核心导出、关键抽象、插件目录和共享 helper 位置见 `.codex/repo-map.md`
插件是核心能力,不是边缘功能。新增服务商、DNS 验证、证书部署、通知方式等能力,通常应该放在插件包里,或放在 `packages/ui/certd-server/src/plugins/<plugin-name>/` 下。
## 改动归属
修改证书申请、验证、部署或通知行为时,先判断改动属于哪里:
- ACME client 代码
- pipeline 核心抽象
- 后端 module/service/entity/controller
- 某个具体插件实现
- 前端 view/form/schema
如果只是某个服务商或部署目标的问题,不要轻易修改共享 pipeline/core 行为,除非确实是可复用的公共能力。
## ACME / EAB
- 公共 EAB(尤其是 Google EAB)可能只能创建一次 ACME 账号。要跨用户复用公共 EAB,应保存并复用同一个 ACME account private key`accountUrl` 如果存到 `userContext` 里,只能视为当前用户缓存,因为 `userContext` 跟用户 id 走。
- ACME 协议的 `newAccount` 支持 `onlyReturnExisting`。使用同一个 account private key 调用 `newAccount({ onlyReturnExisting: true })` 可以取回已创建账号的 URL,且不会再次消费 EAB。
- 修改 EAB 的 `kid` 后,应重新生成绑定该 `kid` 的 account private key;否则应阻止继续申请并提示用户刷新账号私钥。
## 插件开发技能
仓库内置了 Certd 插件开发技能,供 Trae 和 Codex 共用:
- Trae 入口:`.trae/skills`
- Codex 入口:`.codex/skills`
其中 `.codex/skills` 是指向 `.trae/skills` 的目录链接,不要复制出第二份技能内容。更新技能时只维护 `.trae/skills` 下的原始文件,Codex 会通过 `.codex/skills` 读取同一份内容。
当前技能包括:
- `access-plugin-dev`:开发 Access 授权插件
- `dns-provider-dev`:开发 DNS Provider 插件
- `fast-crud-page-dev`:开发或重构前端 Fast Crud 列表管理页面
- `task-plugin-dev`:开发 Task 部署任务插件
- `plugin-converter`:将插件转换为 YAML 配置
做插件相关任务时,先读取对应技能目录下的 `SKILL.md`,再进入具体实现。若用户在插件开发中指出更好的做法,应总结并更新对应技能。
-26
View File
@@ -1,26 +0,0 @@
# 测试与验证规则
实现新功能或修复行为缺陷前,先补对应单元测试,并先运行测试确认它处于失败状态;再实现功能或修复代码,反复运行聚焦单元测试直到通过。若某项改动确实不适合先写单元测试,应在回复中说明原因和替代验证方式。
后补单元测试时,应先基于对正确行为的实际预期编写测试,而不是为了迎合现有实现改写预期;如果运行后出现红灯,且通过测试需要修改已有实现,应先向用户确认这是确实的 bug,还是原本需求/既有行为就是如此;确认后再修改原始实现,避免把测试补充变成未经确认的行为改动。
## 后端单测
- 后端纯单元测试用例放在 `src` 目录内,并尽量与被测文件相邻,例如 `src/utils/random.test.ts`
- 对应 `test:unit` 只跑 `src/**/*.test.ts`,构建/打包配置应排除这些 `*.test.ts` 文件。
- 单元测试需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试把业务代码改成构造函数注入或把逻辑挪到调用方。
- 各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`
## 运行方式
单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如:
- `corepack pnpm --dir packages\ui\certd-server test:unit`
- `corepack pnpm --dir packages\core\basic test:unit`
- `corepack pnpm --dir packages\plugins\plugin-lib test:unit`
也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`
前端 `packages\ui\certd-client` 暂时不跑单元测试。前端改动优先使用 Prettier/ESLint 做改动文件验证。
优先对改动包运行聚焦测试;只有跨包影响明显时再考虑全 monorepo 构建。
-106
View File
@@ -1,106 +0,0 @@
# Certd 仓库地图
本文档由 Codex 子智能体只读探索后整理,用于后续开发时快速定位代码。进入仓库仍应先读取根目录 `AGENTS.md`,本文件只作为导航补充。
## 顶层结构
Certd 是 pnpm + lerna-lite monorepo。
- `package.json`:根脚本与 workspace 元信息
- `pnpm-workspace.yaml`workspace 匹配规则,包含 `packages/**``packages/ui/**`
- `lerna.json`lerna-lite 配置
- `docs`VitePress 文档站
- `docker`Docker 安装和运行相关文件
- `packages/core/acme-client`ACME 协议客户端
- `packages/core/basic`:共享基础工具
- `packages/core/pipeline`:流水线核心抽象、插件模型、执行上下文
- `packages/libs`:共享集成库
- `packages/plugins/plugin-lib`:证书、DNS Provider、格式转换等插件共享能力
- `packages/plugins/plugin-cert`:证书插件包入口
- `packages/ui/certd-server`:后端 Midway 服务
- `packages/ui/certd-client`:前端 Vue/Vite 管理台
- `packages/pro`:商业版独立 Git 工作区,需在该目录内单独检查状态
运行时或生成产物通常包括根目录 `node_modules``logs``output``lerna-debug.log``tmp-certd-client-vite*.log`,以及后端 `packages/ui/certd-server/data``packages/ui/certd-server/logs`、各包 `dist`、插件 metadata/yaml 导出结果。
## 常用验证
- 根目录启动后端生产模式:`pnpm run start:server`
- 后端开发服务:`corepack pnpm --dir packages\ui\certd-server dev`
- 后端聚焦单测:`corepack pnpm --dir packages\ui\certd-server test:unit`
- 后端完整测试:`corepack pnpm --dir packages\ui\certd-server test`
- 后端构建:`corepack pnpm --dir packages\ui\certd-server build`
- 前端开发服务:`corepack pnpm --dir packages\ui\certd-client dev`
- 前端构建:`corepack pnpm --dir packages\ui\certd-client build`
- 前端改动文件格式化:`packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`
- 前端改动文件 ESLint 修复:`packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`
不要主动运行 `pnpm install`。前端不要运行 `pnpm tsc` / `vue-tsc`,当前依赖组合中 `vue-tsc@1.8.27` 会抛无效内部错误;前端 `test:unit` 也只是占位。
## 后端地图
主包:`packages/ui/certd-server`
- `bootstrap.js`Midway 启动入口,使用 `@midwayjs/bootstrap`
- `src/configuration.ts`:Midway 主配置,注册组件和全局中间件
- `src/config/config.default.ts`:端口、HTTPS、静态文件、cron、TypeORM、Flyway、上传、JWT、Swagger 默认配置
- `src/config/loader.ts`:读取 `.env``.env.<env>.yaml`,支持 `certd_` 前缀环境变量覆盖嵌套配置
- `src/modules`:业务模块根目录,例如 `basic``cert``cname``cron``login``monitor``open``pipeline``plugin``suite``sys`
- `src/controller`API 入口,按 `basic``user``sys``openapi` 分组
- `db/migration`SQL 迁移目录,TypeORM `synchronize: false`,表结构变更应配套迁移 SQL
测试使用 Mocha + Node `assert/strict`,纯单测放在 `src/**/*.test.ts`,尽量与被测文件相邻。可参考 `src/utils/random.test.ts``src/controller/basic/app-controller.test.ts``src/modules/pipeline/service/pipeline-service.test.ts`
## 前端地图
主包:`packages/ui/certd-client`
- `vite.config.ts`Vite 配置,dev 端口 `3008``/api``/certd/api` 代理到后端 `127.0.0.1:7001`
- `src/main.ts`:Vue 启动入口,注册 AntDV、Vben、router、全局组件、插件和偏好设置
- `src/App.vue`:根组件,包含 `AConfigProvider``FsFormProvider``router-view`
- `src/router/index.ts``src/router/resolve.ts`:路由入口,使用 `createWebHashHistory`
- `src/router/source/modules/certd.ts`Certd 主业务路由
- `src/store`Pinia store,主要有 `user``project``settings``plugin`
- `src/api/service.ts`Axios 封装
- `src/api/tools.ts`:错误与响应工具
- `src/views/certd`:核心业务视图,例如 `pipeline``cert``monitor``access``notification``open``project``suite``wallet`
- `src/locales`:国际化入口与语言包
列表管理、后台管理、记录查询、CRUD 表格页面优先使用 Fast Crud。开发前读取 `.trae/skills/fast-crud-page-dev/SKILL.md`。常见拆分是 `api.ts``crud.tsx``index.vue`。可参考 `src/views/certd/access``src/views/sys/suite/user-suite/crud.tsx``src/views/certd/wallet/index.vue`。内嵌 `fs-crud` 时要给外层稳定高度或完整 `flex: 1; min-height: 0` 链路。
## 流水线与插件地图
核心入口:`packages/core/pipeline/src/index.ts`,导出 `core``dt``access``registry``plugin``context``decorator``service``notification`
- `packages/core/pipeline/src/plugin`:任务插件抽象,例如 `AbstractTaskPlugin``IsTaskPlugin``TaskInput``pluginRegistry`
- `packages/core/pipeline/src/access`:授权插件抽象,例如 `BaseAccess``IsAccess``AccessInput``accessRegistry`
- `packages/core/pipeline/src/dt/pipeline.ts``Pipeline``Stage``Task``RunStrategy` 等流水线数据结构
- `packages/core/pipeline/src/core`:执行器、上下文、运行历史、文件存储等
- `packages/core/pipeline/src/service`:CNAME、事件、配置、邮件、URL 等 pipeline service 接口
- `packages/ui/certd-server/src/plugins`:后端内置服务商、DNS、部署、通知等插件
- `packages/ui/certd-server/src/plugins/plugin-cert`:证书申请核心插件
- `packages/ui/certd-server/src/plugins/plugin-lib`:后端插件 helper/access
- `packages/plugins/plugin-lib/src/cert``CertReader``CertConverter`、DNS Provider 公共能力
- `packages/plugins/plugin-lib/src/cert/dns-provider``AbstractDnsProvider``dnsProviderRegistry``DomainParser`
插件开发技能入口:
- `.trae/skills/dns-provider-dev/SKILL.md`DNS Provider 插件
- `.trae/skills/task-plugin-dev/SKILL.md`Task 部署任务插件
- `.trae/skills/access-plugin-dev/SKILL.md`Access 授权插件
- `.trae/skills/plugin-converter/SKILL.md`:插件转 YAML 配置
改动归属判断:
- ACME 协议、EAB、账号、订单、挑战流程:优先看 `packages/core/acme-client``packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts`
- 流水线执行、任务生命周期、输入输出、注册机制:看 `packages/core/pipeline`
- 单个云厂商 DNS 验证、证书部署、API 调用失败:改对应 `packages/ui/certd-server/src/plugins/plugin-xxx`
- 通用证书读取、DNS Provider 公共能力、格式转换:改 `packages/plugins/plugin-lib`
- 后端业务数据、接口、实体、权限、迁移:改 `packages/ui/certd-server/src/modules``src/controller`
- 表单、列表、插件配置 UI:改 `packages/ui/certd-client/src/views/certd` 及对应 `src/api`
原则:如果只是单个服务商或部署目标的问题,不动共享 pipeline/core;只有可复用的公共语义或跨插件一致行为,才考虑上移到 `packages/core/pipeline``packages/plugins/plugin-lib`
## Git 注意事项
子智能体探索时根仓库 `git status --short` 为空。`packages/pro` 也是独立仓库且当时未显示未提交改动,但曾出现无法删除 `packages/pro/.git/index.lock` 的警告;后续操作 pro 仓库前应先检查该锁文件或占用状态。
+6 -1
View File
@@ -1,4 +1,4 @@
./packages/core/lego ./packages/core/lego
# IntelliJ project files # IntelliJ project files
node_modules/ node_modules/
npm-debug.log npm-debug.log
@@ -35,3 +35,8 @@ test.js
.pnpm-lock.yaml .pnpm-lock.yaml
pnpm-lock.yaml pnpm-lock.yaml
.studio/ .studio/
# Certd 推广报告,仅本地使用
/popularize/reports/
output/
.uploads/
@@ -0,0 +1,412 @@
# 插件依赖按需加载方案
## 背景与目标
### 当前问题
- `packages/ui/certd-server/node_modules` 包含 50+ 个插件的所有依赖,体积庞大
- 大量云厂商 SDK(AWS、阿里云、腾讯云、华为云等)只在特定插件中使用
- 用户通常只使用少数几个插件,但必须安装所有依赖
### 目标
实现依赖的按需下载和加载:
1. 插件依赖独立管理,不占用主 `node_modules` 空间
2. 只有当用户首次使用某插件时,才动态下载该插件需要的依赖
3. 依赖安装完成后,通过 `await import()` 从独立路径加载
4. 保持现有插件代码的最小改动
## 当前架构分析
### 插件加载机制
- 插件位于 `packages/ui/certd-server/src/plugins/` 下(50+ 个插件目录)
- `AutoLoadPlugins` 类在启动时扫描 `dist/plugins` 目录并动态导入
- 插件注册到不同的 registry`accessRegistry`, `pluginRegistry`, `dnsProviderRegistry`
- 插件代码已经使用 `await import()` 进行懒加载(如 `await import("@aws-sdk/client-acm")`
### 重型依赖分布
`packages/ui/certd-server/package.json` 分析,以下依赖体积大且仅特定插件使用:
**云厂商 SDK(按插件分组):**
- **AWS 插件**`@aws-sdk/client-acm`, `@aws-sdk/client-cloudfront`, `@aws-sdk/client-iam`, `@aws-sdk/client-route-53`, `@aws-sdk/client-s3`, `@aws-sdk/client-sts`
- **阿里云插件**`@alicloud/openapi-client`, `@alicloud/pop-core`, `@alicloud/tea-typescript`, `@alicloud/fc20230330`
- **腾讯云插件**`tencentcloud-sdk-nodejs`, `cos-nodejs-sdk-v5`
- **华为云插件**`@huaweicloud/huaweicloud-sdk-cdn`, `@huaweicloud/huaweicloud-sdk-core`
- **Azure 插件**`@azure/arm-dns`, `@azure/identity`
- **Google Cloud 插件**`@google-cloud/dns`, `@google-cloud/publicca`
- **火山引擎插件**`@volcengine/openapi`, `@volcengine/tos-sdk`
**网络/工具库:**
- `ssh2`, `socks`, `socks-proxy-agent`SSH 相关插件)
- `ali-oss`, `qiniu`, `basic-ftp`(存储/传输插件)
- `nodemailer`(邮件通知插件)
**通用依赖(保留在主 package.json):**
- `@midwayjs/*` 系列(框架核心)
- `@certd/*` 系列(项目内部包)
- `axios`, `lodash-es`, `dayjs`, `js-yaml` 等基础工具
## 设计方案
### 架构概览
```
packages/ui/certd-server/
├── package.json # 主依赖(框架、通用工具)
├── node_modules/ # 主依赖安装目录
├── optional-deps/ # 新增:可选依赖管理目录
│ ├── package.json # 可选依赖总配置(用于 pnpm install
│ ├── pnpm-lock.yaml # 可选依赖锁文件
│ └── node_modules/ # 可选依赖安装目录
├── src/
│ └── modules/
│ └── dependency/ # 新增:依赖管理模块
│ ├── dependency-manager.ts # 核心:依赖管理器
│ ├── dependency-registry.ts # 依赖注册表(插件 -> 依赖映射)
│ └── types.ts # 类型定义
```
### 核心组件
#### 1. 依赖管理器(DependencyManager
**职责:**
- 检查依赖是否已安装
- 动态执行 `pnpm install` 安装缺失依赖
- 提供从 `optional-deps/node_modules` 加载依赖的方法
- 并发控制:避免多个插件同时触发安装
**关键方法:**
```typescript
class DependencyManager {
// 确保依赖已安装,返回依赖模块
async ensureAndImport<T>(packageName: string): Promise<T>
// 检查依赖是否已安装
async isInstalled(packageName: string): Promise<boolean>
// 安装依赖(带锁,避免并发)
async installDependencies(packages: string[]): Promise<void>
// 从 optional-deps/node_modules 加载依赖
async loadModule<T>(packageName: string): Promise<T>
}
```
**实现要点:**
- 使用文件锁(如 `proper-lockfile`)防止并发安装
- 安装前检查 `optional-deps/node_modules/{packageName}` 是否存在
- 安装命令:`pnpm install --dir optional-deps --ignore-workspace`
- 加载时使用绝对路径:`import('file:///absolute/path/to/optional-deps/node_modules/package')`
#### 2. 依赖注册表(DependencyRegistry
**职责:**
- 维护插件名称到依赖列表的映射
- 提供依赖查询接口
**数据结构:**
```typescript
interface PluginDependencyConfig {
pluginName: string;
dependencies: {
packageName: string;
version: string;
optional?: boolean; // 是否可选(安装失败不阻塞)
}[];
}
// 示例注册
dependencyRegistry.register('plugin-aws', [
{ packageName: '@aws-sdk/client-acm', version: '^3.964.0' },
{ packageName: '@aws-sdk/client-cloudfront', version: '^3.964.0' },
{ packageName: '@aws-sdk/client-route-53', version: '^3.964.0' },
]);
```
#### 3. 插件集成
**改造现有插件代码:**
改造前(`plugin-aws/libs/aws-client.ts`):
```typescript
const { ACMClient, ImportCertificateCommand } = await import("@aws-sdk/client-acm");
```
改造后:
```typescript
import { DependencyManager } from "../../../modules/dependency/dependency-manager.js";
const depManager = new DependencyManager();
const { ACMClient, ImportCertificateCommand } = await depManager.ensureAndImport("@aws-sdk/client-acm");
```
**简化方案(推荐):**
创建辅助函数,减少改动量:
```typescript
// src/modules/dependency/import-helper.ts
export async function importOptionalDep<T>(packageName: string): Promise<T> {
const depManager = new DependencyManager();
return await depManager.ensureAndImport<T>(packageName);
}
// 插件中使用
import { importOptionalDep } from "../../../modules/dependency/import-helper.js";
const { ACMClient } = await importOptionalDep("@aws-sdk/client-acm");
```
### 实施步骤
#### 阶段一:基础设施搭建
1. 创建 `optional-deps/` 目录结构
2. 生成 `optional-deps/package.json`(包含所有可选依赖)
3. 实现 `DependencyManager` 核心逻辑
4. 实现依赖安装锁机制
5. 编写单元测试
#### 阶段二:依赖迁移
6. 从主 `package.json` 移除可选依赖
7. 将依赖添加到 `optional-deps/package.json`
8. 创建依赖注册表,映射插件到依赖
#### 阶段三:插件改造
9. 创建 `import-helper.ts` 辅助函数
10. 逐步改造插件代码,使用 `importOptionalDep` 加载依赖
11. 优先改造重型依赖(AWS、阿里云、腾讯云等)
#### 阶段四:测试与优化
12. 端到端测试:验证依赖按需安装和加载
13. 性能优化:缓存已加载的模块
14. 错误处理:安装失败时的降级策略
15. 文档:编写使用说明和迁移指南
## 关键技术决策
### 1. 依赖分组策略
**选择:按插件分组**
- 每个插件声明自己需要的依赖
- 优点:职责清晰,易于维护
- 缺点:可能有重复依赖(但 pnpm 会去重)
**备选:按功能分组**
- 将依赖按功能分组(如 "aws-deps", "aliyun-deps"
- 优点:更细粒度控制
- 缺点:增加复杂度
### 2. 安装触发时机
**选择:首次使用时触发**
- 在插件的 `execute()``getClient()` 方法中触发安装
- 优点:真正的按需加载
- 缺点:首次使用有延迟
**备选:启动时预检查**
- 启动时扫描启用的插件,预安装依赖
- 优点:避免运行时延迟
- 缺点:可能安装不需要的依赖
### 3. 依赖路径解析
**选择:使用绝对路径 + `file://` 协议**
```typescript
const modulePath = path.resolve(__dirname, '../../optional-deps/node_modules', packageName);
return await import(`file://${modulePath}/index.js`);
```
**原因:**
- Node.js ESM 要求明确的 URL 格式
- 避免模块解析冲突
### 4. 并发控制
**选择:文件锁 + 内存锁双重保护**
- 使用 `proper-lockfile` 锁定 `optional-deps/` 目录
- 内存中使用 `Map` 记录正在安装的依赖
- 避免多个插件同时触发安装
### 5. 错误处理
**策略:**
- 安装失败时记录日志,抛出明确的错误信息
- 提供手动安装命令提示:`请运行: cd optional-deps && pnpm install`
- 支持降级:某些非核心依赖安装失败时,插件可以部分功能可用
## 验证方案
### 单元测试
1. 测试 `DependencyManager.isInstalled()` 正确检测依赖状态
2. 测试 `DependencyManager.installDependencies()` 成功安装依赖
3. 测试并发安装时的锁机制
4. 测试从 `optional-deps/node_modules` 加载模块
### 集成测试
1. 清空 `optional-deps/node_modules`
2. 启动服务,验证不触发安装
3. 调用 AWS 插件,验证触发安装并成功加载
4. 再次调用,验证不重复安装
5. 验证主 `node_modules` 体积减少
### 性能测试
1. 测量首次安装依赖的耗时
2. 测量后续加载的耗时(应该与正常 import 相近)
3. 对比改造前后的 `node_modules` 大小
## 风险与挑战
### 1. 首次使用延迟
**风险:** 用户首次使用插件时需要等待依赖安装(可能几十秒)
**缓解:**
- 在 UI 上显示安装进度
- 提供预安装命令:`pnpm run install-optional-deps`
- 文档说明首次使用会有延迟
### 2. 离线环境
**风险:** 离线环境无法下载依赖
**缓解:**
- 提供完整安装包(包含所有可选依赖)
- 支持手动复制 `node_modules`
### 3. 版本冲突
**风险:** 可选依赖与主依赖版本冲突
**缓解:**
- 使用 `--ignore-workspace` 隔离安装
- 定期同步主依赖版本
### 4. TypeScript 类型
**风险:** 动态导入的类型推断
**缓解:**
- 保留 `@types/*` 在主 `devDependencies`
- 使用泛型和类型断言
## 预期收益
1. **空间节省:**`node_modules` 体积减少 60-70%(估算)
2. **安装速度:** 初始 `pnpm install` 速度提升 3-5 倍
3. **用户体验:** 不使用的插件不占用空间,按需加载
4. **维护性:** 依赖分组清晰,易于管理
## 后续优化
1. **依赖预热:** 在后台预安装常用插件依赖
2. **依赖缓存:** 支持从 CDN 或本地缓存安装
3. **依赖更新:** 提供命令批量更新可选依赖
4. **插件市场:** 支持从远程下载插件及其依赖配置
## 附录:依赖分类清单
### 可选依赖(迁移到 optional-deps/package.json
**AWS 相关(plugin-aws, plugin-aws-cn):**
```json
{
"@aws-sdk/client-acm": "^3.964.0",
"@aws-sdk/client-cloudfront": "^3.964.0",
"@aws-sdk/client-iam": "^3.964.0",
"@aws-sdk/client-route-53": "^3.964.0",
"@aws-sdk/client-s3": "^3.964.0",
"@aws-sdk/client-sts": "^3.990.0"
}
```
**阿里云相关(plugin-aliyun, plugin-lib/aliyun):**
```json
{
"@alicloud/fc20230330": "^4.1.7",
"@alicloud/openapi-client": "^0.4.12",
"@alicloud/openapi-util": "^0.3.2",
"@alicloud/pop-core": "^1.7.10",
"@alicloud/sts-sdk": "^1.0.2",
"@alicloud/tea-typescript": "^1.8.0",
"@alicloud/tea-util": "^1.4.10",
"ali-oss": "^6.21.0"
}
```
**腾讯云相关(plugin-tencent, plugin-lib/tencent):**
```json
{
"tencentcloud-sdk-nodejs": "^4.1.112",
"cos-nodejs-sdk-v5": "^2.14.6"
}
```
**华为云相关(plugin-huawei):**
```json
{
"@huaweicloud/huaweicloud-sdk-cdn": "3.1.185",
"@huaweicloud/huaweicloud-sdk-core": "3.1.185",
"@huaweicloud/huaweicloud-sdk-elb": "3.1.185",
"@huaweicloud/huaweicloud-sdk-iam": "3.1.185",
"esdk-obs-nodejs": "^3.25.6"
}
```
**Azure 相关(plugin-azure):**
```json
{
"@azure/arm-dns": "^5.1.0",
"@azure/identity": "^4.13.1"
}
```
**Google Cloud 相关(plugin-google, plugin-cert/google):**
```json
{
"@google-cloud/dns": "^5.3.1",
"@google-cloud/publicca": "^1.3.0"
}
```
**火山引擎相关(plugin-volcengine):**
```json
{
"@volcengine/openapi": "^1.28.1",
"@volcengine/tos-sdk": "^2.9.1"
}
```
**SSH/网络相关(plugin-host, plugin-lib/ssh):**
```json
{
"ssh2": "^1.17.0",
"socks": "^2.8.3",
"socks-proxy-agent": "^8.0.4",
"basic-ftp": "^5.0.5"
}
```
**其他存储/传输(plugin-qiniu, plugin-lib/qiniu):**
```json
{
"qiniu": "^7.12.0"
}
```
**邮件通知(plugin-notification/email):**
```json
{
"nodemailer": "^6.9.16"
}
```
### 主依赖(保留在主 package.json
**框架核心:**
- `@midwayjs/*` 系列
- `@koa/cors`
- `typeorm`, `better-sqlite3`, `mysql2`, `pg`
**项目内部包:**
- `@certd/*` 系列
**通用工具:**
- `axios`, `lodash-es`, `dayjs`, `js-yaml`
- `crypto-js`, `jsonwebtoken`, `bcryptjs`
- `reflect-metadata`, `uuid`, `nanoid`
- 等等
## 总结
本方案通过引入独立的可选依赖管理机制,实现了插件依赖的按需下载和加载。核心思路是:
1. **隔离管理:**`optional-deps/` 目录下维护独立的 `package.json``node_modules`
2. **动态安装:** 通过 `DependencyManager` 在首次使用时触发 `pnpm install`
3. **路径加载:** 使用绝对路径从独立目录加载依赖模块
4. **最小改动:** 通过辅助函数 `importOptionalDep` 简化插件代码改造
该方案可以显著减少主 `node_modules` 体积,提升初始安装速度,同时保持现有架构的兼容性和可维护性。
-13
View File
@@ -1,13 +0,0 @@
你是一名资深nodejs工程师,擅长开发Certd开源系统的任务插件。
certd是一款全自动证书申请部署管理工具,基于流水线的方式,通过里面申请证书插件申请证书,然后将证书传递给下一个部署任务插件,不同的部署任务插件将证书部署到用户的各个应用系统当中。
certd插件分成以下几种类型:
Access:存储用户的第三放应用的授权数据,比如用户名密码,accessSecret 或 accessToken等。同时它里面的方法还负责对接第三方的api接口
Task 部署任务插件,它继承AbstractTaskPlugin类,被流水线调用execute方法,将证书部署到对应的应用上
DnsProvider: DNS提供商插件,它用于在ACME申请证书时给域名添加txt解析记录。
注意事项:
1、使用技能:在开始工作前,请阅读并加载.trae/skills下面的技能,根据skills进行相应的插件开发
2、迭代技能:当开发过程用户提醒你更好的做法时,你需要总结经验,更新相应的skills,让skills越来越完善,能够在以后得新插件开发中具备指导意义。
3、一般调用的api接口文档会比较复杂,你不知道接口是什么时,请务必询问用户,让用户提供API接口文档
4、完成开发后无需测试,通知用户自己去测试
+79 -1
View File
@@ -25,7 +25,7 @@ version: 1.0.0
## 实现流程 ## 实现流程
1. 先在 `packages/ui/certd-client/src/views` 下找 1-2 个相近 Fast Crud 页面,沿用它们的导入、布局、命名和权限写法。 1. 先在 `packages/ui/certd-client/src/views` 下找 1-2 个相近 Fast Crud 页面,沿用它们的导入、布局、命名和权限写法。
2.`index.vue` 中使用 `fs-crud ref="crudRef" v-bind="crudBinding"`,并在 `onMounted` / `onActivated` 时调用 `crudExpose.doRefresh()` 2.`index.vue` 中使用 `fs-crud ref="crudRef" v-bind="crudBinding"`,并在 `onMounted` `onActivated` 时调用 `crudExpose.doRefresh()`;两个生命周期同时存在时只保留一个刷新入口,避免首次进入页面请求两次
3.`crud.tsx` 中配置 `request.pageRequest``columns``search``form``rowHandle``actionbar``toolbar` 等,接口分页参数和返回值按现有页面适配。 3.`crud.tsx` 中配置 `request.pageRequest``columns``search``form``rowHandle``actionbar``toolbar` 等,接口分页参数和返回值按现有页面适配。
4. 操作按钮优先放在 Fast Crud 的 `rowHandle.buttons``actionbar.buttons` 中;审核、保存设置、批量操作等复杂交互可通过 `context` 调用 `index.vue` 中的方法。 4. 操作按钮优先放在 Fast Crud 的 `rowHandle.buttons``actionbar.buttons` 中;审核、保存设置、批量操作等复杂交互可通过 `context` 调用 `index.vue` 中的方法。
5. 金额、状态、时间、枚举等字段优先复用项目已有组件、字典和格式化工具;避免在模板里重复堆格式化逻辑。 5. 金额、状态、时间、枚举等字段优先复用项目已有组件、字典和格式化工具;避免在模板里重复堆格式化逻辑。
@@ -77,6 +77,84 @@ container:{}, //容器配置 ,对应fs-container
- 有固定操作栏、统计区、说明区时,这些区域应 `flex: none`,把剩余空间交给表格区域。 - 有固定操作栏、统计区、说明区时,这些区域应 `flex: none`,把剩余空间交给表格区域。
- 修改嵌入式 Fast Crud 页面后,要检查空数据、少量数据和多页数据时表格高度、分页器和空状态是否仍在预期区域内。 - 修改嵌入式 Fast Crud 页面后,要检查空数据、少量数据和多页数据时表格高度、分页器和空状态是否仍在预期区域内。
## 列表导出
- 列表需要导出时,优先使用 Fast Crud 工具栏导出能力,不要另写一套导出按钮或后端接口,除非数据必须跨权限、跨分页或异步生成文件。
- 导出当前搜索条件下的数据时,在 `toolbar.export` 中设置 `dataFrom: "search"`,并显式打开导出按钮。
- 导出列必须输出 Excel 可读的纯文本或数字;不要直接导出对象、数组、VNode、进度条组件、开关组件、时间戳毫秒值等。
- 有隐藏但业务上需要导出的字段时,把字段定义为普通列并设置 `column.show: false`,再在 `columnFilter` 中对该字段返回 `true`。例如证书域名这类只用于导出的辅助列。
- 嵌套字段可以使用 `lastVars.certDomains` 这类 key;导出格式化时用安全取值函数读取嵌套值。
- `dataFormatter` 中统一格式化特殊字段:时间字段转 `YYYY-MM-DD HH:mm:ss`,日期类有效期转业务文案或 `YYYY-MM-DD`,枚举/开关转字典 label,数组转逗号分隔字符串,对象转明确的业务摘要。
```typescript
import { ColumnProps, DataFormatterContext } from "@fast-crud/fast-crud";
import dayjs from "dayjs";
function getRecordValue(row: any, key: string) {
return key.split(".").reduce((target, item) => target?.[item], row);
}
function formatListValue(value: any) {
if (Array.isArray(value)) {
return value.join(",");
}
return value ?? "";
}
function exportColumnFilter(col: ColumnProps) {
if (!col.key || ["_index", "_selection", "rowHandle"].includes(col.key)) {
return false;
}
if (col.key === "lastVars.certDomains") {
return true;
}
return col.show !== false;
}
function exportDataFormatter(opts: DataFormatterContext) {
const { row, originalRow, col, exportCol } = opts;
const key = col.key;
const value = getRecordValue(originalRow, key);
if (key === "lastVars.certDomains") {
row[key] = formatListValue(value);
} else if (key.includes("Time") && value) {
row[key] = dayjs(value).format("YYYY-MM-DD HH:mm:ss");
}
if (col.width) {
exportCol.width = col.width / 10;
}
}
return {
crudOptions: {
toolbar: {
buttons: {
export: { show: true },
},
export: {
dataFrom: "search",
columnFilter: exportColumnFilter,
dataFormatter: exportDataFormatter,
},
},
columns: {
"lastVars.certDomains": {
title: "证书域名",
type: "text",
column: {
show: false,
width: 260,
ellipsis: true,
},
form: { show: false },
},
},
},
};
```
## 内置 CRUD 按钮 ## 内置 CRUD 按钮
只要在 `request` 中配置了 `addRequest``editRequest``delRequest`Fast Crud 会自动在 `rowHandle` 渲染新增、编辑、删除按钮并完成对应操作,**不需要手写 `openDeleteConfirm``openEditDialog` 等方法**。 只要在 `request` 中配置了 `addRequest``editRequest``delRequest`Fast Crud 会自动在 `rowHandle` 渲染新增、编辑、删除按钮并完成对应操作,**不需要手写 `openDeleteConfirm``openEditDialog` 等方法**。
+193 -54
View File
@@ -1,71 +1,210 @@
# Certd 开发 Agent 上下文 # Certd 开发 Agent 上下文
这个文件是给在本仓库工作的开发 agent 看的常驻项目说明。进入仓库后先读本文,再按任务读取对应导航或规则文件,避免每次重新全量扫描项目 进入仓库后先读本文。本文同时包含常驻规则、仓库地图、常用入口和验证命令;不要依赖分散规则文件
仓库代码导航、目录地图、常用入口和参考文件见 `.codex/repo-map.md`。更细的开发规则拆在 `.codex/agent-rules/` 下;本文只保留最高优先级的规则、架构边界和工作方式。
## 项目定位 ## 项目定位
Certd 是支持私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web 管理台和后端服务,用于证书申请、续期、部署、监控、通知和开放 API 集成。 Certd 是私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web 管理台和后端服务,用于证书申请、续期、部署、监控、通知和开放 API 集成。
核心产品模型是“证书流水线”: 核心模型是“证书流水线”:
- 通过 ACME 申请证书 - 通过 ACME 申请证书
- 使用 DNS-01、HTTP-01、CNAME 代理或服务商集成完成域名验证 - 完成 DNS-01、HTTP-01、CNAME 代理或服务商验证
- 将证书转换或导出 pem、pfx、der、jks、p7b 等格式 - 导出 pem、pfx、der、jks、p7b 等证书格式
- 部署证书到主机、Nginx、Kubernetes、CDN、云厂商、面板等目标 - 部署到主机、Nginx、Kubernetes、CDN、云厂商、面板等目标
- 通知用户,并监控站点证书过期时间 - 通知用户,并监控站点证书过期时间
系统会保存证书、云厂商凭据、SSH 信息、API Key 等敏感数据始终按私有化/本地部署产品处理,避免泄露本地数据和配置。 系统会保存证书、云厂商凭据、SSH 信息、API Key 等敏感数据始终按私有化/本地部署产品处理,避免泄露本地数据和配置。
## 必读索引
- `.codex/repo-map.md`:仓库结构、后端/前端入口、流水线与插件地图、验证命令
- `.codex/agent-rules/backend.md`:后端、数据库迁移、文件上传、service/事务约定
- `.codex/agent-rules/frontend.md`:前端、Fast Crud、弹窗表单、格式化和禁跑命令
- `.codex/agent-rules/plugins.md`:流水线、插件归属、ACME/EAB、插件开发技能
- `.codex/agent-rules/testing.md`:测试优先策略、单测位置、ESM mock、聚焦验证
- `.codex/agent-rules/coding-style.md`:注释、可读性、DRY、单一职责等通用代码风格
## 仓库边界 ## 仓库边界
这是一个 pnpm + lerna monorepo。核心定位: - 根仓库是 pnpm + lerna monorepo;不要引入 npm/yarn lockfile
- `packages/pro/` 是独立 Git 工作区;修改商业版代码后,必须在 `packages/pro` 内单独执行 `git status` / `git diff`
- `packages/ui/certd-server`:后端服务
- `packages/ui/certd-client`:前端 Web 管理台
- `packages/core/pipeline`:流水线核心
- `packages/core/acme-client`ACME 协议客户端
- `packages/plugins/plugin-lib`:通用插件辅助能力和证书相关共享代码
`packages/pro/` 是独立 Git 工作区,使用 `packages/pro/.git` 管理。根仓库的 `git status` / `git diff` 默认看不到这里的实际改动;修改商业版代码后,要在 `packages/pro` 目录内单独执行 `git status` / `git diff` 检查。
## 硬性规则
- 根包管理器是 pnpm,不要引入 npm/yarn lockfile。
- 不要主动运行 `pnpm install`;用户会事先准备好 `node_modules`。如果 `pnpm install` 或测试因缺少依赖、TTY、网络问题失败,停止尝试并告知用户环境问题。
- 前端不要运行 `pnpm tsc` / `vue-tsc`;当前依赖组合中 `vue-tsc@1.8.27` 会抛无效内部错误。前端 `test:unit` 只是占位脚本。
- 不要把 `packages/ui/certd-server/data/``logs/`、生成的 metadata/dist 等运行时或构建产物纳入改动,除非任务明确要求。 - 不要把 `packages/ui/certd-server/data/``logs/`、生成的 metadata/dist 等运行时或构建产物纳入改动,除非任务明确要求。
- 做数据库结构变更时,添加或更新迁移脚本,不要依赖 TypeORM 自动同步 - 例外:分析插件动态依赖时,可以只读查看后端数据目录下的 `./data/.runtime-deps`;该目录用于 runtime-deps 动态安装第三方 SDK,不应纳入提交
- 做插件相关任务时,先读取对应 `.trae/skills/<skill>/SKILL.md`,再进入具体实现。
- 后端 service 拼接可选 `projectId` 查询条件时,不要直接写 `{ userId, projectId }`;应使用 `BaseService.buildUserProjectQuery(userId, projectId)`,只有 `projectId != null` 时才加入查询条件。
## 工作方式 核心包:
- 先读本文;需要代码导航、目录入口、参考文件或验证命令时读 `.codex/repo-map.md` - `packages/ui/certd-server`:后端服务
- 任务涉及后端、前端、插件、测试或代码风格时,先读取 `.codex/agent-rules/` 下对应规则文件,再查看具体代码 - `packages/ui/certd-client`:前端 Web 管理台
- 在 PowerShell 中读取中文、Markdown、locale、文档类文件时,显式使用 `Get-Content -Encoding utf8`;如果仍乱码,再执行 `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()` 后重试 - `packages/core/pipeline`:流水线核心
- `packages/core/acme-client`ACME 客户端。
- `packages/plugins/plugin-lib`:插件共享能力。
## 仓库地图
- `package.json`:根脚本与 workspace 元信息。
- `pnpm-workspace.yaml`workspace 匹配规则,包含 `packages/**``packages/ui/**`
- `lerna.json`lerna-lite 配置。
- `docs`VitePress 文档站。
- `docker`Docker 安装和运行相关文件。
- `packages/core/acme-client`ACME 协议客户端。
- `packages/core/basic`:共享基础工具。
- `packages/core/pipeline`:流水线核心抽象、插件模型、执行上下文。
- `packages/libs`:共享集成库。
- `packages/plugins/plugin-lib`:证书、DNS Provider、格式转换等插件共享能力。
- `packages/plugins/plugin-cert`:证书插件包入口。
- `packages/ui/certd-server`:后端 Midway 服务。
- `packages/ui/certd-client`:前端 Vue/Vite 管理台。
- `packages/pro`:商业版独立 Git 工作区,需在该目录内单独检查状态。
常见运行时或生成产物:
- 根目录:`node_modules``logs``output``lerna-debug.log``tmp-certd-client-vite*.log`
- 后端:`packages/ui/certd-server/data``packages/ui/certd-server/logs`
- 后端动态依赖:`./data/.runtime-deps`,常见于阿里云 SDK、腾讯云 SDK 等插件第三方依赖。
- 各包:`dist`
- 插件:metadata/yaml 导出结果。
## 常用验证
- 后端聚焦单测:`corepack pnpm --dir packages\ui\certd-server test:unit`
- 后端完整测试:`corepack pnpm --dir packages\ui\certd-server test`
- 前端构建:`corepack pnpm --dir packages\ui\certd-client build`
- 前端改动文件格式化:`packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`
- 前端改动文件 ESLint 修复:`packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`
- 后端改动文件 lint fix`corepack pnpm --dir packages\ui\certd-server run lint`
- 其他package lint fix`corepack pnpm --dir packages\xxx\xxxx run lint`
## 通用工作规则
- 先读本文,再按任务读取具体代码或技能文件。
- PowerShell 读取中文、Markdown、locale、文档类文件时使用 `Get-Content -Raw -Encoding UTF8`;仍乱码时先执行 `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()`
- PowerShell 中用 `rg` 搜索含引号、括号、反斜杠的 pattern 时,优先用单引号包裹整个 pattern,例如 `rg 'await import\("tencentcloud-sdk-nodejs' packages/ui/certd-server/src -g '*.ts'`
- 不要主动运行 `pnpm install`;缺依赖、TTY、网络导致安装或测试失败时,停止尝试并说明环境问题。
- 优先沿用现有模块、插件、service、页面模式;不要为形式上的复用制造过度抽象。
- 代码可读性优先于短写法。复杂条件、三元表达式、链式调用、内联对象和多层 helper 调用要拆成命名清晰的中间变量或小方法。
- 方法调用链不要直接塞进另一个方法参数;先用有意义的局部变量承接返回值,再传入下一步。
- 注释优先使用中文,尤其是业务规则、兼容逻辑、协议细节和隐藏风险;文件已有英文风格或引用外部术语时可保持一致。
- 遵守 DRY 和单一职责;第三次出现的业务规则、字段转换、权限判断、Repository 选择、事务传播、金额计算等逻辑,应优先抽成合适 helper 或 service 方法。
## 后端规则
- 后端主包是 `packages/ui/certd-server`,使用 Node.js、ESM、TypeScript、MidwayJS 3、Koa、TypeORM 和 SQL 迁移。
- 做后端任务时,先定位 `packages/ui/certd-server/src/modules` 下的模块,以及相关 entity/service/controller。 - 做后端任务时,先定位 `packages/ui/certd-server/src/modules` 下的模块,以及相关 entity/service/controller。
- 表结构变更必须添加或更新 `packages/ui/certd-server/db/migration/*.sql`;不要依赖 TypeORM 自动同步。
- 文件上传接口 `/basic/file/upload` 返回临时 key;业务保存前必须调用 `FileService.saveFile(userId, key, "public" | "private")` 转成永久 key,不能直接保存 `tmpfile_key_...`
- 方法参数超过 3 个时,优先改为对象参数。
- 事务链路方法统一用 `method(ctx, req)``ctx` 放第一位并承载 `manager?: EntityManager`,业务参数放 `req` 对象。
- 只有需要事务传播时才定义 `ctx`;普通查询、纯函数和简单私有方法继续使用明确参数。
- 需要按事务上下文取 Repository 时,用 `BaseService.getRepo(ctx, EntityClass)`
- 需要“有事务则复用、无事务则开启”时,用 `BaseService.transactionWithCtx(ctx, callback)`
- 拼接可选 `projectId` 查询条件时,用 `BaseService.buildUserProjectQuery(userId, projectId)`;不要直接写 `{ userId, projectId }`
- `ctx` 类型复用 `BaseService` 导出的 `ServiceContext`
- 新增 service 方法避免与 `BaseService` 方法签名冲突,例如不要用 `delete(id)` 覆盖 `delete(ids, where?)`;改用 `deleteById` 等具体名称。
### 后端地图
- `packages/ui/certd-server/bootstrap.js`Midway 启动入口,使用 `@midwayjs/bootstrap`
- `packages/ui/certd-server/src/configuration.ts`:Midway 主配置,注册组件和全局中间件。
- `packages/ui/certd-server/src/config/config.default.ts`:端口、HTTPS、静态文件、cron、TypeORM、Flyway、上传、JWT、Swagger 默认配置。
- `packages/ui/certd-server/src/config/loader.ts`:读取 `.env``.env.<env>.yaml`,支持 `certd_` 前缀环境变量覆盖嵌套配置。
- `packages/ui/certd-server/src/modules`:业务模块根目录,常见模块包括:
- `basic`
- `cert`
- `cname`
- `cron`
- `login`
- `monitor`
- `open`
- `pipeline`
- `plugin`
- `suite`
- `sys`
- `packages/ui/certd-server/src/controller`API 入口,按 `basic``user``sys``openapi` 分组。
- `packages/ui/certd-server/db/migration`SQL 迁移目录;TypeORM `synchronize: false`,表结构变更必须配套迁移 SQL。
- 后端测试使用 Mocha + Node `assert/strict`;纯单测放在 `src/**/*.test.ts`,可参考 `src/utils/random.test.ts``src/controller/basic/app-controller.test.ts``src/modules/pipeline/service/pipeline-service.test.ts`
## 前端规则
- 前端主包是 `packages/ui/certd-client`,使用 Vue 3、Vite、TypeScript、Ant Design Vue、Fast Crud、Pinia、vue-router、vue-i18n。
- 做前端任务时,先定位 `packages/ui/certd-client/src/views/certd` 下的页面,再找对应 `src/api` - 做前端任务时,先定位 `packages/ui/certd-client/src/views/certd` 下的页面,再找对应 `src/api`
- 做服务商、DNS、部署、通知相关任务时,先看 `packages/ui/certd-server/src/plugins`,再看 `packages/plugins/plugin-lib` 里的共享辅助能力 - 不要运行前端 `pnpm tsc` / `vue-tsc`;当前 `vue-tsc@1.8.27` 会抛无效内部错误。前端 `test:unit` 只是占位脚本,也不要跑
- 优先沿用现有模块、插件、服务模式,再考虑新增抽象;避免为了形式上的“复用”制造过度设计 - 前端 TS/Vue/locale 改动后,只对本次改动文件运行现有 Prettier / ESLint`packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>``packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`
- 实现新功能或修复行为缺陷前,优先补对应单元测试并确认红灯,再实现代码并跑聚焦验证。确实不适合先写测试时,在回复中说明原因和替代验证方式 - 列表管理、后台管理、记录查询、CRUD 表格页面优先使用 Fast Crud;开发或重构前读 `.trae/skills/fast-crud-page-dev/SKILL.md`
- 后补单元测试时,先按正确行为写预期;如果红灯需要修改既有实现,先向用户确认这是 bug 还是既有需求,避免未经确认改变行为 - 只有轻量只读展示、强交互自定义界面或既有页面模式明显不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明
- 内嵌 Fast Crud 时,外层必须有稳定高度或完整 `flex: 1; min-height: 0` 链路。
- 后台管理列表展示或筛选用户字段时,优先参考 `packages/ui/certd-client/src/views/sys/suite/user-suite/crud.tsx``userId` 字段模式,用 `table-select` + `/sys/authority/user/getSimpleUserByIds` 字典回显和搜索。
- 对话框里只做确认可用 `Modal.confirm`;有字段输入、表单校验或提交字段时,必须用 `useFormDialog` / `openFormDialog`
### 前端地图
- `packages/ui/certd-client/vite.config.ts`Vite 配置。
- dev 端口:`3008`
- 代理路径:`/api``/certd/api`
- 代理目标:`127.0.0.1:7001`
- `packages/ui/certd-client/src/main.ts`:Vue 启动入口,注册 AntDV、Vben、router、全局组件、插件和偏好设置。
- `packages/ui/certd-client/src/App.vue`:根组件,包含 `AConfigProvider``FsFormProvider``router-view`
- `packages/ui/certd-client/src/router/index.ts``src/router/resolve.ts`:路由入口,使用 `createWebHashHistory`
- `packages/ui/certd-client/src/router/source/modules/certd.ts`Certd 主业务路由。
- `packages/ui/certd-client/src/store`Pinia store,主要包括:
- `user`
- `project`
- `settings`
- `plugin`
- `packages/ui/certd-client/src/api/service.ts`Axios 封装。
- `packages/ui/certd-client/src/api/tools.ts`:错误与响应工具。
- `packages/ui/certd-client/src/views/certd`:核心业务视图,常见目录包括:
- `pipeline`
- `cert`
- `monitor`
- `access`
- `notification`
- `open`
- `project`
- `suite`
- `wallet`
- `packages/ui/certd-client/src/locales`:国际化入口与语言包。
- Fast Crud 页面常见拆分是 `api.ts``crud.tsx``index.vue`;可参考 `src/views/certd/access``src/views/sys/suite/user-suite/crud.tsx``src/views/certd/wallet/index.vue`
## 流水线与插件规则
- 插件是核心能力。新增服务商、DNS 验证、证书部署、通知方式,通常放到插件包或 `packages/ui/certd-server/src/plugins/<plugin-name>/`
- 做服务商、DNS、部署、通知相关任务时,先看 `packages/ui/certd-server/src/plugins`,再看 `packages/plugins/plugin-lib`
- 插件依赖的第三方 SDK 可能通过 runtime-deps 动态安装到后端运行目录 `./data/.runtime-deps`。分析阿里云、腾讯云等 SDK 行为时,需要进入该目录阅读实际安装版本代码。
- 修改证书申请、验证、部署或通知行为时,先判断归属:ACME client、pipeline 核心、后端 module/service/entity/controller、具体插件、前端 view/form/schema。
- 单个服务商或部署目标的问题,不要轻易修改共享 pipeline/core;只有可复用公共语义或跨插件一致行为才上移到 `packages/core/pipeline``packages/plugins/plugin-lib`
- ACME / EAB:公共 EAB 可能只能创建一次账号;跨用户复用公共 EAB 时,应保存并复用同一个 ACME account private key。
- `newAccount({ onlyReturnExisting: true })` 可用同一个 account private key 取回已创建账号 URL,且不会再次消费 EAB。
- 修改 EAB `kid` 后,应重新生成绑定该 `kid` 的 account private key;否则应阻止继续申请并提示刷新账号私钥。
- 插件开发前先读对应技能:`.trae/skills/dns-provider-dev/SKILL.md``.trae/skills/task-plugin-dev/SKILL.md``.trae/skills/access-plugin-dev/SKILL.md``.trae/skills/plugin-converter/SKILL.md`
- `.codex/skills` 是指向 `.trae/skills` 的目录链接;更新技能只维护 `.trae/skills`,不要复制第二份。
### 流水线与插件地图
- `packages/core/pipeline/src/index.ts`:核心导出入口,导出 `core``dt``access``registry``plugin``context``decorator``service``notification`
- `packages/core/pipeline/src/plugin`:任务插件抽象,主要包括:
- `AbstractTaskPlugin`
- `IsTaskPlugin`
- `TaskInput`
- `pluginRegistry`
- `packages/core/pipeline/src/access`:授权插件抽象,主要包括:
- `BaseAccess`
- `IsAccess`
- `AccessInput`
- `accessRegistry`
- `packages/core/pipeline/src/dt/pipeline.ts``Pipeline``Stage``Task``RunStrategy` 等流水线数据结构。
- `packages/core/pipeline/src/core`:执行器、上下文、运行历史、文件存储等。
- `packages/core/pipeline/src/service`:CNAME、事件、配置、邮件、URL 等 pipeline service 接口。
- `packages/ui/certd-server/src/plugins`:后端内置服务商、DNS、部署、通知等插件。
- `packages/ui/certd-server/src/plugins/plugin-cert`:证书申请核心插件。
- `packages/ui/certd-server/src/plugins/plugin-lib`:后端插件 helper/access。
- `packages/plugins/plugin-lib/src/cert``CertReader``CertConverter`、DNS Provider 公共能力。
- `packages/plugins/plugin-lib/src/cert/dns-provider``AbstractDnsProvider``dnsProviderRegistry``DomainParser`
- ACME 协议、EAB、账号、订单、挑战流程:优先看 `packages/core/acme-client``packages/ui/certd-server/src/plugins/plugin-cert/plugin/cert-plugin/acme.ts`
- 流水线执行、任务生命周期、输入输出、注册机制:看 `packages/core/pipeline`
- 单个云厂商 DNS 验证、证书部署、API 调用失败:改对应 `packages/ui/certd-server/src/plugins/plugin-xxx`
- 通用证书读取、DNS Provider 公共能力、格式转换:改 `packages/plugins/plugin-lib`
- 后端业务数据、接口、实体、权限、迁移:改 `packages/ui/certd-server/src/modules``src/controller`
- 表单、列表、插件配置 UI:改 `packages/ui/certd-client/src/views/certd` 及对应 `src/api`
## 测试与验证
- 实现新功能或修复行为缺陷前,优先补单元测试并先确认红灯,再实现并跑聚焦验证。
- 确实不适合先写测试时,在回复中说明原因和替代验证方式。
- 后补单元测试时,按正确行为写预期;若红灯需要修改既有实现,先向用户确认这是 bug 还是既有需求,避免未经确认改变行为。
- 后端纯单测放在 `src/**/*.test.ts`,尽量与被测文件相邻;`test:unit` 只跑这些文件,构建/打包应排除 `*.test.ts`
- 单测需要 mock ESM 静态 import 时,优先使用 `esmock`,不要为了测试改业务代码结构。
- 各包 `test:unit` 脚本应显式设置 `NODE_ENV=unittest`
- 单包单测优先用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit`
- 优先对改动包运行聚焦测试或格式化/ESLint;只有跨包影响明显时再考虑更大范围构建。 - 优先对改动包运行聚焦测试或格式化/ESLint;只有跨包影响明显时再考虑更大范围构建。
## 架构边界
插件是核心能力,不是边缘功能。新增服务商、DNS 验证、证书部署、通知方式等能力,通常应该放在插件包里,或放在 `packages/ui/certd-server/src/plugins/<plugin-name>/` 下。
修改证书申请、验证、部署或通知行为时,先判断改动属于 ACME client、pipeline 核心抽象、后端 module/service/entity/controller、具体插件实现,还是前端 view/form/schema。
如果只是某个服务商或部署目标的问题,不要轻易修改共享 pipeline/core 行为,除非确实是可复用的公共能力。
+47
View File
@@ -3,6 +3,53 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
### Performance Improvements
* **plugin:** 增加 Dynadot DNS and access 插件 ([a3a215b](https://github.com/certd/certd/commit/a3a215b7ae2b90efcde91270ce4165bbfe77dc64))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
### Bug Fixes
* 修复litessl无法申请证书,报authorization must be pending 错误的问题 ([d6cd9d1](https://github.com/certd/certd/commit/d6cd9d136d2812b2335917305f36d6d9414507ad))
### Performance Improvements
* 首页夜间模式主图切换为黑色背景 ([15484bc](https://github.com/certd/certd/commit/15484bc119fef7a0ca7f3fdab01d665fde47e688))
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
### Bug Fixes
* **cert-plugin:** 修复DNS提供商授权无法回显的bug ([016ae86](https://github.com/certd/certd/commit/016ae865b1d914fe5792e77a08e3ab5358df5f89))
* Parse PEM chain and import certificate chain ([#747](https://github.com/certd/certd/issues/747)) ([454912d](https://github.com/certd/certd/commit/454912d31407d350cbd170953ccbd0564e74fd6c))
### Performance Improvements
* 添加AWS Rate Limit应对措施 ([#748](https://github.com/certd/certd/issues/748)) ([56b8c68](https://github.com/certd/certd/commit/56b8c689ec2b5cff49010a8c765483dd36803e9d))
* 新增站点证书监控从DNS解析记录批量导入功能 ([f9541fa](https://github.com/certd/certd/commit/f9541fab701e01ba57af061da322204c894adfb8))
* 优化 HiPM DNSMgr 插件,添加域名查询双层策略 ([#744](https://github.com/certd/certd/issues/744)) @WUHINS ([0f3f851](https://github.com/certd/certd/commit/0f3f8519e04d95cb848e28b98a3d4fcbed481fce))
### Reverts
* Revert "perf: 添加AWS Rate Limit应对措施 (#748)" (#749) ([5e8bdac](https://github.com/certd/certd/commit/5e8bdac00850bed4f5f2a272bee42c490730ec21)), closes [#748](https://github.com/certd/certd/issues/748) [#749](https://github.com/certd/certd/issues/749)
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
### Performance Improvements
* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7))
* 优化列表页面请求两次的问题 ([5546af5](https://github.com/certd/certd/commit/5546af518e92c765513787ccaf8e856be789bcf9))
* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0))
* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc))
* **volcengine-vke:** 火山VKE集群证书支持两种类型的证书保密字典 ([77b8024](https://github.com/certd/certd/commit/77b802445322d576d54d194f7c505da49e0e824c))
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
### Bug Fixes ### Bug Fixes
+4 -1
View File
@@ -179,7 +179,10 @@ https://certd.handfree.work/
2. 您的需求我们将优先实现,并且可能将作为专业版功能提供 2. 您的需求我们将优先实现,并且可能将作为专业版功能提供
3. 获得专业版功能 3. 获得专业版功能
[50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product)
> [50元专业版优惠券限时领取](https://app.handfree.work/subject/#/app/certd/product) https://app.handfree.work/subject/#/app/certd/product
> app.handfree.work是Certd官方激活码购买平台
专业版、商业版特权对比 专业版、商业版特权对比
+72
View File
@@ -3,6 +3,78 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
### Performance Improvements
* **plugin:** 增加 Dynadot DNS and access 插件 ([a3a215b](https://github.com/certd/certd/commit/a3a215b7ae2b90efcde91270ce4165bbfe77dc64))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
### Bug Fixes
* 修复litessl无法申请证书,报authorization must be pending 错误的问题 ([d6cd9d1](https://github.com/certd/certd/commit/d6cd9d136d2812b2335917305f36d6d9414507ad))
### Performance Improvements
* 首页夜间模式主图切换为黑色背景 ([15484bc](https://github.com/certd/certd/commit/15484bc119fef7a0ca7f3fdab01d665fde47e688))
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
### Bug Fixes
* **cert-plugin:** 修复DNS提供商授权无法回显的bug ([016ae86](https://github.com/certd/certd/commit/016ae865b1d914fe5792e77a08e3ab5358df5f89))
* Parse PEM chain and import certificate chain ([#747](https://github.com/certd/certd/issues/747)) ([454912d](https://github.com/certd/certd/commit/454912d31407d350cbd170953ccbd0564e74fd6c))
### Performance Improvements
* 添加AWS Rate Limit应对措施 ([#748](https://github.com/certd/certd/issues/748)) ([56b8c68](https://github.com/certd/certd/commit/56b8c689ec2b5cff49010a8c765483dd36803e9d))
* 新增站点证书监控从DNS解析记录批量导入功能 ([f9541fa](https://github.com/certd/certd/commit/f9541fab701e01ba57af061da322204c894adfb8))
* 优化 HiPM DNSMgr 插件,添加域名查询双层策略 ([#744](https://github.com/certd/certd/issues/744)) @WUHINS ([0f3f851](https://github.com/certd/certd/commit/0f3f8519e04d95cb848e28b98a3d4fcbed481fce))
### Reverts
* Revert "perf: 添加AWS Rate Limit应对措施 (#748)" (#749) ([5e8bdac](https://github.com/certd/certd/commit/5e8bdac00850bed4f5f2a272bee42c490730ec21)), closes [#748](https://github.com/certd/certd/issues/748) [#749](https://github.com/certd/certd/issues/749)
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
### Performance Improvements
* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7))
* 优化列表页面请求两次的问题 ([5546af5](https://github.com/certd/certd/commit/5546af518e92c765513787ccaf8e856be789bcf9))
* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0))
* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc))
* **volcengine-vke:** 火山VKE集群证书支持两种类型的证书保密字典 ([77b8024](https://github.com/certd/certd/commit/77b802445322d576d54d194f7c505da49e0e824c))
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
### Bug Fixes
* 修复阿里云证书订单orderid 选择出错的问题 ([41254d1](https://github.com/certd/certd/commit/41254d10b748a2d3e6ba43c7e11411650c748d1b))
* **monitor:** 修复开放接口自动创建证书流水线重复触发和等待时间不足的问题 ([91d5c90](https://github.com/certd/certd/commit/91d5c90eb0eaf65c81dddbd2d4d4b404cb8b4d07))
* **pipeline:** 修复批量随机修改定时没有生效的bug ([2e19dda](https://github.com/certd/certd/commit/2e19dda72e70b525a7c269e18e963a5ee602f59f))
### Features
* 商业版支持邀请返佣功能 ([f9a310b](https://github.com/certd/certd/commit/f9a310b6c3bbf30f221482a0c59e9c30080bdfc8))
* 商业版支持邀请推广功能 ([f1d2a10](https://github.com/certd/certd/commit/f1d2a1033a0f8d3dbd91fc9793e07bd0b858b539))
* 新增管理员针对用户流水线和证书监控管理功能 ([0211552](https://github.com/certd/certd/commit/021155278e7375f8487b0531ed1b5ad52512f007))
* 新增套餐激活码功能,通过CDK兑换套餐 ([81d6289](https://github.com/certd/certd/commit/81d6289a8631b073b49f24dee4b14bb1c8f31071))
* 新增推广等级激励功能 ([5096df5](https://github.com/certd/certd/commit/5096df5cc0d8f0ad8aa327b8e2a900ba23714bd8))
* 新增证书申请参数模版管理,开放接口支持使用证书参数模版和指定证书申请参数 ([f8b71a0](https://github.com/certd/certd/commit/f8b71a0e612fad527cf49136335e0b46f0f379cd))
* 支持dns-persist-01持久化验证方式申请证书,优化Acme账号的存储方式 ([67b05e2](https://github.com/certd/certd/commit/67b05e2d75e96b9f855e1ca0b3d0d8d03b92d8e6))
### Performance Improvements
* 插件全局配置支持下拉选项自定义映射功能 ([c637985](https://github.com/certd/certd/commit/c637985575b09196b04cce37ac14fbe68c029bde))
* 商业版提现增加收款二维码上传 ([83a5a21](https://github.com/certd/certd/commit/83a5a21f956e50942541f1532f3a8dcaa5821d34))
* **aliyun-apig:** 优化阿里云API网关部署插件的查询及翻页 ([3e4b7f3](https://github.com/certd/certd/commit/3e4b7f30ac6f3c976c8274bdf256c69b8a2c46db))
* **trade:** 优化商品购买页面的规格展示和折扣计算,支持订单取消 ([6624769](https://github.com/certd/certd/commit/66247690326ce2789900fc9110c08b3502cea655))
## [1.40.5](https://github.com/certd/certd/compare/v1.40.4...v1.40.5) (2026-05-26) ## [1.40.5](https://github.com/certd/certd/compare/v1.40.4...v1.40.5) (2026-05-26)
### Bug Fixes ### Bug Fixes
+1 -1
View File
@@ -6,7 +6,7 @@ Certd 是一款开源、免费、全自动申请和部署更新SSL证书的工
关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具 关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签、证书管理工具
| 官方开源地址: | | | &nbsp;|官方开源地址: |
| ---- | ---- | | ---- | ---- |
| [Github](https://github.com/certd/certd)| ![](https://img.shields.io/github/stars/certd/certd?logo=github) | | [Github](https://github.com/certd/certd)| ![](https://img.shields.io/github/stars/certd/certd?logo=github) |
| [Gitee](https://gitee.com/certd/certd) | ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark) | | [Gitee](https://gitee.com/certd/certd) | ![](https://gitee.com/certd/certd/badge/star.svg?theme=dark) |
+48 -47
View File
@@ -33,53 +33,54 @@
| 29.| **彩虹DNS** | 彩虹DNS管理系统授权 | | 29.| **彩虹DNS** | 彩虹DNS管理系统授权 |
| 30.| **多吉云** | | | 30.| **多吉云** | |
| 31.| **Dokploy授权** | | | 31.| **Dokploy授权** | |
| 32.| **farcdn授权** | | | 32.| **Dynadot授权** | |
| 33.| **FlexCDN授权** | | | 33.| **farcdn授权** | |
| 34.| **Gcore** | Gcore | | 34.| **FlexCDN授权** | |
| 35.| **Github授权** | | | 35.| **Gcore** | Gcore |
| 36.| **godaddy授权** | | | 36.| **Github授权** | |
| 37.| **HiPM DNSMgr** | HiPM DNSMgr API Token 授权 | | 37.| **godaddy授权** | |
| 38.| **金山云授权** | | | 38.| **HiPM DNSMgr** | HiPM DNSMgr API Token 授权 |
| 39.| **FTP授权** | | | 39.| **金山云授权** | |
| 40.| **七牛OSS授权** | | | 40.| **FTP授权** | |
| 41.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 | | 41.| **七牛OSS授权** | |
| 42.| **s3/minio授权** | S3/minio oss授权 | | 42.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
| 43.| **namesilo授权** | | | 43.| **s3/minio授权** | S3/minio oss授权 |
| 44.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 | | 44.| **namesilo授权** | |
| 45.| **Nginx Proxy Manager 授权** | 用于登录 Nginx Proxy Manager,并为代理主机证书部署提供授权。 | | 45.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
| 46.| **1panel授权** | 账号和密码 | | 46.| **Nginx Proxy Manager 授权** | 用于登录 Nginx Proxy Manager,并为代理主机证书部署提供授权。 |
| 47.| **支付宝** | | | 47.| **1panel授权** | 账号和密码 |
| 48.| **白山云授权** | | | 48.| **支付宝** | |
| 49.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 | | 49.| **白山云授权** | |
| 50.| **cdnfly授权** | | | 50.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
| 51.| **k8s授权** | | | 51.| **cdnfly授权** | |
| 52.| **括彩云cdn授权** | 括彩云CDN,每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) | | 52.| **k8s授权** | |
| 53.| **LeCDN授权** | | | 53.| **括彩云cdn授权** | 括彩云CDN,每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 54.| **lucky** | | | 54.| **LeCDN授权** | |
| 55.| **猫云授权** | | | 55.| **lucky** | |
| 56.| **plesk授权** | | | 56.| **猫云授权** | |
| 57.| **长亭雷池授权** | | | 57.| **plesk授权** | |
| 58.| **群晖登录授权** | | | 58.| **长亭雷池授权** | |
| 59.| **uniCloud** | unicloud授权 | | 59.| **群晖登录授权** | |
| 60.| **微信支付** | | | 60.| **uniCloud** | unicloud授权 |
| 61.| **易盾rcdn授权** | 易盾CDN,每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) | | 61.| **微信支付** | |
| 62.| **易发云短信** | sms.yfyidc.cn/ | | 62.| **易盾rcdn授权** | 易盾CDN,每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 63.| **易盾DCDN授权** | https://user.yiduncdn.com | | 63.| **易发云短信** | sms.yfyidc.cn/ |
| 64.| **易支付** | | | 64.| **易盾DCDN授权** | https://user.yiduncdn.com |
| 65.| **proxmox** | | | 65.| **易支付** | |
| 66.| **Spaceship.com 授权** | Spaceship.com API 授权插件 | | 66.| **proxmox** | |
| 67.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器授权 | | 67.| **Spaceship.com 授权** | Spaceship.com API 授权插件 |
| 68.| **UCloud授权** | 优刻得授权 | | 68.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器授权 |
| 69.| **又拍云** | | | 69.| **UCloud授权** | 优刻得授权 |
| 70.| **网宿授权** | | | 70.| **又拍云** | |
| 71.| **西部数码授权** | | | 71.| **网宿授权** | |
| 72.| **我爱云授权** | 我爱云CDN | | 72.| **西部数码授权** | |
| 73.| **新网授权(代理方式)** | | | 73.| **我爱云授权** | 我爱云CDN |
| 74.| **新网授权** | | | 74.| **新网授权(代理方式)** | |
| 75.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 | | 75.| **新网授权** | |
| 76.| **Zenlayer授权** | Zenlayer授权 | | 76.| **新网互联授权** | 仅支持代理账号,ip需要加入白名单 |
| 77.| **GoEdge授权** | | | 77.| **Zenlayer授权** | Zenlayer授权 |
| 78.| **雨云授权** | https://app.rainyun.com/ | | 78.| **GoEdge授权** | |
| 79.| **雨云授权** | https://app.rainyun.com/ |
<style module> <style module>
table th:first-of-type { table th:first-of-type {
+16 -15
View File
@@ -13,21 +13,22 @@
| 9.| **BIND9 DNS** | 通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录 | | 9.| **BIND9 DNS** | 通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录 |
| 10.| **cloudflare** | cloudflare dns provider | | 10.| **cloudflare** | cloudflare dns provider |
| 11.| **dns.la** | dns.la | | 11.| **dns.la** | dns.la |
| 12.| **godaddy** | GoDaddy | | 12.| **Dynadot** | Dynadot DNS提供商 |
| 13.| **HiPM DNSMgr** | HiPM DNSMgr DNS 解析提供商 | | 13.| **godaddy** | GoDaddy |
| 14.| **华为云** | 华为云DNS解析提供商 | | 14.| **HiPM DNSMgr** | HiPM DNSMgr DNS 解析提供商 |
| 15.| **namesilo** | namesilo dns provider | | 15.| **华为云** | 华为云DNS解析提供商 |
| 16.| **雨云** | 雨云DNS解析提供商 | | 16.| **namesilo** | namesilo dns provider |
| 17.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器 | | 17.| **雨云** | 雨云DNS解析提供商 |
| 18.| **腾讯云** | 腾讯云域名DNS解析提供者 | | 18.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器 |
| 19.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 | | 19.| **腾讯云** | 腾讯云域名DNS解析提供者 |
| 20.| **西部数码** | west dns provider | | 20.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 |
| 21.| **Google Cloud DNS** | Google Cloud DNS提供商 | | 21.| **西部数码** | west dns provider |
| 22.| **Dns提供商Demo** | dns provider示例 | | 22.| **Google Cloud DNS** | Google Cloud DNS提供商 |
| 23.| **彩虹DNS** | 彩虹DNS管理系统 | | 23.| **Dns提供商Demo** | dns provider示例 |
| 24.| **Spaceship** | Spaceship 域名解析 | | 24.| **彩虹DNS** | 彩虹DNS管理系统 |
| 25.| **51dns** | 51DNS | | 25.| **Spaceship** | Spaceship 域名解析 |
| 26.| **新网互联** | 新网互联 | | 26.| **51dns** | 51DNS |
| 27.| **新网互联** | 新网互联 |
<style module> <style module>
table th:first-of-type { table th:first-of-type {
View File
+1 -1
View File
@@ -9,5 +9,5 @@
} }
}, },
"npmClient": "pnpm", "npmClient": "pnpm",
"version": "1.41.0" "version": "1.41.4"
} }
+1 -2
View File
@@ -15,8 +15,7 @@
"vitepress-plugin-lightbox": "^1.0.2" "vitepress-plugin-lightbox": "^1.0.2"
}, },
"scripts": { "scripts": {
"start": "lerna bootstrap --hoist", "start": "cd ./packages/ui/certd-server && pnpm start",
"start:server": "cd ./packages/ui/certd-server && pnpm start",
"devb": "lerna run dev-build", "devb": "lerna run dev-build",
"i-all": "lerna link && lerna exec npm install ", "i-all": "lerna link && lerna exec npm install ",
"publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ", "publish": "pnpm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits && pnpm run afterpublishOnly ",
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/publishlab/node-acme-client/compare/v1.41.3...v1.41.4) (2026-06-14)
**Note:** Version bump only for package @certd/acme-client
## [1.41.3](https://github.com/publishlab/node-acme-client/compare/v1.41.2...v1.41.3) (2026-06-11)
### Bug Fixes
* 修复litessl无法申请证书,报authorization must be pending 错误的问题 ([d6cd9d1](https://github.com/publishlab/node-acme-client/commit/d6cd9d136d2812b2335917305f36d6d9414507ad))
## [1.41.2](https://github.com/publishlab/node-acme-client/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/acme-client
## [1.41.1](https://github.com/publishlab/node-acme-client/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/acme-client
# [1.41.0](https://github.com/publishlab/node-acme-client/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/publishlab/node-acme-client/compare/v1.40.5...v1.41.0) (2026-06-04)
### Bug Fixes ### Bug Fixes
+9 -8
View File
@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client", "description": "Simple and unopinionated ACME client",
"private": false, "private": false,
"author": "nmorsman", "author": "nmorsman",
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"module": "./dist/index.js", "module": "./dist/index.js",
"main": "./dist/index.js", "main": "./dist/index.js",
@@ -18,7 +18,7 @@
"types" "types"
], ],
"dependencies": { "dependencies": {
"@certd/basic": "^1.41.0", "@certd/basic": "^1.41.4",
"@peculiar/x509": "^1.11.0", "@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5", "asn1js": "^3.0.5",
"axios": "^1.9.0", "axios": "^1.9.0",
@@ -33,18 +33,18 @@
"@types/node": "^20.14.10", "@types/node": "^20.14.10",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "^4.4.1", "chai": "^5.1.0",
"chai-as-promised": "^7.1.2", "chai-as-promised": "^7.1.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"jsdoc-to-markdown": "^8.0.1", "jsdoc-to-markdown": "^8.0.1",
"mocha": "^10.6.0", "mocha": "^10.6.0",
"nock": "^13.5.4", "nock": "^13.5.4",
"prettier": "^2.8.8", "prettier": "3.3.3",
"tsd": "^0.31.1", "tsd": "^0.31.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
@@ -59,7 +59,8 @@
"before-test:unit": "node -e \"const fs=require('fs');fs.rmSync('dist-test',{recursive:true,force:true});fs.rmSync('tsconfig.test.tsbuildinfo',{force:true});\"", "before-test:unit": "node -e \"const fs=require('fs');fs.rmSync('dist-test',{recursive:true,force:true});fs.rmSync('tsconfig.test.tsbuildinfo',{force:true});\"",
"test:unit": "cross-env NODE_ENV=unittest npm run before-test:unit && cross-env NODE_ENV=unittest tsc -p tsconfig.test.json --skipLibCheck && cross-env NODE_ENV=unittest mocha -t 60000 \"dist-test/**/*.test.js\"", "test:unit": "cross-env NODE_ENV=unittest npm run before-test:unit && cross-env NODE_ENV=unittest tsc -p tsconfig.test.json --skipLibCheck && cross-env NODE_ENV=unittest mocha -t 60000 \"dist-test/**/*.test.js\"",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -76,5 +77,5 @@
"bugs": { "bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues" "url": "https://github.com/publishlab/node-acme-client/issues"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
@@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import auto from "./auto.js";
import { createCsr, createPrivateRsaKey } from "./crypto/index.js";
declare const describe: any;
declare const it: any;
describe("auto challenge status polling", () => {
it("polls the authorization URL after completing a challenge", async () => {
const [, csr] = await createCsr({ commonName: "example.com" }, await createPrivateRsaKey());
const challenge = {
type: "dns-01",
url: "https://ca.example/chall/1",
token: "token",
};
const authz = {
status: "pending",
identifier: { type: "dns", value: "example.com" },
url: "https://ca.example/authz/1",
challenges: [challenge],
};
const order = {
status: "pending",
url: "https://ca.example/order/1",
finalize: "https://ca.example/order/1/finalize",
authorizations: [authz.url],
};
const polledUrls: string[] = [];
const originalSetTimeout = globalThis.setTimeout;
(globalThis as any).setTimeout = (fn: (...args: any[]) => void) => originalSetTimeout(fn, 0);
try {
const certificate = await auto(
{
logger: { info: () => {} },
sslProvider: "litessl",
getAccountUrl: () => "https://ca.example/acct/1",
createOrder: async () => order,
getAuthorizations: async () => [authz],
getChallengeKeyAuthorization: async () => "key-authorization",
verifyChallenge: async () => {},
completeChallenge: async () => ({ ...challenge, status: "processing" }),
waitForValidStatus: async (item: { url: string }) => {
polledUrls.push(item.url);
return { ...item, status: "valid" };
},
finalizeOrder: async () => ({ ...order, status: "valid", certificate: "https://ca.example/cert/1" }),
getCertificate: async () => "CERTIFICATE",
} as any,
{
csr,
termsOfServiceAgreed: true,
waitDnsDiffuseTime: 0,
challengeCreateFn: async (_authz: any, keyAuthorizationGetter: (challenge: any) => Promise<string>) => ({
challenge,
keyAuthorization: await keyAuthorizationGetter(challenge),
}),
challengeRemoveFn: async () => {},
}
);
assert.equal(certificate, "CERTIFICATE");
assert.deepEqual(polledUrls, [authz.url]);
} finally {
(globalThis as any).setTimeout = originalSetTimeout;
}
});
});
+1 -1
View File
@@ -172,7 +172,7 @@ export default async (client, userOpts) => {
} }
challengeCompleted = true; challengeCompleted = true;
log(`[auto] [${d}] 等待返回valid状态`); log(`[auto] [${d}] 等待返回valid状态`);
await client.waitForValidStatus(challenge,d); await client.waitForValidStatus(authz,d);
}); });
+1 -1
View File
@@ -263,4 +263,4 @@ export function createChallengeFn(opts = {}) {
// createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handsfree.work") // createChallengeFn({logger:{info:console.log}}).walkDnsChallengeRecord("handfree.work")
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/basic
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
### Performance Improvements
* 新增站点证书监控从DNS解析记录批量导入功能 ([f9541fa](https://github.com/certd/certd/commit/f9541fab701e01ba57af061da322204c894adfb8))
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
### Performance Improvements
* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc))
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/basic **Note:** Version bump only for package @certd/basic
+1 -1
View File
@@ -1 +1 @@
12:22 21:25
+15 -13
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/basic", "name": "@certd/basic",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -15,7 +15,9 @@
"test": "mocha --loader=ts-node/esm", "test": "mocha --loader=ts-node/esm",
"test:unit": "cross-env NODE_ENV=unittest mocha --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"", "test:unit": "cross-env NODE_ENV=unittest mocha --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"async-lock": "^1.4.1", "async-lock": "^1.4.1",
@@ -26,31 +28,31 @@
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"lru-cache": "^10.0.0", "lru-cache": "^11.0.1",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nodemailer": "^6.9.3" "nodemailer": "^6.9.16"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.10", "@types/chai": "^4.3.12",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.6",
"@types/node-forge": "^1.3.2", "@types/node-forge": "^1.3.2",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "4.3.10", "chai": "^5.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.41.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"mocha": "^10.2.0", "mocha": "^10.6.0",
"prettier": "^2.8.8", "prettier": "3.3.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
@@ -1,8 +1,9 @@
import { expect } from "chai"; import { expect } from "chai";
import { createAxiosService, HttpClient, setGlobalHeaders } from "./util.request.js"; import { createAgent, createAxiosService, getGlobalAgents, HttpClient, isNoProxyMatched, setGlobalHeaders, setGlobalProxy } from "./util.request.js";
import { ILogger } from "./util.log.js"; import { ILogger } from "./util.log.js";
const testLogger = { const testLogger = {
debug() {},
info() {}, info() {},
error() {}, error() {},
} as unknown as ILogger; } as unknown as ILogger;
@@ -10,6 +11,9 @@ const testLogger = {
describe("util.request", () => { describe("util.request", () => {
afterEach(() => { afterEach(() => {
setGlobalHeaders({}); setGlobalHeaders({});
setGlobalProxy({});
delete process.env.NO_PROXY;
delete process.env.no_proxy;
}); });
it("should merge global headers without overriding request headers", async () => { it("should merge global headers without overriding request headers", async () => {
@@ -50,4 +54,122 @@ describe("util.request", () => {
request: "request", request: "request",
}); });
}); });
it("should set no_proxy environment variables", () => {
setGlobalProxy({
httpProxy: "http://127.0.0.1:1080",
httpsProxy: "http://127.0.0.1:1080",
noProxy: "localhost,*.internal.example.com",
});
expect(process.env.NO_PROXY).to.equal("localhost,*.internal.example.com");
expect(process.env.no_proxy).to.equal("localhost,*.internal.example.com");
});
it("should normalize multiline no_proxy environment variables", () => {
setGlobalProxy({
noProxy: "localhost\n127.0.0.1, 192.168.*\n*.internal.example.com",
});
expect(process.env.NO_PROXY).to.equal("localhost,127.0.0.1,192.168.*,*.internal.example.com");
expect(process.env.no_proxy).to.equal("localhost,127.0.0.1,192.168.*,*.internal.example.com");
});
it("should not change environment variables when creating agents", () => {
process.env.HTTP_PROXY = "http://old-http-proxy";
process.env.HTTPS_PROXY = "http://old-https-proxy";
process.env.NO_PROXY = "old.local";
createAgent({
httpProxy: "http://127.0.0.1:1080",
httpsProxy: "http://127.0.0.1:1081",
});
expect(process.env.HTTP_PROXY).to.equal("http://old-http-proxy");
expect(process.env.HTTPS_PROXY).to.equal("http://old-https-proxy");
expect(process.env.NO_PROXY).to.equal("old.local");
});
it("should bypass global proxy when request host matches no_proxy", async () => {
setGlobalProxy({
httpProxy: "http://127.0.0.1:1080",
httpsProxy: "http://127.0.0.1:1080",
noProxy: "localhost,.internal.example.com",
});
const globalAgents = getGlobalAgents();
const http = createAxiosService({ logger: testLogger }) as HttpClient;
const res = await http.request({
url: "https://api.internal.example.com",
method: "get",
logReq: false,
logRes: false,
adapter: async config => {
return {
config,
data: {
usesGlobalHttpAgent: config.httpAgent === globalAgents.httpAgent,
usesGlobalHttpsAgent: config.httpsAgent === globalAgents.httpsAgent,
},
headers: {},
status: 200,
statusText: "OK",
};
},
});
expect(res).to.deep.equal({
usesGlobalHttpAgent: false,
usesGlobalHttpsAgent: false,
});
});
it("should bypass custom request proxy when request host matches no_proxy", async () => {
setGlobalProxy({
noProxy: ".internal.example.com",
});
const http = createAxiosService({ logger: testLogger }) as HttpClient;
const res = await http.request({
url: "https://api.internal.example.com",
method: "get",
httpProxy: "http://127.0.0.1:1080",
logReq: false,
logRes: false,
adapter: async config => {
return {
config,
data: {
httpAgent: config.httpAgent?.constructor?.name,
httpsAgent: config.httpsAgent?.constructor?.name,
},
headers: {},
status: 200,
statusText: "OK",
};
},
});
expect(res).to.deep.equal({
httpAgent: "Agent",
httpsAgent: "Agent",
});
});
it("should match no_proxy rules", () => {
expect(isNoProxyMatched("*", { hostname: "api.example.com", port: "" })).to.equal(true);
expect(isNoProxyMatched("api.example.com", { hostname: "api.example.com", port: "" })).to.equal(true);
expect(isNoProxyMatched("example.com", { hostname: "api.example.com", port: "" })).to.equal(true);
expect(isNoProxyMatched(".example.com", { hostname: "api.example.com", port: "" })).to.equal(true);
expect(isNoProxyMatched("*.example.com", { hostname: "api.example.com", port: "" })).to.equal(true);
expect(isNoProxyMatched("127.0.0.1", { hostname: "127.0.0.1", port: "" })).to.equal(true);
expect(isNoProxyMatched("192.168.*", { hostname: "192.168.1.10", port: "" })).to.equal(true);
expect(isNoProxyMatched("192.168.*", { hostname: "192.169.1.10", port: "" })).to.equal(false);
expect(isNoProxyMatched("[::1]", { hostname: "::1", port: "" })).to.equal(true);
expect(isNoProxyMatched("[::1]:8443", { hostname: "::1", port: "8443" })).to.equal(true);
expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "8443" })).to.equal(true);
expect(isNoProxyMatched("api.example.com:8443", { hostname: "api.example.com", port: "443" })).to.equal(false);
expect(isNoProxyMatched("127.0.0.1", { hostname: "127.0.0.2", port: "" })).to.equal(false);
expect(isNoProxyMatched(".example.com", { hostname: "example.org", port: "" })).to.equal(false);
});
}); });
+174 -28
View File
@@ -82,11 +82,24 @@ export class HttpError extends Error {
export const HttpCommonError = HttpError; export const HttpCommonError = HttpError;
let defaultAgents = createAgent(); let defaultAgents = createAgent();
const directAgents = createAgent();
let defaultProxyOptions: GlobalProxyOptions = {};
let defaultHeaders: Record<string, string> = {}; let defaultHeaders: Record<string, string> = {};
export function setGlobalProxy(opts: { httpProxy?: string; httpsProxy?: string }) { export type GlobalProxyOptions = {
httpProxy?: string;
httpsProxy?: string;
noProxy?: string;
};
export function setGlobalProxy(opts: GlobalProxyOptions) {
logger.info("setGlobalProxy:", opts); logger.info("setGlobalProxy:", opts);
defaultAgents = createAgent(opts); defaultProxyOptions = { ...opts };
defaultAgents = createAgent({
httpProxy: opts.httpProxy,
httpsProxy: opts.httpsProxy,
});
setProxyEnvironment(opts);
} }
export function getGlobalAgents() { export function getGlobalAgents() {
@@ -137,21 +150,25 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
if (config.timeout == null) { if (config.timeout == null) {
config.timeout = 15000; config.timeout = 15000;
} }
let agents = defaultAgents; const bypassProxy = shouldBypassProxy(config, defaultProxyOptions.noProxy);
if (config.skipSslVerify || config.httpProxy) { const useCustomProxy = !!config.httpProxy && !bypassProxy;
let rejectUnauthorized = true; let agents = bypassProxy ? directAgents : defaultAgents;
if (bypassProxy) {
logger.info("命中no_proxy配置,跳过代理:", config.url);
}
if (config.skipSslVerify || useCustomProxy) {
const agentOptions: any = {};
if (config.skipSslVerify) { if (config.skipSslVerify) {
logger.info("忽略接口请求的SSL校验"); logger.info("忽略接口请求的SSL校验");
rejectUnauthorized = false; agentOptions.rejectUnauthorized = false;
} }
const proxy: any = {}; if (useCustomProxy) {
if (config.httpProxy) {
logger.info("使用自定义http代理:", config.httpProxy); logger.info("使用自定义http代理:", config.httpProxy);
proxy.httpProxy = config.httpProxy; agentOptions.httpProxy = config.httpProxy;
proxy.httpsProxy = config.httpProxy; agentOptions.httpsProxy = config.httpProxy;
} }
agents = createAgent({ rejectUnauthorized, ...proxy } as any); agents = createAgent(agentOptions);
} }
delete config.skipSslVerify; delete config.skipSslVerify;
@@ -354,7 +371,7 @@ export type CreateAgentOptions = {
httpsProxy?: string; httpsProxy?: string;
} & nodeHttp.AgentOptions; } & nodeHttp.AgentOptions;
export function createAgent(opts: CreateAgentOptions = {}) { export function createAgent(opts: CreateAgentOptions = {}) {
opts = merge( const { httpProxy, httpsProxy, ...agentOptions } = merge(
{ {
autoSelectFamily: true, autoSelectFamily: true,
autoSelectFamilyAttemptTimeout: 1000, autoSelectFamilyAttemptTimeout: 1000,
@@ -364,29 +381,19 @@ export function createAgent(opts: CreateAgentOptions = {}) {
); );
let httpAgent, httpsAgent; let httpAgent, httpsAgent;
const httpProxy = opts.httpProxy;
if (httpProxy) { if (httpProxy) {
process.env.HTTP_PROXY = httpProxy;
process.env.http_proxy = httpProxy;
logger.info("use httpProxy:", httpProxy); logger.info("use httpProxy:", httpProxy);
httpAgent = new HttpProxyAgent(httpProxy, opts as any); httpAgent = new HttpProxyAgent(httpProxy, agentOptions as any);
merge(httpAgent.options, opts); merge(httpAgent.options, agentOptions);
} else { } else {
process.env.HTTP_PROXY = ""; httpAgent = new nodeHttp.Agent(agentOptions);
process.env.http_proxy = "";
httpAgent = new nodeHttp.Agent(opts);
} }
const httpsProxy = opts.httpsProxy;
if (httpsProxy) { if (httpsProxy) {
process.env.HTTPS_PROXY = httpsProxy;
process.env.https_proxy = httpsProxy;
logger.info("use httpsProxy:", httpsProxy); logger.info("use httpsProxy:", httpsProxy);
httpsAgent = new HttpsProxyAgent(httpsProxy, opts as any); httpsAgent = new HttpsProxyAgent(httpsProxy, agentOptions as any);
merge(httpsAgent.options, opts); merge(httpsAgent.options, agentOptions);
} else { } else {
process.env.HTTPS_PROXY = ""; httpsAgent = new https.Agent(agentOptions);
process.env.https_proxy = "";
httpsAgent = new https.Agent(opts);
} }
return { return {
httpAgent, httpAgent,
@@ -394,6 +401,145 @@ export function createAgent(opts: CreateAgentOptions = {}) {
}; };
} }
function setProxyEnvironment(opts: GlobalProxyOptions = {}) {
setEnvValue("HTTP_PROXY", opts.httpProxy);
setEnvValue("http_proxy", opts.httpProxy);
setEnvValue("HTTPS_PROXY", opts.httpsProxy);
setEnvValue("https_proxy", opts.httpsProxy);
const noProxy = normalizeNoProxyText(opts.noProxy);
setEnvValue("NO_PROXY", noProxy);
setEnvValue("no_proxy", noProxy);
}
function setEnvValue(key: string, value?: string) {
process.env[key] = value || "";
}
function shouldBypassProxy(config: AxiosRequestConfig, noProxy?: string) {
if (!noProxy) {
return false;
}
const target = getRequestTarget(config);
if (!target) {
return false;
}
return splitNoProxyRules(noProxy).some(item => isNoProxyMatched(item, target));
}
function getRequestTarget(config: AxiosRequestConfig) {
try {
const baseURL = config.baseURL || undefined;
const url = new URL(config.url || "", baseURL);
return {
hostname: normalizeHost(url.hostname),
port: url.port,
};
} catch (e) {
return null;
}
}
export function isNoProxyMatched(rule: string, target: { hostname: string; port: string }) {
if (rule === "*") {
return true;
}
const normalizedRule = normalizeNoProxyRule(rule);
if (!normalizedRule.host) {
return false;
}
if (normalizedRule.port && normalizedRule.port !== target.port) {
return false;
}
const host = normalizeHost(target.hostname);
if (normalizedRule.host.includes("*")) {
return wildcardHostMatched(normalizedRule.host, host);
}
if (normalizedRule.host.startsWith("*.")) {
const suffix = normalizedRule.host.substring(1);
return host.endsWith(suffix);
}
if (normalizedRule.host.startsWith(".")) {
return host === normalizedRule.host.substring(1) || host.endsWith(normalizedRule.host);
}
return host === normalizedRule.host || host.endsWith(`.${normalizedRule.host}`);
}
function normalizeNoProxyRule(rule: string) {
let value = rule.trim().toLowerCase();
if (value.includes("://")) {
try {
const url = new URL(value);
return {
host: normalizeHost(url.hostname),
port: url.port,
};
} catch (e) {
return {
host: "",
port: "",
};
}
}
let port = "";
if (value.startsWith("[")) {
const closeIndex = value.indexOf("]");
const host = value.substring(1, closeIndex);
const rest = value.substring(closeIndex + 1);
if (rest.startsWith(":")) {
port = rest.substring(1);
}
return {
host: normalizeHost(host),
port,
};
}
const colonCount = (value.match(/:/g) || []).length;
const portIndex = value.lastIndexOf(":");
if (colonCount === 1 && portIndex > -1) {
port = value.substring(portIndex + 1);
value = value.substring(0, portIndex);
}
return {
host: normalizeHost(value),
port,
};
}
function normalizeHost(host: string) {
let value = host.trim().toLowerCase();
if (value.startsWith("[") && value.endsWith("]")) {
value = value.substring(1, value.length - 1);
}
return value;
}
function wildcardHostMatched(rule: string, host: string) {
const pattern = rule.split("*").map(escapeRegExp).join(".*");
return new RegExp(`^${pattern}$`).test(host);
}
function escapeRegExp(value: string) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function normalizeNoProxyText(noProxy?: string) {
return splitNoProxyRules(noProxy).join(",");
}
function splitNoProxyRules(noProxy?: string) {
if (!noProxy) {
return [];
}
return noProxy
.split(/[,\s]+/)
.map(item => item.trim())
.filter(Boolean);
}
export async function download(req: { http: HttpClient; config: HttpRequestConfig; savePath: string; logger: ILogger }) { export async function download(req: { http: HttpClient; config: HttpRequestConfig; savePath: string; logger: ILogger }) {
const { http, config, savePath, logger } = req; const { http, config, savePath, logger } = req;
return safePromise((resolve, reject) => { return safePromise((resolve, reject) => {
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
**Note:** Version bump only for package @certd/pipeline
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/pipeline
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
### Performance Improvements
* 新增站点证书监控从DNS解析记录批量导入功能 ([f9541fa](https://github.com/certd/certd/commit/f9541fab701e01ba57af061da322204c894adfb8))
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/pipeline
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
### Bug Fixes ### Bug Fixes
+16 -14
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/pipeline", "name": "@certd/pipeline",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -16,14 +16,16 @@
"test": "mocha --loader=ts-node/esm", "test": "mocha --loader=ts-node/esm",
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"", "test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"@certd/basic": "^1.41.0", "@certd/basic": "^1.41.4",
"@certd/plus-core": "^1.41.0", "@certd/plus-core": "^1.41.4",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13" "reflect-metadata": "^0.2.2"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^23.0.4", "@rollup/plugin-commonjs": "^23.0.4",
@@ -31,23 +33,23 @@
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@types/chai": "^4.3.10", "@types/chai": "^4.3.12",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.6",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "4.3.10", "chai": "^5.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.41.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"mocha": "^10.2.0", "mocha": "^10.6.0",
"prettier": "^2.8.8", "prettier": "3.3.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+20 -1
View File
@@ -3,6 +3,7 @@ import { FormItemProps } from "../dt/index.js";
import { HttpClient, ILogger, utils } from "@certd/basic"; import { HttpClient, ILogger, utils } from "@certd/basic";
import * as _ from "lodash-es"; import * as _ from "lodash-es";
import { PluginRequestHandleReq } from "../plugin/index.js"; import { PluginRequestHandleReq } from "../plugin/index.js";
import { IRuntimeDepsService, IServiceGetter } from "../service/index.js";
// export type AccessRequestHandleReqInput<T = any> = { // export type AccessRequestHandleReqInput<T = any> = {
// id?: number; // id?: number;
@@ -20,6 +21,8 @@ export type AccessInputDefine = FormItemProps & {
export type AccessDefine = Registrable & { export type AccessDefine = Registrable & {
icon?: string; icon?: string;
subtype?: string; subtype?: string;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
input?: { input?: {
[key: string]: AccessInputDefine; [key: string]: AccessInputDefine;
}; };
@@ -39,13 +42,29 @@ export type AccessContext = {
logger: ILogger; logger: ILogger;
utils: typeof utils; utils: typeof utils;
accessService: IAccessService; accessService: IAccessService;
serviceGetter?: IServiceGetter;
define?: AccessDefine;
}; };
export abstract class BaseAccess implements IAccess { export abstract class BaseAccess implements IAccess {
ctx!: AccessContext; ctx!: AccessContext;
runtimeDepsService?: IRuntimeDepsService;
setCtx(ctx: AccessContext) { async importRuntime(specifier: string) {
if (!this.runtimeDepsService) {
throw new Error("runtimeDepsService 未初始化");
}
return await this.runtimeDepsService.importRuntime(specifier, this.ctx.logger);
}
async setCtx(ctx: AccessContext) {
this.ctx = ctx; this.ctx = ctx;
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
}
if (this.runtimeDepsService && this.ctx.define?.name) {
await this.runtimeDepsService.ensureRuntimeDependencies({ pluginKeys: `access:${this.ctx.define.name}`, logger: this.ctx.logger });
}
} }
async onRequest(req: AccessRequestHandleReq) { async onRequest(req: AccessRequestHandleReq) {
@@ -67,7 +67,9 @@ export async function newAccess(type: string, input: any, accessService: IAccess
accessService, accessService,
}; };
} }
access.setCtx(ctx); ctx.define = ctx.define || register.define;
access.runtimeDepsService = (accessService as any).runtimeDepsService;
await access.setCtx(ctx);
access._type = type; access._type = type;
return access; return access;
} }
+1 -1
View File
@@ -387,7 +387,7 @@ export class Executor {
}), }),
serviceGetter: this.options.serviceGetter, serviceGetter: this.options.serviceGetter,
}; };
instance.setCtx(taskCtx); await instance.setCtx(taskCtx);
await instance.onInstance(); await instance.onInstance();
const result = await instance.execute(); const result = await instance.execute();
+20 -2
View File
@@ -3,7 +3,7 @@ import { Registrable } from "../registry/index.js";
import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js"; import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js";
import { HttpClient, ILogger, utils } from "@certd/basic"; import { HttpClient, ILogger, utils } from "@certd/basic";
import * as _ from "lodash-es"; import * as _ from "lodash-es";
import { IEmailService } from "../service/index.js"; import { IEmailService, IRuntimeDepsService, IServiceGetter } from "../service/index.js";
export type NotificationBody = { export type NotificationBody = {
userId?: number; userId?: number;
@@ -39,6 +39,8 @@ export type NotificationInputDefine = FormItemProps & {
}; };
export type NotificationDefine = Registrable & { export type NotificationDefine = Registrable & {
needPlus?: boolean; needPlus?: boolean;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
input?: { input?: {
[key: string]: NotificationInputDefine; [key: string]: NotificationInputDefine;
}; };
@@ -78,6 +80,8 @@ export type NotificationContext = {
logger: ILogger; logger: ILogger;
utils: typeof utils; utils: typeof utils;
emailService: IEmailService; emailService: IEmailService;
serviceGetter?: IServiceGetter;
define?: NotificationDefine;
}; };
export abstract class BaseNotification implements INotification { export abstract class BaseNotification implements INotification {
@@ -85,6 +89,14 @@ export abstract class BaseNotification implements INotification {
ctx!: NotificationContext; ctx!: NotificationContext;
http!: HttpClient; http!: HttpClient;
logger!: ILogger; logger!: ILogger;
runtimeDepsService?: IRuntimeDepsService;
async importRuntime(specifier: string) {
if (!this.runtimeDepsService) {
return await import(specifier);
}
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
}
async doSend(body: NotificationBody) { async doSend(body: NotificationBody) {
return await this.send(body); return await this.send(body);
@@ -93,10 +105,16 @@ export abstract class BaseNotification implements INotification {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
async onInstance() {} async onInstance() {}
setCtx(ctx: NotificationContext) { async setCtx(ctx: NotificationContext) {
this.ctx = ctx; this.ctx = ctx;
this.http = ctx.http; this.http = ctx.http;
this.logger = ctx.logger; this.logger = ctx.logger;
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
}
if (this.runtimeDepsService && this.ctx.define?.name) {
await this.runtimeDepsService.ensureRuntimeDependencies({ pluginKeys: `notification:${this.ctx.define.name}`, logger: this.logger });
}
} }
setDefine = (define: NotificationDefine) => { setDefine = (define: NotificationDefine) => {
this.define = define; this.define = define;
@@ -61,7 +61,8 @@ export async function newNotification(type: string, input: any, ctx: Notificatio
throw new Error("ctx is required"); throw new Error("ctx is required");
} }
plugin.setDefine(register.define); plugin.setDefine(register.define);
plugin.setCtx(ctx); ctx.define = ctx.define || register.define;
await plugin.setCtx(ctx);
await plugin.onInstance(); await plugin.onInstance();
return plugin; return plugin;
} }
+20 -2
View File
@@ -10,7 +10,7 @@ import { INotificationService } from "../notification/index.js";
import { Registrable } from "../registry/index.js"; import { Registrable } from "../registry/index.js";
import { IPluginConfigService } from "../service/config.js"; import { IPluginConfigService } from "../service/config.js";
import { TaskEmitter } from "../service/emit.js"; import { TaskEmitter } from "../service/emit.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js"; import { ICnameProxyService, IEmailService, IRuntimeDepsService, IServiceGetter, IUrlService } from "../service/index.js";
export type PluginRequestHandleReq<T = any> = { export type PluginRequestHandleReq<T = any> = {
typeName: string; typeName: string;
@@ -46,6 +46,8 @@ export type PluginDefine = Registrable & {
default?: any; default?: any;
group?: string; group?: string;
icon?: string; icon?: string;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
input?: { input?: {
[key: string]: TaskInputDefine; [key: string]: TaskInputDefine;
}; };
@@ -73,6 +75,8 @@ export type ITaskPlugin = {
onInstance(): Promise<void>; onInstance(): Promise<void>;
execute(): Promise<void | string>; execute(): Promise<void | string>;
onRequest(req: PluginRequestHandleReq<any>): Promise<any>; onRequest(req: PluginRequestHandleReq<any>): Promise<any>;
setCtx(ctx: TaskInstanceContext): Promise<void>;
importRuntime?(specifier: string): Promise<any>;
[key: string]: any; [key: string]: any;
}; };
@@ -146,6 +150,14 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
logger!: ILogger; logger!: ILogger;
http!: HttpClient; http!: HttpClient;
accessService!: IAccessService; accessService!: IAccessService;
runtimeDepsService!: IRuntimeDepsService;
async importRuntime(specifier: string) {
if (!this.runtimeDepsService) {
throw new Error("runtimeDepsService 未初始化");
}
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
}
clearLastStatus() { clearLastStatus() {
this._result.clearLastStatus = true; this._result.clearLastStatus = true;
@@ -161,11 +173,17 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
} }
} }
setCtx(ctx: TaskInstanceContext) { async setCtx(ctx: TaskInstanceContext) {
this.ctx = ctx; this.ctx = ctx;
this.logger = ctx.logger; this.logger = ctx.logger;
this.accessService = ctx.accessService; this.accessService = ctx.accessService;
this.http = ctx.http; this.http = ctx.http;
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
}
if (this.runtimeDepsService && this.ctx.define?.name) {
await this.runtimeDepsService.ensureRuntimeDependencies({ pluginKeys: `plugin:${this.ctx.define.name}`, logger: this.logger });
}
// 将证书加入secret // 将证书加入secret
// @ts-ignore // @ts-ignore
if (this.cert && this.cert.crt && this.cert.key) { if (this.cert && this.cert.crt && this.cert.key) {
@@ -7,6 +7,7 @@ export type Registrable = {
group?: string; group?: string;
deprecated?: string; deprecated?: string;
order?: number; order?: number;
icon?: string;
}; };
export type TargetGetter<T> = () => Promise<T>; export type TargetGetter<T> = () => Promise<T>;
export type RegistryItem<T> = { export type RegistryItem<T> = {
+1 -1
View File
@@ -32,7 +32,7 @@ export class PipelineEmitter {
} }
off(event: string, listener: PipelineEventListener) { off(event: string, listener: PipelineEventListener) {
if (this.events[event]) { if (this.events[event]) {
this.events[event] = this.events[event].filter((l) => l !== listener); this.events[event] = this.events[event].filter(l => l !== listener);
} }
} }
once(event: string, listener: PipelineEventListener) { once(event: string, listener: PipelineEventListener) {
@@ -3,6 +3,7 @@ export * from "./cname.js";
export * from "./config.js"; export * from "./config.js";
export * from "./url.js"; export * from "./url.js";
export * from "./emit.js"; export * from "./emit.js";
export * from "./runtime.js";
export type IServiceGetter = { export type IServiceGetter = {
get: <T>(name: string) => Promise<T>; get: <T>(name: string) => Promise<T>;
}; };
@@ -0,0 +1,27 @@
/**
*
*/
export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise<any>;
/**
*
*/
export type ILogger = {
info: (message: string) => void;
};
/**
*
*/
export type EnsureRuntimeDepsOptions = {
pluginKeys: string | string[];
logger?: ILogger;
};
/**
*
*/
export interface IRuntimeDepsService {
ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise<any>;
importRuntime: ImportRuntime;
}
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/lib-huawei
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/lib-huawei
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/lib-huawei
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/lib-huawei **Note:** Version bump only for package @certd/lib-huawei
+8 -5
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-huawei", "name": "@certd/lib-huawei",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"module": "./dist/bundle.js", "module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts", "types": "./dist/d/index.d.ts",
@@ -12,20 +12,23 @@
"dev-build": "npm run build", "dev-build": "npm run build",
"preview": "vite preview", "preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests", "test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish" "pub": "npm publish",
"compile": "npm run build",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"axios": "^1.9.0", "axios": "^1.9.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^3.7.4" "rollup": "4.50.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"prettier": "^2.8.8", "prettier": "3.3.3",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/lib-iframe
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/lib-iframe
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/lib-iframe
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/lib-iframe **Note:** Version bump only for package @certd/lib-iframe
+12 -9
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-iframe", "name": "@certd/lib-iframe",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -15,24 +15,27 @@
"build2": "vue-tsc --noEmit && vite build", "build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview", "preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests", "test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish" "pub": "npm publish",
"compile": "npm run build",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"nanoid": "^4.0.0" "nanoid": "^5.0.7"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.12",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.24.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"prettier": "^2.8.8", "prettier": "3.3.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/jdcloud
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/jdcloud
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/jdcloud
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/jdcloud **Note:** Version bump only for package @certd/jdcloud
+10 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "@certd/jdcloud", "name": "@certd/jdcloud",
"version": "1.41.0", "version": "1.41.4",
"description": "jdcloud openApi sdk", "description": "jdcloud openApi sdk",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"module": "./dist/bundle.js", "module": "./dist/bundle.js",
@@ -10,7 +10,10 @@
"build": "npm run before-build && rollup -c ", "build": "npm run before-build && rollup -c ",
"dev-build": "npm run build", "dev-build": "npm run build",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests", "test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish" "pub": "npm publish",
"compile": "npm run build",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"author": "", "author": "",
"license": "Apache", "license": "Apache",
@@ -21,7 +24,7 @@
"debug": "^3.1.0", "debug": "^3.1.0",
"node-fetch": "^2.1.2", "node-fetch": "^2.1.2",
"querystring": "^0.2.0", "querystring": "^0.2.0",
"rollup": "^3.7.4", "rollup": "4.50.0",
"url": "^0.11.0", "url": "^0.11.0",
"uuid": "^3.1.0" "uuid": "^3.1.0"
}, },
@@ -29,13 +32,13 @@
"@rollup/plugin-typescript": "^11.0.0", "@rollup/plugin-typescript": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "^4.1.2", "chai": "^5.1.0",
"config": "^1.30.0", "config": "^1.30.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"js-yaml": "^3.11.0", "js-yaml": "^3.11.0",
"mocha": "^5.0.0", "mocha": "^10.6.0",
"prettier": "^2.8.8", "prettier": "3.3.3",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"engines": { "engines": {
@@ -59,5 +62,5 @@
"fetch" "fetch"
] ]
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
**Note:** Version bump only for package @certd/lib-k8s
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/lib-k8s
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/lib-k8s
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/lib-k8s
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/lib-k8s **Note:** Version bump only for package @certd/lib-k8s
+11 -9
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/lib-k8s", "name": "@certd/lib-k8s",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",
@@ -16,25 +16,27 @@
"preview": "vite preview", "preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests", "test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"@certd/basic": "^1.41.0", "@certd/basic": "^1.41.4",
"@kubernetes/client-node": "0.21.0" "@kubernetes/client-node": "0.21.0"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.12",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.24.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"prettier": "^2.8.8", "prettier": "3.3.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+18 -3
View File
@@ -1,7 +1,22 @@
{ {
"extends": "./node_modules/mwts/", "parser": "@typescript-eslint/parser",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"], "plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": { "env": {
"jest": true "mocha": true
},
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unused-vars": "off"
} }
} }
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
**Note:** Version bump only for package @certd/lib-server
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/lib-server
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/lib-server
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
### Performance Improvements
* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc))
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
### Bug Fixes ### Bug Fixes
+20 -20
View File
@@ -1,6 +1,6 @@
{ {
"name": "@certd/lib-server", "name": "@certd/lib-server",
"version": "1.41.0", "version": "1.41.4",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@@ -15,11 +15,11 @@
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"", "test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
"test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'", "test1": "midway-bin test --ts -V -f test/blank.test.ts -t 'hash-check'",
"cov": "midway-bin cov --ts", "cov": "midway-bin cov --ts",
"lint": "mwts check",
"lint:fix": "mwts fix",
"prepublish": "npm run build", "prepublish": "npm run build",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"keywords": [], "keywords": [],
"author": "greper", "author": "greper",
@@ -29,11 +29,11 @@
], ],
"license": "AGPL", "license": "AGPL",
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.41.0", "@certd/acme-client": "^1.41.4",
"@certd/basic": "^1.41.0", "@certd/basic": "^1.41.4",
"@certd/pipeline": "^1.41.0", "@certd/pipeline": "^1.41.4",
"@certd/plugin-lib": "^1.41.0", "@certd/plugin-lib": "^1.41.4",
"@certd/plus-core": "^1.41.0", "@certd/plus-core": "^1.41.4",
"@midwayjs/cache": "3.14.0", "@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11", "@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13", "@midwayjs/i18n": "3.20.13",
@@ -46,28 +46,28 @@
"better-sqlite3": "^11.1.2", "better-sqlite3": "^11.1.2",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mwts": "^1.3.0",
"mwtsc": "^1.4.0",
"typeorm": "^0.3.20" "typeorm": "^0.3.20"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "mwts": "^1.3.0",
"@types/mocha": "^10.0.1", "mwtsc": "^1.4.0",
"@types/chai": "^4.3.12",
"@types/mocha": "^10.0.6",
"@types/node": "^18", "@types/node": "^18",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.24.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"mocha": "^10.2.0", "mocha": "^10.6.0",
"prettier": "^2.8.8", "prettier": "3.3.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typeorm": "^0.3.11", "typeorm": "^0.3.20",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
@@ -81,6 +81,7 @@ export class SysPrivateSettings extends BaseSettings {
httpsProxy? = ''; httpsProxy? = '';
httpProxy? = ''; httpProxy? = '';
noProxy? = '';
commonHeaders?: string = ''; commonHeaders?: string = '';
reverseProxies?: Record<string, string> = {}; reverseProxies?: Record<string, string> = {};
@@ -165,6 +165,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
const opts = { const opts = {
httpProxy: privateSetting.httpProxy, httpProxy: privateSetting.httpProxy,
httpsProxy: privateSetting.httpsProxy, httpsProxy: privateSetting.httpsProxy,
noProxy: privateSetting.noProxy,
}; };
setGlobalProxy(opts); setGlobalProxy(opts);
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders)); setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
@@ -172,7 +173,6 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
if (privateSetting.dnsResultOrder) { if (privateSetting.dnsResultOrder) {
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any); dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
} }
if (privateSetting.pipelineMaxRunningCount) { if (privateSetting.pipelineMaxRunningCount) {
executorQueue.setMaxRunningCount(privateSetting.pipelineMaxRunningCount); executorQueue.setMaxRunningCount(privateSetting.pipelineMaxRunningCount);
} }
@@ -1,20 +1,29 @@
import { IAccessService } from "@certd/pipeline"; import { IAccessService, IRuntimeDepsService } from "@certd/pipeline";
export type AccessRuntimeDepsService = IRuntimeDepsService;
export class AccessGetter implements IAccessService { export class AccessGetter implements IAccessService {
userId: number; userId: number;
projectId?: number; projectId?: number;
getter: <T>(id: any, userId?: number, projectId?: number, ignorePermission?: boolean) => Promise<T>; runtimeDepsService?: AccessRuntimeDepsService;
constructor(userId: number, projectId: number, getter: (id: any, userId: number, projectId?: number, ignorePermission?: boolean) => Promise<any>) { getter: <T>(id: any, userId?: number, projectId?: number, ignorePermission?: boolean, runtimeDepsService?: AccessRuntimeDepsService) => Promise<T>;
constructor(
userId: number,
projectId: number,
getter: (id: any, userId: number, projectId?: number, ignorePermission?: boolean, runtimeDepsService?: AccessRuntimeDepsService) => Promise<any>,
runtimeDepsService?: AccessRuntimeDepsService
) {
this.userId = userId; this.userId = userId;
this.projectId = projectId; this.projectId = projectId;
this.getter = getter; this.getter = getter;
this.runtimeDepsService = runtimeDepsService;
} }
async getById<T = any>(id: any) { async getById<T = any>(id: any) {
return await this.getter<T>(id, this.userId, this.projectId); return await this.getter<T>(id, this.userId, this.projectId, false, this.runtimeDepsService);
} }
async getCommonById<T = any>(id: any) { async getCommonById<T = any>(id: any) {
return await this.getter<T>(id, 0, null); return await this.getter<T>(id, 0, null, false, this.runtimeDepsService);
} }
} }
@@ -2,10 +2,11 @@ import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { InjectEntityModel } from "@midwayjs/typeorm"; import { InjectEntityModel } from "@midwayjs/typeorm";
import { In, Repository } from "typeorm"; import { In, Repository } from "typeorm";
import { AccessGetter, BaseService, PageReq, PermissionException, ValidateException } from "../../../index.js"; import { AccessGetter, BaseService, PageReq, PermissionException, ValidateException } from "../../../index.js";
import type { AccessRuntimeDepsService } from "./access-getter.js";
import { AccessEntity } from "../entity/access.js"; import { AccessEntity } from "../entity/access.js";
import { AccessDefine, accessRegistry, newAccess } from "@certd/pipeline"; import { AccessDefine, accessRegistry, newAccess } from "@certd/pipeline";
import { EncryptService } from "./encrypt-service.js"; import { EncryptService } from "./encrypt-service.js";
import { logger, utils } from "@certd/basic"; import { http, logger, utils } from "@certd/basic";
/** /**
* *
@@ -160,7 +161,7 @@ export class AccessService extends BaseService<AccessEntity> {
}; };
} }
async getAccessById(id: any, checkUserId: boolean, userId?: number, projectId?: number): Promise<any> { async getAccessById(id: any, checkUserId: boolean, userId?: number, projectId?: number, runtimeDepsService?: AccessRuntimeDepsService): Promise<any> {
const entity = await this.info(id); const entity = await this.info(id);
if (entity == null) { if (entity == null) {
throw new Error(`该授权配置不存在,请确认是否已被删除:id=${id}`); throw new Error(`该授权配置不存在,请确认是否已被删除:id=${id}`);
@@ -183,12 +184,20 @@ export class AccessService extends BaseService<AccessEntity> {
id: entity.id, id: entity.id,
...setting, ...setting,
}; };
const accessGetter = new AccessGetter(userId, projectId, this.getById.bind(this)); const getAccessById = this.getById.bind(this);
return await newAccess(entity.type, input, accessGetter); const accessGetter = new AccessGetter(userId, projectId, getAccessById, runtimeDepsService);
const accessContext = {
logger,
http,
utils,
accessService: accessGetter,
} as any;
const access = await newAccess(entity.type, input, accessGetter, accessContext);
return access;
} }
async getById(id: any, userId: number, projectId?: number): Promise<any> { async getById(id: any, userId: number, projectId?: number, _ignorePermission?: boolean, runtimeDepsService?: AccessRuntimeDepsService): Promise<any> {
return await this.getAccessById(id, true, userId, projectId); return await this.getAccessById(id, true, userId, projectId, runtimeDepsService);
} }
decryptAccessEntity(entity: AccessEntity): any { decryptAccessEntity(entity: AccessEntity): any {
@@ -4,6 +4,7 @@ import {
accessRegistry, accessRegistry,
FormItemProps, FormItemProps,
IAccessService, IAccessService,
IRuntimeDepsService,
IServiceGetter, IServiceGetter,
PluginRequestHandleReq, PluginRequestHandleReq,
Registrable Registrable
@@ -27,6 +28,8 @@ export type AddonInputDefine = FormItemProps & {
export type AddonDefine = Registrable & { export type AddonDefine = Registrable & {
addonType: string; addonType: string;
needPlus?: boolean; needPlus?: boolean;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
input?: { input?: {
[key: string]: AddonInputDefine; [key: string]: AddonInputDefine;
}; };
@@ -64,15 +67,20 @@ export abstract class BaseAddon implements IAddon {
ctx!: AddonContext; ctx!: AddonContext;
http!: HttpClient; http!: HttpClient;
logger!: ILogger; logger!: ILogger;
runtimeDepsService?: IRuntimeDepsService;
async importRuntime(specifier: string) {
if (!this.runtimeDepsService) {
return await import(specifier);
}
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
}
title!: string; title!: string;
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
async onInstance() {} async onInstance() {}
async getAccess<T = any>(accessId: string | number, isCommon = false) { async getAccess<T = any>(accessId: string | number, isCommon = false) {
if (accessId == null) { if (accessId == null) {
throw new Error("您还没有配置授权"); throw new Error("您还没有配置授权");
@@ -106,11 +114,16 @@ export abstract class BaseAddon implements IAddon {
return res as T; return res as T;
} }
async setCtx(ctx: AddonContext) {
setCtx(ctx: AddonContext) {
this.ctx = ctx; this.ctx = ctx;
this.http = ctx.http; this.http = ctx.http;
this.logger = ctx.logger; this.logger = ctx.logger;
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
}
if (this.runtimeDepsService && this.define?.addonType && this.define?.name) {
await this.runtimeDepsService.ensureRuntimeDependencies({ pluginKeys: `addon:${this.define.addonType}:${this.define.name}`, logger: this.logger });
}
} }
setDefine = (define:AddonDefine) => { setDefine = (define:AddonDefine) => {
this.define = define; this.define = define;
@@ -63,9 +63,7 @@ export async function newAddon(addonType:string,type: string, input: any, ctx: A
throw new Error("ctx is required"); throw new Error("ctx is required");
} }
plugin.setDefine(register.define); plugin.setDefine(register.define);
plugin.setCtx(ctx); await plugin.setCtx(ctx);
await plugin.onInstance(); await plugin.onInstance();
return plugin; return plugin;
} }
+18 -3
View File
@@ -1,7 +1,22 @@
{ {
"extends": "./node_modules/mwts/", "parser": "@typescript-eslint/parser",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"], "plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": { "env": {
"jest": true "mocha": true
},
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unused-vars": "off"
} }
} }
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/midway-flyway-js
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/midway-flyway-js **Note:** Version bump only for package @certd/midway-flyway-js
+12 -9
View File
@@ -1,6 +1,6 @@
{ {
"name": "@certd/midway-flyway-js", "name": "@certd/midway-flyway-js",
"version": "1.41.0", "version": "1.41.4",
"description": "midway with flyway, sql upgrade way ", "description": "midway with flyway, sql upgrade way ",
"private": false, "private": false,
"type": "module", "type": "module",
@@ -16,7 +16,10 @@
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests", "test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"cov": "midway-bin cov --ts", "cov": "midway-bin cov --ts",
"prepublish": "npm run build", "prepublish": "npm run build",
"pub": "npm publish" "pub": "npm publish",
"compile": "npm run build",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"keywords": [], "keywords": [],
"author": "greper", "author": "greper",
@@ -32,22 +35,22 @@
"better-sqlite3": "^11.1.2" "better-sqlite3": "^11.1.2"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.12",
"@types/node": "^18", "@types/node": "^18",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.24.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"prettier": "^2.8.8", "prettier": "3.3.3",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typeorm": "^0.3.11", "typeorm": "^0.3.20",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
**Note:** Version bump only for package @certd/plugin-cert
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/plugin-cert
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
**Note:** Version bump only for package @certd/plugin-cert
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/plugin-cert
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/plugin-cert **Note:** Version bump only for package @certd/plugin-cert
+14 -17
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-cert", "name": "@certd/plugin-cert",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -15,31 +15,28 @@
"preview": "vite preview", "preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest echo no unit tests", "test:unit": "cross-env NODE_ENV=unittest echo no unit tests",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"@certd/acme-client": "^1.41.0", "@certd/plugin-lib": "^1.41.4"
"@certd/basic": "^1.41.0",
"@certd/pipeline": "^1.41.0",
"@certd/plugin-lib": "^1.41.0",
"psl": "^1.9.0",
"punycode.js": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "@types/chai": "^4.3.12",
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.6",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "^4.3.6", "chai": "^5.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.24.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"mocha": "^10.1.0", "mocha": "^10.6.0",
"prettier": "^2.8.8", "prettier": "3.3.3",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
**Note:** Version bump only for package @certd/plugin-lib
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
**Note:** Version bump only for package @certd/plugin-lib
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
### Performance Improvements
* 新增站点证书监控从DNS解析记录批量导入功能 ([f9541fa](https://github.com/certd/certd/commit/f9541fab701e01ba57af061da322204c894adfb8))
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
**Note:** Version bump only for package @certd/plugin-lib
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
**Note:** Version bump only for package @certd/plugin-lib **Note:** Version bump only for package @certd/plugin-lib
+18 -34
View File
@@ -1,7 +1,7 @@
{ {
"name": "@certd/plugin-lib", "name": "@certd/plugin-lib",
"private": false, "private": false,
"version": "1.41.0", "version": "1.41.4",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -15,51 +15,35 @@
"preview": "vite preview", "preview": "vite preview",
"test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"", "test:unit": "cross-env NODE_ENV=unittest mocha --no-config --node-option no-warnings --node-option loader=ts-node/esm \"src/**/*.test.ts\"",
"pub": "npm publish", "pub": "npm publish",
"compile": "tsc --skipLibCheck --watch" "compile": "tsc --skipLibCheck --watch",
"format": "prettier --write src",
"lint": "eslint --fix"
}, },
"dependencies": { "dependencies": {
"@alicloud/openapi-client": "^0.4.15", "@certd/acme-client": "^1.41.4",
"@alicloud/openapi-util": "^0.3.2", "@certd/basic": "^1.41.4",
"@alicloud/pop-core": "^1.7.10", "@certd/pipeline": "^1.41.4",
"@alicloud/tea-util": "^1.4.11",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.41.0",
"@certd/basic": "^1.41.0",
"@certd/pipeline": "^1.41.0",
"@certd/plus-core": "^1.41.0",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
"cos-nodejs-sdk-v5": "^2.15.4",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"iconv-lite": "^0.6.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"psl": "^1.15.0", "psl": "^1.15.0"
"punycode.js": "^2.3.1",
"qiniu": "^7.12.0",
"rimraf": "^5.0.5",
"socks": "^2.8.3",
"socks-proxy-agent": "^8.0.4",
"ssh2": "1.17.0",
"strip-ansi": "^7.1.0",
"tencentcloud-sdk-nodejs": "^4.1.166"
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^4.3.3", "rimraf": "^5.0.5",
"@types/mocha": "^10.0.0", "@types/chai": "^4.3.12",
"@types/mocha": "^10.0.6",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.26.1",
"chai": "^4.3.6", "chai": "^5.1.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.24.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"esmock": "^2.7.5", "esmock": "^2.7.5",
"mocha": "^10.1.0", "mocha": "^10.6.0",
"prettier": "^2.8.8", "prettier": "3.3.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"gitHead": "7ceb0f6306b8b5e9ab875b9f7c41cc7d56209ea4" "gitHead": "bc731e4fb119787930e816a7d57c808b1b5cd66a"
} }
@@ -132,7 +132,7 @@ export class CertConverter {
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true });
} }
await this.exec(`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype PKCS12 -deststorepass "${jksPassword}" `); await this.exec(`keytool -importkeystore -srckeystore ${p12Path} -srcstoretype PKCS12 -srcstorepass "${jksPassword}" -destkeystore ${jksPath} -deststoretype JKS -deststorepass "${jksPassword}" `);
fs.unlinkSync(p12Path); fs.unlinkSync(p12Path);
const fileBuffer = fs.readFileSync(jksPath); const fileBuffer = fs.readFileSync(jksPath);
@@ -4,6 +4,8 @@ import { IAccess, IAccessService, IServiceGetter, PageRes, PageSearch, Registrab
export type DnsProviderDefine = Registrable & { export type DnsProviderDefine = Registrable & {
accessType: string; accessType: string;
icon?: string; icon?: string;
dependPlugins?: Record<string, string>;
dependPackages?: Record<string, string>;
}; };
export type CreateRecordOptions = { export type CreateRecordOptions = {
@@ -27,6 +29,7 @@ export type DnsProviderContext = {
domainParser: IDomainParser; domainParser: IDomainParser;
serviceGetter: IServiceGetter; serviceGetter: IServiceGetter;
accessGetter?: IAccessService; accessGetter?: IAccessService;
define?: DnsProviderDefine;
}; };
export type DomainRecord = { export type DomainRecord = {
@@ -34,6 +37,14 @@ export type DomainRecord = {
domain: string; domain: string;
}; };
export type DnsResolveRecord = {
id: string;
hostRecord: string;
fullRecord: string;
type: string;
value: string;
};
export interface IDnsProvider<T = any> { export interface IDnsProvider<T = any> {
onInstance(): Promise<void>; onInstance(): Promise<void>;
@@ -53,12 +64,14 @@ export interface IDnsProvider<T = any> {
removeRecord(options: RemoveRecordOptions<T>): Promise<void>; removeRecord(options: RemoveRecordOptions<T>): Promise<void>;
setCtx(ctx: DnsProviderContext): void; setCtx(ctx: DnsProviderContext): Promise<void>;
//中文域名是否需要punycode转码,如果返回True,则使用punycode来添加解析记录,否则使用中文域名添加解析记录 //中文域名是否需要punycode转码,如果返回True,则使用punycode来添加解析记录,否则使用中文域名添加解析记录
usePunyCode(): boolean; usePunyCode(): boolean;
getDomainListPage(pager: PageSearch): Promise<PageRes<DomainRecord>>; getDomainListPage(pager: PageSearch): Promise<PageRes<DomainRecord>>;
getRecordListPage?(domain: string, pager: PageSearch): Promise<PageRes<DnsResolveRecord>>;
} }
export interface ISubDomainsGetter { export interface ISubDomainsGetter {
@@ -1,12 +1,20 @@
import { HttpClient, ILogger } from "@certd/basic"; import { HttpClient, ILogger } from "@certd/basic";
import { IAccessService, PageRes, PageSearch } from "@certd/pipeline"; import { IAccessService, IRuntimeDepsService, PageRes, PageSearch } from "@certd/pipeline";
import punycode from "punycode.js"; import punycode from "punycode.js";
import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, DomainRecord, IDnsProvider, RemoveRecordOptions } from "./api.js"; import { CreateRecordOptions, DnsProviderContext, DnsProviderDefine, DnsResolveRecord, DomainRecord, IDnsProvider, RemoveRecordOptions } from "./api.js";
import { dnsProviderRegistry } from "./registry.js"; import { dnsProviderRegistry } from "./registry.js";
export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> { export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
ctx!: DnsProviderContext; ctx!: DnsProviderContext;
http!: HttpClient; http!: HttpClient;
logger!: ILogger; logger!: ILogger;
runtimeDepsService?: IRuntimeDepsService;
async importRuntime(specifier: string) {
if (!this.runtimeDepsService) {
throw new Error("runtimeDepsService 未初始化");
}
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
}
usePunyCode(): boolean { usePunyCode(): boolean {
//是否使用punycode来添加解析记录 //是否使用punycode来添加解析记录
@@ -30,10 +38,16 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
return punycode.toUnicode(domain); return punycode.toUnicode(domain);
} }
setCtx(ctx: DnsProviderContext) { async setCtx(ctx: DnsProviderContext) {
this.ctx = ctx; this.ctx = ctx;
this.logger = ctx.logger; this.logger = ctx.logger;
this.http = ctx.http; this.http = ctx.http;
if (!this.runtimeDepsService && this.ctx.serviceGetter) {
this.runtimeDepsService = await this.ctx.serviceGetter.get("runtimeDepsService");
}
if (this.runtimeDepsService && this.ctx.define?.name) {
await this.runtimeDepsService.ensureRuntimeDependencies({ pluginKeys: `dnsProvider:${this.ctx.define.name}`, logger: this.logger });
}
} }
async parseDomain(fullDomain: string) { async parseDomain(fullDomain: string) {
@@ -49,6 +63,10 @@ export abstract class AbstractDnsProvider<T = any> implements IDnsProvider<T> {
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> { async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
throw new Error("Method not implemented."); throw new Error("Method not implemented.");
} }
async getRecordListPage(domain: string, req: PageSearch): Promise<PageRes<DnsResolveRecord>> {
throw new Error("Method not implemented.");
}
} }
export async function createDnsProvider(opts: { dnsProviderType: string; context: DnsProviderContext }): Promise<IDnsProvider> { export async function createDnsProvider(opts: { dnsProviderType: string; context: DnsProviderContext }): Promise<IDnsProvider> {
@@ -64,9 +82,10 @@ export async function createDnsProvider(opts: { dnsProviderType: string; context
const accessGetter: IAccessService = await context.serviceGetter.get("accessService"); const accessGetter: IAccessService = await context.serviceGetter.get("accessService");
context.accessGetter = accessGetter; context.accessGetter = accessGetter;
} }
context.define = dnsProviderDefine;
// @ts-ignore // @ts-ignore
const dnsProvider: IDnsProvider = new DnsProviderClass(); const dnsProvider: IDnsProvider = new DnsProviderClass();
dnsProvider.setCtx(context); await dnsProvider.setCtx(context);
await dnsProvider.onInstance(); await dnsProvider.onInstance();
return dnsProvider; return dnsProvider;
} }
+37 -30
View File
@@ -1,12 +1,9 @@
FROM node:22-alpine3.21 AS builder # 根据目标平台选择基础镜像:amd64/arm64 用 trixie-slimarm/v7 没有 trixie-slim 发布,回退到 alpine
FROM --platform=linux/amd64 node:22-trixie-slim AS base-amd64
# RUN apk add build-base FROM --platform=linux/arm64 node:22-trixie-slim AS base-arm64
# RUN wget -O - https://github.com/jemalloc/jemalloc/releases/download/5.3.0/jemalloc-5.3.0.tar.bz2 | tar -xj && \ FROM --platform=linux/arm/v7 node:22-alpine AS base-arm-v7
# cd jemalloc-5.3.0 && \
# ./configure && \
# make && \
# make install
FROM base-${TARGETARCH}${TARGETVARIANT:+-}${TARGETVARIANT} AS builder
WORKDIR /workspace/ WORKDIR /workspace/
COPY . /workspace/ COPY . /workspace/
@@ -14,34 +11,44 @@ COPY . /workspace/
# https://pnpm.io/zh/migration # https://pnpm.io/zh/migration
RUN npm install -g pnpm@10.33.4 RUN npm install -g pnpm@10.33.4
#RUN cd /workspace/certd-client && pnpm install && npm run build
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
# RUN cd /workspace/certd-server && \
# pnpm install --ignore-scripts && \
# yes | pnpm approve-builds && \
# pnpm rebuild && \
# npm run build-on-docker
FROM base-${TARGETARCH}${TARGETVARIANT:+-}${TARGETVARIANT}
FROM node:22-alpine3.21
EXPOSE 7001 EXPOSE 7001
EXPOSE 7002 EXPOSE 7002
# 安装jemalloc内存分配器,优化内存占用 -- 基本没用,反而更高了 # 根据基础镜像发行版选择包管理器
# COPY --from=builder /usr/local/lib/libjemalloc.so.2 /usr/local/lib/ # trixie-slim -> apt-get, alpine -> apk
# ENV LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 RUN if [ -f /etc/debian_version ]; then \
apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
gnupg \
wget \
openssl \
netcat-openbsd \
iputils-ping \
dnsutils \
iproute2 \
&& wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor | tee /usr/share/keyrings/adoptium.gpg > /dev/null \
&& echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb bookworm main" | tee /etc/apt/sources.list.d/adoptium.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends temurin-8-jre \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*; \
elif [ -f /etc/alpine-release ]; then \
apk add --no-cache \
openssl \
openjdk8-jre; \
else \
echo "Unsupported base image"; exit 1; \
fi
RUN apk add --no-cache openssl
RUN apk add --no-cache openjdk8
RUN apk add --no-cache gcompat
WORKDIR /app/ WORKDIR /app/
COPY --from=builder /workspace/certd-server/ /app/
COPY ./patch/ssh2/*.js /app/node_modules/.pnpm/node_modules/ssh2/lib/protocol/
ENV TERM=xterm
ENV LEGO_VERSION=4.30.1 ENV LEGO_VERSION=4.30.1
ENV LEGO_DOWNLOAD_DIR=/app/tools/lego ENV LEGO_DOWNLOAD_DIR=/app/tools/lego
@@ -57,14 +64,14 @@ RUN ARCH=$(uname -m) && \
elif [ "$ARCH" = "aarch64" ]; then \ elif [ "$ARCH" = "aarch64" ]; then \
wget -O $LEGO_DOWNLOAD_DIR/lego_v${LEGO_VERSION}_linux_arm64.tar.gz https://github.com/go-acme/lego/releases/download/v${LEGO_VERSION}/lego_v${LEGO_VERSION}_linux_arm64.tar.gz; \ wget -O $LEGO_DOWNLOAD_DIR/lego_v${LEGO_VERSION}_linux_arm64.tar.gz https://github.com/go-acme/lego/releases/download/v${LEGO_VERSION}/lego_v${LEGO_VERSION}_linux_arm64.tar.gz; \
else \ else \
# armv7 不支持lego 不要再尝试了
echo "Unsupported architecture: $ARCH"; \ echo "Unsupported architecture: $ARCH"; \
fi fi
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
ENV NODE_ENV=production ENV NODE_ENV=production
ENV MIDWAY_SERVER_ENV=production ENV MIDWAY_SERVER_ENV=production
COPY --from=builder /workspace/certd-server/ /app/
COPY ./patch/ssh2/*.js /app/node_modules/.pnpm/node_modules/ssh2/lib/protocol/
CMD ["node", "--optimize-for-size", "./bootstrap.js"] CMD ["node", "--optimize-for-size", "./bootstrap.js"]
+2 -2
View File
@@ -4,8 +4,8 @@ VITE_APP_PM_ENABLED=true
VITE_APP_TITLE=Certd VITE_APP_TITLE=Certd
VITE_APP_SLOGAN=让你的证书永不过期 VITE_APP_SLOGAN=让你的证书永不过期
VITE_APP_COPYRIGHT_YEAR=2021-2026 VITE_APP_COPYRIGHT_YEAR=2021-2026
VITE_APP_COPYRIGHT_NAME=handsfree.work VITE_APP_COPYRIGHT_NAME=handfree.work
VITE_APP_COPYRIGHT_URL=https://certd.handsfree.work VITE_APP_COPYRIGHT_URL=https://certd.handfree.work
VITE_APP_LOGO=static/images/logo/logo.svg VITE_APP_LOGO=static/images/logo/logo.svg
VITE_APP_LOGIN_LOGO=static/images/logo/rect-black.svg VITE_APP_LOGIN_LOGO=static/images/logo/rect-black.svg
VITE_APP_PROJECT_PATH=https://github.com/certd/certd VITE_APP_PROJECT_PATH=https://github.com/certd/certd
+31
View File
@@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.41.4](https://github.com/certd/certd/compare/v1.41.3...v1.41.4) (2026-06-14)
### Bug Fixes
* 修复设置里面不显示tab页签,导致某些页面需要点击查询按钮才有数据出来的bug ([c1b5a35](https://github.com/certd/certd/commit/c1b5a35f90a7d4b41397717b5c27905bc68e1bfb))
## [1.41.3](https://github.com/certd/certd/compare/v1.41.2...v1.41.3) (2026-06-11)
### Performance Improvements
* 首页夜间模式主图切换为黑色背景 ([15484bc](https://github.com/certd/certd/commit/15484bc119fef7a0ca7f3fdab01d665fde47e688))
## [1.41.2](https://github.com/certd/certd/compare/v1.41.1...v1.41.2) (2026-06-10)
### Bug Fixes
* **cert-plugin:** 修复DNS提供商授权无法回显的bug ([016ae86](https://github.com/certd/certd/commit/016ae865b1d914fe5792e77a08e3ab5358df5f89))
### Performance Improvements
* 新增站点证书监控从DNS解析记录批量导入功能 ([f9541fa](https://github.com/certd/certd/commit/f9541fab701e01ba57af061da322204c894adfb8))
## [1.41.1](https://github.com/certd/certd/compare/v1.41.0...v1.41.1) (2026-06-05)
### Performance Improvements
* 流水线、监控站点支持导出 ([99fd308](https://github.com/certd/certd/commit/99fd3083f259cdb96fd656f04858dd708d1251c7))
* 优化列表页面请求两次的问题 ([5546af5](https://github.com/certd/certd/commit/5546af518e92c765513787ccaf8e856be789bcf9))
* 优化邀请注册流程 ([7a71e45](https://github.com/certd/certd/commit/7a71e45799d782d0691606fb42b4236f1d3009b0))
* **settings:** 新增NO_PROXY代理排除配置 ([c0df8be](https://github.com/certd/certd/commit/c0df8be83237e323c2c9a5bd02507430a86a00cc))
# [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04) # [1.41.0](https://github.com/certd/certd/compare/v1.40.5...v1.41.0) (2026-06-04)
### Bug Fixes ### Bug Fixes
+7 -8
View File
@@ -1,6 +1,6 @@
{ {
"name": "@certd/ui-client", "name": "@certd/ui-client",
"version": "1.41.0", "version": "1.41.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite --open", "dev": "vite --open",
@@ -74,7 +74,7 @@
"mitt": "^3.0.1", "mitt": "^3.0.1",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.52.2",
"monaco-yaml": "^5.3.1", "monaco-yaml": "^5.3.1",
"nanoid": "^4.0.0", "nanoid": "^5.0.7",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@@ -93,7 +93,6 @@
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"theme-colors": "^0.1.0", "theme-colors": "^0.1.0",
"vee-validate": "^4.15.0", "vee-validate": "^4.15.0",
"vitest": "^0.34.6",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-cropperjs": "^5.0.0", "vue-cropperjs": "^5.0.0",
"vue-echarts": "^7.0.3", "vue-echarts": "^7.0.3",
@@ -106,8 +105,8 @@
"zod-defaults": "^0.1.3" "zod-defaults": "^0.1.3"
}, },
"devDependencies": { "devDependencies": {
"@certd/lib-iframe": "^1.41.0", "@certd/lib-iframe": "^1.41.4",
"@certd/pipeline": "^1.41.0", "@certd/pipeline": "^1.41.4",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12", "@types/chai": "^4.3.12",
@@ -115,8 +114,8 @@
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",
"@types/node": "^18", "@types/node": "^18",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^8.26.1",
"@vitejs/plugin-legacy": "^5.3.2", "@vitejs/plugin-legacy": "^5.3.2",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^3.1.0",
@@ -129,7 +128,7 @@
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dependency-cruiser": "^16.2.3", "dependency-cruiser": "^16.2.3",
"dot": "^1.1.3", "dot": "^1.1.3",
"eslint": "8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

@@ -14,7 +14,7 @@ export default {
default: undefined, default: undefined,
}, },
}, },
emits: ["update:modelValue", "selected-change"], emits: ["update:modelValue", "selected-change", "change"],
setup(props: any, ctx: any) { setup(props: any, ctx: any) {
const options = ref<any[]>([]); const options = ref<any[]>([]);
@@ -33,7 +33,8 @@ export default {
// if (props.modelValue == null && options.value.length > 0) { // if (props.modelValue == null && options.value.length > 0) {
// ctx.emit("update:modelValue", options.value[0].value); // ctx.emit("update:modelValue", options.value[0].value);
// } // }
onSelectedChange(props.modelValue); //selected-changeoption
onSelectedChange(props.modelValue, true);
} }
onCreate(); onCreate();
@@ -41,9 +42,12 @@ export default {
ctx.emit("update:modelValue", value); ctx.emit("update:modelValue", value);
onSelectedChange(value); onSelectedChange(value);
} }
function onSelectedChange(value: any) { function onSelectedChange(value: any, isFirst: boolean = false) {
if (value) { if (value) {
const option = options.value.find(item => item.value == value); const option = options.value.find(item => item.value == value);
if (!isFirst) {
ctx.emit("change", value);
}
if (option) { if (option) {
ctx.emit("selected-change", option); ctx.emit("selected-change", option);
return; return;
@@ -18,6 +18,7 @@ export default {
subdomainConfirmTitle: "Subdomain Confirmation", subdomainConfirmTitle: "Subdomain Confirmation",
subdomainConfirmContent: "{domain} appears to be a subdomain. Only delegated subdomains and free second-level subdomains need to be maintained here. Otherwise certificate application may fail. Continue?", subdomainConfirmContent: "{domain} appears to be a subdomain. Only delegated subdomains and free second-level subdomains need to be maintained here. Otherwise certificate application may fail. Continue?",
importFromProvider: "Import from Domain Provider", importFromProvider: "Import from Domain Provider",
importFromResolveRecords: "Import from DNS Records",
syncExpirationDate: "Sync Domain Expiration Time", syncExpirationDate: "Sync Domain Expiration Time",
syncTaskSubmitted: "Sync task submitted", syncTaskSubmitted: "Sync task submitted",
syncExpirationProgress: "Sync Domain Expiration Progress", syncExpirationProgress: "Sync Domain Expiration Progress",
@@ -82,6 +82,7 @@ export default {
pipelineContent: "Pipeline Content", pipelineContent: "Pipeline Content",
scheduledTaskCount: "Scheduled Task Count", scheduledTaskCount: "Scheduled Task Count",
deployTaskCount: "Deployment Task Count", deployTaskCount: "Deployment Task Count",
certDomains: "Certificate Domains",
remainingValidity: "Remaining Validity", remainingValidity: "Remaining Validity",
effectiveTime: "Effective time", effectiveTime: "Effective time",
expiryTime: "Expiry Time", expiryTime: "Expiry Time",
@@ -41,6 +41,7 @@ export default {
pi: { pi: {
validTime: "Piepline Valid Time", validTime: "Piepline Valid Time",
validTimeHelper: "Not filled in means permanent validity", validTimeHelper: "Not filled in means permanent validity",
permanentValid: "Permanent",
}, },
types: { types: {
certApply: "Cert Apply", certApply: "Cert Apply",
@@ -4,7 +4,7 @@ export default {
"The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.", "The domain name configured here serves as a proxy for verifying other domains. When other domains apply for certificates, they map to this domain via CNAME for ownership verification. The advantage is that any domain can apply for a certificate this way without providing an AccessSecret.",
cnameLinkText: "CNAME principle and usage instructions", cnameLinkText: "CNAME principle and usage instructions",
cnameDomain: "CNAME Domain", cnameDomain: "CNAME Domain",
cnameDomainPlaceholder: "cname.handsfree.work", cnameDomainPlaceholder: "cname.handfree.work",
cnameDomainHelper: cnameDomainHelper:
"Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.", "Requires a domain registered with a DNS provider on the right (or you can transfer other domain DNS servers here).\nOnce the CNAME domain is set, it cannot be changed. It is recommended to use a first-level subdomain.",
cnameDomainPattern: "Domain name cannot contain *", cnameDomainPattern: "Domain name cannot contain *",
@@ -40,4 +40,8 @@ export default {
pluginManagement: "Plugin Management", pluginManagement: "Plugin Management",
pluginBetaWarning: "Custom plugins are in BETA and may have breaking changes in future", pluginBetaWarning: "Custom plugins are in BETA and may have breaking changes in future",
pleaseSelectRecord: "Please select records first", pleaseSelectRecord: "Please select records first",
clearRuntimeDeps: "Clear Runtime Deps Cache",
clearRuntimeDepsTooltip: "Restart the certd container after clearing, otherwise cached modules will not be reloaded",
clearRuntimeDepsConfirm: "Are you sure to clear the runtime dependencies cache? Please restart the certd container afterwards to ensure dependencies are reloaded.",
clearRuntimeDepsSuccess: "Runtime dependencies cache cleared successfully, please restart the certd container",
}; };
@@ -111,6 +111,9 @@ export default {
httpsProxyPlaceholder: "http://192.168.1.2:18010/", httpsProxyPlaceholder: "http://192.168.1.2:18010/",
saveThenTestTitle: "Save first, then click test", saveThenTestTitle: "Save first, then click test",
httpsProxyHelper: "Usually both proxies are the same, save first then test", httpsProxyHelper: "Usually both proxies are the same, save first then test",
noProxy: "Proxy Bypass",
noProxyPlaceholder: "localhost,127.0.0.1,.example.com,192.168.*",
noProxyHelper: "Configure NO_PROXY. Separate entries with commas, spaces, or line breaks; matched requests bypass the proxy. \nExample: localhost,127.0.0.1,.example.com,192.168.*",
dualStackNetwork: "Dual Stack Network", dualStackNetwork: "Dual Stack Network",
ipv4Priority: "IPv4 Priority", ipv4Priority: "IPv4 Priority",
ipv6Priority: "IPv6 Priority", ipv6Priority: "IPv6 Priority",
@@ -18,6 +18,7 @@ export default {
subdomainConfirmTitle: "子域名确认", subdomainConfirmTitle: "子域名确认",
subdomainConfirmContent: "检测到{domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?", subdomainConfirmContent: "检测到{domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?",
importFromProvider: "从域名提供商导入", importFromProvider: "从域名提供商导入",
importFromResolveRecords: "从解析记录导入",
syncExpirationDate: "同步域名过期时间", syncExpirationDate: "同步域名过期时间",
syncTaskSubmitted: "同步任务已提交", syncTaskSubmitted: "同步任务已提交",
syncExpirationProgress: "同步域名过期时间进度", syncExpirationProgress: "同步域名过期时间进度",
@@ -86,6 +86,7 @@ export default {
pipelineContent: "流水线内容", pipelineContent: "流水线内容",
scheduledTaskCount: "定时任务数", scheduledTaskCount: "定时任务数",
deployTaskCount: "部署任务数", deployTaskCount: "部署任务数",
certDomains: "证书域名",
remainingValidity: "到期剩余", remainingValidity: "到期剩余",
effectiveTime: "生效时间", effectiveTime: "生效时间",
expiryTime: "过期时间", expiryTime: "过期时间",
@@ -41,6 +41,7 @@ export default {
pi: { pi: {
validTime: "流水线有效期", validTime: "流水线有效期",
validTimeHelper: "不填则为永久有效", validTimeHelper: "不填则为永久有效",
permanentValid: "永久有效",
}, },
types: { types: {
certApply: "证书申请", certApply: "证书申请",
@@ -3,7 +3,7 @@ export default {
cnameDescription: "此处配置的域名作为其他域名校验的代理,当别的域名需要申请证书时,通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书,也无需填写AccessSecret。", cnameDescription: "此处配置的域名作为其他域名校验的代理,当别的域名需要申请证书时,通过CNAME映射到此域名上来验证所有权。好处是任何域名都可以通过此方式申请证书,也无需填写AccessSecret。",
cnameLinkText: "CNAME功能原理及使用说明", cnameLinkText: "CNAME功能原理及使用说明",
cnameDomain: "CNAME域名", cnameDomain: "CNAME域名",
cnameDomainPlaceholder: "cname.handsfree.work", cnameDomainPlaceholder: "cname.handfree.work",
cnameDomainHelper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名", cnameDomainHelper: "需要一个右边DNS提供商注册的域名(也可以将其他域名的dns服务器转移到这几家来)。\nCNAME域名一旦确定不可修改,建议使用一级子域名",
cnameDomainPattern: "域名不能使用星号", cnameDomainPattern: "域名不能使用星号",
cnameProviderSubdomain: "托管子域名", cnameProviderSubdomain: "托管子域名",
@@ -40,4 +40,8 @@ export default {
pluginManagement: "插件管理", pluginManagement: "插件管理",
pluginBetaWarning: "自定义插件处于BETA测试版,后续可能会有破坏性变更", pluginBetaWarning: "自定义插件处于BETA测试版,后续可能会有破坏性变更",
pleaseSelectRecord: "请先勾选记录", pleaseSelectRecord: "请先勾选记录",
clearRuntimeDeps: "清理第三方依赖缓存",
clearRuntimeDepsTooltip: "清除后需重启 certd 容器,否则已缓存模块不会重新读取",
clearRuntimeDepsConfirm: "确定要清理第三方依赖缓存吗?清理后请重启 certd 容器以确保重新加载依赖。",
clearRuntimeDepsSuccess: "第三方依赖缓存清理成功,请重启 certd 容器",
}; };
@@ -108,6 +108,9 @@ export default {
httpsProxyPlaceholder: "http://192.168.1.2:18010/", httpsProxyPlaceholder: "http://192.168.1.2:18010/",
saveThenTestTitle: "保存后,再点击测试", saveThenTestTitle: "保存后,再点击测试",
httpsProxyHelper: "一般这两个代理填一样的,保存后再测试", httpsProxyHelper: "一般这两个代理填一样的,保存后再测试",
noProxy: "代理排除",
noProxyPlaceholder: "localhost,127.0.0.1,.example.com,192.168.*",
noProxyHelper: "配置NO_PROXY,多个地址可用英文逗号、空格或换行分隔,命中的请求将不走代理\n例如:localhost,127.0.0.1,.example.com,192.168.*",
dualStackNetwork: "双栈网络", dualStackNetwork: "双栈网络",
ipv4Priority: "IPV4优先", ipv4Priority: "IPV4优先",
ipv6Priority: "IPV6优先", ipv6Priority: "IPV6优先",
@@ -107,6 +107,9 @@ function install(app: App, options: any = {}) {
scroll: { scroll: {
x: 960, x: 960,
}, },
rowSelection: {
fixed: "left",
},
size: "small", size: "small",
pagination: false, pagination: false,
onResizeColumn: (w: number, col: any) => { onResizeColumn: (w: number, col: any) => {
@@ -82,6 +82,7 @@ export const certdResources = [
isMenu: true, isMenu: true,
icon: "ion:duplicate-outline", icon: "ion:duplicate-outline",
auth: true, auth: true,
keepAlive: true,
}, },
}, },
{ {
@@ -282,6 +283,7 @@ export const certdResources = [
meta: { meta: {
icon: "ion:barcode-outline", icon: "ion:barcode-outline",
auth: true, auth: true,
keepAlive: true,
isMenu: true, isMenu: true,
}, },
}, },
@@ -350,6 +352,7 @@ export const certdResources = [
}, },
icon: "ion:gift-outline", icon: "ion:gift-outline",
auth: true, auth: true,
keepAlive: true,
}, },
}, },
{ {
@@ -389,7 +392,7 @@ export const certdResources = [
meta: { meta: {
show: () => { show: () => {
const settingStore = useSettingStore(); const settingStore = useSettingStore();
return settingStore.isComm; return settingStore.isInviteCommissionEnabled;
}, },
icon: "ion:gift-outline", icon: "ion:gift-outline",
auth: true, auth: true,
@@ -112,7 +112,7 @@ export const sysResources = [
}, },
{ {
title: "certd.sysResources.headerMenus", title: "certd.sysResources.headerMenus",
name: "HeaderMenus", name: "SettingsHeaderMenus",
path: "/sys/settings/header-menus", path: "/sys/settings/header-menus",
component: "/sys/settings/header-menus/index.vue", component: "/sys/settings/header-menus/index.vue",
meta: { meta: {
@@ -128,7 +128,7 @@ export const sysResources = [
}, },
{ {
title: "certd.sysResources.sysAccess", title: "certd.sysResources.sysAccess",
name: "SysAccess", name: "SysAccessManager",
path: "/sys/access", path: "/sys/access",
component: "/sys/access/index.vue", component: "/sys/access/index.vue",
meta: { meta: {
@@ -311,7 +311,7 @@ export const sysResources = [
}, },
icon: "ion:bag-check", icon: "ion:bag-check",
permission: "sys:settings:edit", permission: "sys:settings:edit",
keepAlive: true, keepAlive: false,
auth: true, auth: true,
}, },
}, },
@@ -105,6 +105,7 @@ export type InviteSetting = {
export type SysPrivateSetting = { export type SysPrivateSetting = {
httpProxy?: string; httpProxy?: string;
httpsProxy?: string; httpsProxy?: string;
noProxy?: string;
commonHeaders?: string; commonHeaders?: string;
reverseProxies?: any; reverseProxies?: any;
dnsResultOrder?: string; dnsResultOrder?: string;
@@ -15,6 +15,7 @@ import { resetAllStores, useAccessStore } from "/@/vben/stores";
import { useUserStore as vbenUserStore } from "/@/vben/stores/modules/user"; import { useUserStore as vbenUserStore } from "/@/vben/stores/modules/user";
import { request } from "/@/api/service"; import { request } from "/@/api/service";
import { inviteUtils } from "/@/utils/util.invite";
interface UserState { interface UserState {
userInfo: Nullable<UserInfoRes>; userInfo: Nullable<UserInfoRes>;
@@ -66,6 +67,9 @@ export const useUserStore = defineStore({
}, },
async register(user: RegisterReq) { async register(user: RegisterReq) {
await UserApi.register(user); await UserApi.register(user);
if (user.inviteCode) {
inviteUtils.clear();
}
notification.success({ notification.success({
message: "注册成功,请登录", message: "注册成功,请登录",
}); });
@@ -85,6 +89,9 @@ export const useUserStore = defineStore({
let loginRes: any = null; let loginRes: any = null;
if (loginType === "sms") { if (loginType === "sms") {
loginRes = await UserApi.loginBySms(params as SmsLoginReq); loginRes = await UserApi.loginBySms(params as SmsLoginReq);
if ((params as SmsLoginReq).inviteCode) {
inviteUtils.clear();
}
} else { } else {
loginRes = await UserApi.login(params as LoginReq); loginRes = await UserApi.login(params as LoginReq);
} }
@@ -136,12 +143,12 @@ export const useUserStore = defineStore({
} catch (e) { } catch (e) {
console.error("注销登录请求失败:", e); console.error("注销登录请求失败:", e);
} }
// 第三方登录注销
this.oauthLogout();
} }
this.resetState(); this.resetState();
resetAllStores(); resetAllStores();
// 第三方登录注销
await this.oauthLogout();
goLogin && router.push("/login"); goLogin && router.push("/login");
mitter.emit("app.logout"); mitter.emit("app.logout");
}, },
@@ -8,4 +8,45 @@
.vben-normal-menu__item.is-active { .vben-normal-menu__item.is-active {
background-color: #3b3b3b !important; background-color: #3b3b3b !important;
} }
.cd-table {
th,
td {
border-bottom: 1px solid #303030;
border-left: 1px solid #303030;
}
th {
background: #1f1f1f;
color: rgba(255, 255, 255, 0.85);
border-top: 1px solid #303030;
&:last-child {
border-right: 1px solid #303030;
}
}
td {
&:last-child {
border-right: 1px solid #303030;
}
}
td.position-sticky-right {
background-color: #141414;
}
.position-sticky-right::before {
background: #303030;
}
tr.hover-color:hover td {
background: #262626;
}
.status-active {
background: #1f3a23;
color: #81c784;
}
}
} }
@@ -0,0 +1,24 @@
import { onActivated, onMounted } from "vue";
/**
*
* - KeepAlive onActivated init
* - onMounted onActivated
*/
export function useMounted(init: () => void | Promise<void>) {
let activated = false;
onActivated(() => {
activated = true;
init();
});
onMounted(() => {
// 让 onActivated 有机会先执行;组件未被 KeepAlive 缓存时 onActivated 不会触发,由这里兜底。
setTimeout(() => {
if (!activated) {
init();
}
});
});
}
@@ -41,6 +41,10 @@ export const inviteUtils = {
} }
}, },
clear() {
localStorage.removeItem(INVITE_STORAGE_KEY);
},
captureFromLocation() { captureFromLocation() {
const hashQuery = window.location.hash?.split("?")[1] || ""; const hashQuery = window.location.hash?.split("?")[1] || "";
const search = window.location.search?.replace(/^\?/, "") || ""; const search = window.location.search?.replace(/^\?/, "") || "";
@@ -11,7 +11,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue"; import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { createAccessApi } from "/@/views/certd/access/api"; import { createAccessApi } from "/@/views/certd/access/api";
@@ -23,14 +24,7 @@ export default defineComponent({
const { t } = useI18n(); const { t } = useI18n();
const api = createAccessApi("user"); const api = createAccessApi("user");
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api, permission: { isProjectPermission: true } } }); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api, permission: { isProjectPermission: true } } });
useMounted(() => crudExpose.doRefresh());
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
return { return {
crudBinding, crudBinding,
@@ -11,7 +11,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue"; import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
import { createAddonApi } from "./api"; import { createAddonApi } from "./api";
@@ -26,14 +27,7 @@ export default defineComponent({
createCrudOptions, createCrudOptions,
context: { api, permission: { isProjectPermission: true } }, context: { api, permission: { isProjectPermission: true } },
}); });
useMounted(() => crudExpose.doRefresh());
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
return { return {
crudBinding, crudBinding,
@@ -11,7 +11,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onActivated, onMounted } from "vue"; import { defineComponent } from "vue";
import { useMounted } from "/@/use/use-mounted";
import { useFs } from "@fast-crud/fast-crud"; import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud"; import createCrudOptions from "./crud";
@@ -24,14 +25,7 @@ export default defineComponent({
permission: { isProjectPermission: true }, permission: { isProjectPermission: true },
}, },
}); });
useMounted(() => crudExpose.doRefresh());
//
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
return { return {
crudBinding, crudBinding,
@@ -109,7 +109,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
}, },
rowHandle: { rowHandle: {
fixed: "right", fixed: "right",
width: 120, width: 200,
buttons: { buttons: {
edit: { edit: {
click: ({ row }) => openForm(row), click: ({ row }) => openForm(row),

Some files were not shown because too many files have changed in this diff Show More