mirror of
https://github.com/certd/certd.git
synced 2026-10-14 18:52:24 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5558376bae | ||
|
|
2da560a21f | ||
|
|
bc9a0988a7 | ||
|
|
6a731f986e | ||
|
|
2cb4e551fa | ||
|
|
e5a1844852 | ||
|
|
b8f82ecb5e | ||
|
|
18583f6ce0 | ||
|
|
1468eb1ef2 | ||
|
|
8ab699a910 | ||
|
|
b173311468 | ||
|
|
522dcd8ffb | ||
|
|
6809a35177 | ||
|
|
bd82147496 | ||
|
|
a4224ab1df | ||
|
|
4c4dc8ca46 | ||
|
|
8165a5ff89 | ||
|
|
1017a38eb4 | ||
|
|
80374cae8a | ||
|
|
6339cdffa9 | ||
|
|
1042e599d9 | ||
|
|
1a4f1cb07d | ||
|
|
edb249c2ad | ||
|
|
e9921928df | ||
|
|
fa0c87dec3 | ||
|
|
ba64c2d147 | ||
|
|
cf008b9cff | ||
|
|
9d6f7ef0f2 | ||
|
|
0ddb1f69d2 | ||
|
|
4091abdfd7 | ||
|
|
27008cb44a | ||
|
|
319f555569 | ||
|
|
83a6aed625 | ||
|
|
f1b67049d1 | ||
|
|
8cbca5761e | ||
|
|
967846bef5 | ||
|
|
18b2d3ac20 | ||
|
|
7d22fe3d7d | ||
|
|
ee67b6c042 | ||
|
|
1cfa76683b | ||
|
|
4662e45e58 | ||
|
|
1fefbdc9ab | ||
|
|
1cb2a57c55 | ||
|
|
246ee83015 | ||
|
|
335ddfc7a5 | ||
|
|
5b500830a1 | ||
|
|
5f53b81c75 | ||
|
|
c8c269f612 | ||
|
|
743617dbda | ||
|
|
89806b828a | ||
|
|
2e530bfdb0 | ||
|
|
83495b3213 | ||
|
|
5108416904 | ||
|
|
5589da1822 | ||
|
|
21e5aed3f3 | ||
|
|
4a88f795e1 | ||
|
|
604fa5be63 | ||
|
|
7ed1be994f | ||
|
|
6cc74a1c0a | ||
|
|
167b303fae | ||
|
|
b91c9e4ea6 | ||
|
|
584b7b6f45 | ||
|
|
2312c444ef | ||
|
|
0373d019f8 | ||
|
|
fe09e75b80 | ||
|
|
432c9c6cc1 | ||
|
|
5fc1d55879 | ||
|
|
268cd6cc9c | ||
|
|
20ffe31638 | ||
|
|
02d30067ea | ||
|
|
0ce7808e08 | ||
|
|
d609ee2b7a | ||
|
|
083df61fcc | ||
|
|
a524988be6 | ||
|
|
8517a0b564 | ||
|
|
ec69b8f11b | ||
|
|
edda1b57f3 | ||
|
|
5a7766992d | ||
|
|
02dabe11db | ||
|
|
58024128d8 | ||
|
|
e44bf9d773 | ||
|
|
1114223bda | ||
|
|
55022e5fa6 | ||
|
|
ae732fb298 | ||
|
|
a6ef6996c3 | ||
|
|
97cf5e127b | ||
|
|
690df7373c | ||
|
|
9cc01e5da1 | ||
|
|
0a068a2746 |
@@ -400,6 +400,7 @@ export class OAuthDemoAccess extends BaseAccess {
|
||||
4. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
|
||||
6. **统一接口调用**:封装统一的 API 请求方法,避免重复编写错误处理逻辑。
|
||||
7. **旧版数据兼容**: 新增注解的插件参数,必须要考虑旧版数据兼容,比如新增一个deployType参数,有两种值:`default`和`custom`,需要在使用时判空,走旧版逻辑。
|
||||
|
||||
## 开发技巧
|
||||
|
||||
|
||||
@@ -454,3 +454,4 @@ new TencentDnsProvider();
|
||||
3. **记录结构**:定义适合对应云平台的记录数据结构,至少包含 id 字段用于删除记录。
|
||||
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`,参数文本化,不要传对象,否则会输出`[object Object]}`。
|
||||
5. **错误处理**:API 调用失败时应抛出明确的错误信息。
|
||||
6. **旧版数据兼容**: 新增插件参数,必须要考虑旧版数据兼容,比如新增一个deployType参数,有两种值:`default`和`custom`,需要在使用时判空,走旧版逻辑。
|
||||
|
||||
@@ -281,12 +281,12 @@ export class DemoTest extends AbstractTaskPlugin {
|
||||
return {
|
||||
value: item.siteName,
|
||||
label: item.siteName,
|
||||
domain: item.siteName,
|
||||
domain: item.siteName, //这里必须要包含domain 否则后面分组时候全部分配到未匹配中
|
||||
};
|
||||
});
|
||||
//将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
|
||||
return {
|
||||
list: optionsUtils.buildGroupOptions(options, this.certDomains),
|
||||
list: optionsUtils.buildGroupOptions(options, this.certDomains), //分组后的列表
|
||||
total: siteRes.length,
|
||||
};
|
||||
}
|
||||
@@ -641,6 +641,7 @@ new AliyunOSSDeploy();
|
||||
4. **日志输出**:使用 `this.logger` 输出日志,而不是 `console`,参数文本化,不要传对象,否则会输出`[object Object]}`。
|
||||
5. **错误处理**:执行过程中的错误应被捕获并记录。
|
||||
6. **授权获取**:使用 `this.getAccess(accessId)` 获取授权信息。
|
||||
7. **旧版数据兼容**: 新增@TaskInput注解的插件参数,必须要考虑旧版数据兼容,比如新增一个deployType参数,有两种值:`default`和`custom`,需要在使用时判空,走旧版逻辑。
|
||||
|
||||
## 部署逻辑注意事项
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ Certd 是可私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web
|
||||
- 先读本文,再按任务读取具体代码或技能文件。
|
||||
- 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'`。
|
||||
- 手工编辑或创建文件时优先使用 `apply_patch`。单个文件内有多处不连续改动时,拆成多个独立的 `*** Update File` 块,每块只改一处附近上下文;不要在同一个 update hunk 里强塞多个 `@@`。
|
||||
- 只有真正机械化的大批量替换、格式化或生成任务才考虑脚本/工具。若必须使用临时脚本,应放在临时目录并在同一个受控步骤内完成创建、执行、删除;不要把临时脚本落在仓库里跨多步工具调用执行。
|
||||
- 不要主动运行 `pnpm install`;缺依赖、TTY、网络导致安装或测试失败时,停止尝试并说明环境问题。
|
||||
- 优先沿用现有模块、插件、service、页面模式;不要为形式上的复用制造过度抽象。
|
||||
- 代码可读性优先于短写法。复杂条件、三元表达式、链式调用、内联对象和多层 helper 调用要拆成命名清晰的中间变量或小方法。
|
||||
@@ -134,6 +136,7 @@ Certd 是可私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web
|
||||
- 做前端任务时,先定位 `packages/ui/certd-client/src/views/certd` 下的页面,再找对应 `src/api`。
|
||||
- 不要运行前端 `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>`。
|
||||
- Vue 模板中需要用 JSX 动态渲染时,使用 `<script setup lang="tsx">` 编写;不要使用 `h()` 创建 VNode。
|
||||
- 列表管理、后台管理、记录查询、CRUD 表格页面优先使用 Fast Crud;开发或重构前读 `.trae/skills/fast-crud-page-dev/SKILL.md`。
|
||||
- 只有轻量只读展示、强交互自定义界面或既有页面模式明显不适合 Fast Crud 时,才手写 `a-table` / 自定义列表,并在回复中说明。
|
||||
- 内嵌 Fast Crud 时,外层必须有稳定高度或完整 `flex: 1; min-height: 0` 链路。
|
||||
@@ -210,3 +213,8 @@ Certd 是可私有化部署的 SSL/TLS 证书自动化管理平台,提供 Web
|
||||
- 后端业务数据、接口、实体、权限、迁移:改 `packages/ui/certd-server/src/modules` 与 `src/controller`。
|
||||
- 表单、列表、插件配置 UI:改 `packages/ui/certd-client/src/views/certd` 及对应 `src/api`。
|
||||
|
||||
## 其他注意事项
|
||||
|
||||
### 旧版数据兼容
|
||||
|
||||
- 新增插件参数时,必须要考虑旧版数据兼容,比如新增一个deployType参数,有两种值:`default`和`custom`,需要在使用时判空,走旧版逻辑。
|
||||
|
||||
@@ -3,6 +3,73 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 优化火山引擎dcdn,单个单个部署,规避超时问题 ([cf008b9](https://github.com/certd/certd/commit/cf008b9cff54542dc551fb159578dbd49006229c))
|
||||
* **pipeline:** 修复过期/损坏的安装锁导致的依赖安装失败 ([1017a38](https://github.com/certd/certd/commit/1017a38eb48d6778745d4905d9ec4f6067a261d8))
|
||||
* **ui:** 修复oauth设置页保存后未正确刷新的问题 ([8ab699a](https://github.com/certd/certd/commit/8ab699a9104b47e02a88ad764e995a495deb775e))
|
||||
* **unicloud:** 修复unicloud登录接口改版后请求失败的问题 ([18583f6](https://github.com/certd/certd/commit/18583f6ce025fa8d365fad5a8c9c6020f3bcfbae))
|
||||
|
||||
### Features
|
||||
|
||||
* 优化开放接口证书自动申请功能 ([6339cdf](https://github.com/certd/certd/commit/6339cdffa9f79505dada5617d8c7fcfacc419a2e))
|
||||
* **pipeline:** 新增流水线批量编辑、阶段宽度调整功能 ([e5a1844](https://github.com/certd/certd/commit/e5a18448522be88bc504a45a46ad599f3c39ca30))
|
||||
* **ssh:** ssh连接支持跳板机 ([2cb4e55](https://github.com/certd/certd/commit/2cb4e551fa7616f587a2d907f9b513f7df3a9957))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署到京东云CDN支持设置跳转类型 ([e992192](https://github.com/certd/certd/commit/e9921928df011c2435af73b68ebc3d773f13c16a))
|
||||
* 统一通知类型常量管理,新增开放接口通知能力 ([edb249c](https://github.com/certd/certd/commit/edb249c2ad3c173dbfd04d8d156f895a6706ffd1))
|
||||
* 优化宝塔报错提示 ([1cfa766](https://github.com/certd/certd/commit/1cfa76683b40da9e9198552f2226419d4afa86a3))
|
||||
* 优化第三方登录设置提示 ([1468eb1](https://github.com/certd/certd/commit/1468eb1ef292a9e21a2a43131b86b8acb3688307))
|
||||
* 优化第三方依赖安装失败日志输出 ([18b2d3a](https://github.com/certd/certd/commit/18b2d3ac2078d0c6636af897f8f66e4eb5ff832c))
|
||||
* 优化SqliteError: disk I/O error报错修复提示 ([7d22fe3](https://github.com/certd/certd/commit/7d22fe3d7d0de97bae2ceedd051a0c16a1cd90e3))
|
||||
* 证书成功通知增加有效期显示 ([f1b6704](https://github.com/certd/certd/commit/f1b67049d16ebcabe6d6d9b6cb53a8a2b7a9265c))
|
||||
* **cert-plugin:** 证书申请支持失败重试 ([0ddb1f6](https://github.com/certd/certd/commit/0ddb1f69d2a46b993e792862ce9952336d532f5b))
|
||||
* Improve English localization coverage ([#778](https://github.com/certd/certd/issues/778)) @MephistoJB ([bd82147](https://github.com/certd/certd/commit/bd821474969a22078b9d2e73518e765b8c9a26c7))
|
||||
* **install:** 补充迁移到PostgreSQL后,表自增序列从1开始的修复SQL脚本和说明 ([80374ca](https://github.com/certd/certd/commit/80374cae8a43fd17eb180381a093267e466a0bc2))
|
||||
* **plugin-ucloud:** ucloud us3部署插件增加region选择 ([8165a5f](https://github.com/certd/certd/commit/8165a5ff8986e759948c06f924d12e605b2ec8f6))
|
||||
* **tencent-clb:** 为腾讯云CLB部署插件支持远程选择输入 ([967846b](https://github.com/certd/certd/commit/967846bef590b76d27e472180b96372ff55cfb6f))
|
||||
* **ui:** 域名选择器支持点击复制域名 ([b173311](https://github.com/certd/certd/commit/b173311468d2c1f318d1333257c3ebcf916da071))
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复正常批量删除流水线报权限不足的bug ([5b50083](https://github.com/certd/certd/commit/5b500830a122c6c42dab054e57fed509050f94da))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 优化动态加载依赖镜像地址,多次重试 ([5f53b81](https://github.com/certd/certd/commit/5f53b81c75dd242b4260ac08cae14c6d1a08a883))
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||
* 修复dingtalk通知格式没有换行的bug ([7ed1be9](https://github.com/certd/certd/commit/7ed1be994f8b4b74cdeb38743060c912c027248b))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 给SQLITE_IOERR_WRITE增加友好报错提示,将certd:latest镜像改为certd:slim ([b91c9e4](https://github.com/certd/certd/commit/b91c9e4ea671cb359ef164e27864de1d66cba9d3))
|
||||
* 优化vke keubconfig获取方式,改成先查询,如果没有再创建临时config ([604fa5b](https://github.com/certd/certd/commit/604fa5be634d099d797bfee5c2b0f26ce0ac8461))
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复火山引擎查不到自定义源站域名的问题 ([02dabe1](https://github.com/certd/certd/commit/02dabe11db3e9b13ca4621ce9ddd2b808bfca390))
|
||||
* 修复火山引擎自定义源站域名查询不到的问题 ([e44bf9d](https://github.com/certd/certd/commit/e44bf9d77375d48ac7fd1582e69fae02dfd248fa))
|
||||
* **pipeline:** 重构运行时依赖加载逻辑,修复火山引擎DNS解析报runtimeDepsService未初始化的bug ([ec69b8f](https://github.com/certd/certd/commit/ec69b8f11bfd4b20991aef74a72a47182ca79a9d))
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **volcengine-alb:** 修复火山引擎ALB 默认证书部署类型会部署到扩展证书的问题 ([0a068a2](https://github.com/certd/certd/commit/0a068a274673e9768954e9f7367c267d44f3b530))
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -107,11 +107,13 @@ https://certd.handfree.work/
|
||||
|
||||
**镜像版本:**
|
||||
|
||||
| 版本标签 | 基础系统 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `latest` / `[version]` | Alpine Linux | 默认版本,镜像体积小 |
|
||||
| `slim` / `[version]-slim` | Debian slim | 基于glibc,dns解析兼容性好(可能需要配置security_opt -seccomp=unconfined) |
|
||||
| `armv7` / `[version]-armv7` | Alpine Linux | ARMv7 架构专用版本 |
|
||||
|
||||
| 标签 | 指定版本 | 基础系统 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `latest` | `[version]` | Alpine Linux | 默认版本,镜像体积小 |
|
||||
| `slim` | `[version]-slim` | Debian slim | 基于glibc,dns解析兼容性好(可能需要配置security_opt -seccomp=unconfined) |
|
||||
| `armv7` | `[version]-armv7` | Alpine Linux | ARMv7 架构专用版本 |
|
||||
|
||||
|
||||
**镜像地址:**
|
||||
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复正常批量删除流水线报权限不足的bug ([5b50083](https://github.com/certd/certd/commit/5b500830a122c6c42dab054e57fed509050f94da))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 优化动态加载依赖镜像地址,多次重试 ([5f53b81](https://github.com/certd/certd/commit/5f53b81c75dd242b4260ac08cae14c6d1a08a883))
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||
* 修复dingtalk通知格式没有换行的bug ([7ed1be9](https://github.com/certd/certd/commit/7ed1be994f8b4b74cdeb38743060c912c027248b))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 给SQLITE_IOERR_WRITE增加友好报错提示,将certd:latest镜像改为certd:slim ([b91c9e4](https://github.com/certd/certd/commit/b91c9e4ea671cb359ef164e27864de1d66cba9d3))
|
||||
* 优化vke keubconfig获取方式,改成先查询,如果没有再创建临时config ([604fa5b](https://github.com/certd/certd/commit/604fa5be634d099d797bfee5c2b0f26ce0ac8461))
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复火山引擎查不到自定义源站域名的问题 ([02dabe1](https://github.com/certd/certd/commit/02dabe11db3e9b13ca4621ce9ddd2b808bfca390))
|
||||
* 修复火山引擎自定义源站域名查询不到的问题 ([e44bf9d](https://github.com/certd/certd/commit/e44bf9d77375d48ac7fd1582e69fae02dfd248fa))
|
||||
* **pipeline:** 重构运行时依赖加载逻辑,修复火山引擎DNS解析报runtimeDepsService未初始化的bug ([ec69b8f](https://github.com/certd/certd/commit/ec69b8f11bfd4b20991aef74a72a47182ca79a9d))
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **volcengine-alb:** 修复火山引擎ALB 默认证书部署类型会部署到扩展证书的问题 ([0a068a2](https://github.com/certd/certd/commit/0a068a274673e9768954e9f7367c267d44f3b530))
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -103,9 +103,14 @@ docker-compose up -d
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
> 重要: 建议Fetch大小改成1000,避免内存溢出
|
||||
|
||||
下一步、下一步,直到数据加载设置,勾选`在加载前截断目标表`(此选项很重要,并且会清空mysql certd数据库中的数据)
|
||||

|
||||
> 重要: 在插入行后做提交,也改成1000,避免内存溢出
|
||||
|
||||
#### 5. 导入完成
|
||||
|
||||
@@ -115,4 +120,58 @@ docker-compose up -d
|
||||
|
||||
访问MySQL版本测试,数据已成功迁移
|
||||
|
||||
确认没有问题之后,删除旧版certd
|
||||
确认没有问题之后,删除旧版certd
|
||||
|
||||
|
||||
## 三、问题
|
||||
|
||||
### 1. 迁移到PG之后,所有表的Sequence会从1开始
|
||||
|
||||
执行如下sql 语句,批量将所有表的id自增设置到当前数据的最大值
|
||||
```sql
|
||||
DO $$
|
||||
DECLARE
|
||||
rec RECORD;
|
||||
seq_name TEXT;
|
||||
max_id BIGINT;
|
||||
BEGIN
|
||||
-- 遍历所有用户表(排除系统schema)
|
||||
FOR rec IN
|
||||
SELECT table_schema, table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_type = 'BASE TABLE'
|
||||
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||
LOOP
|
||||
-- 检查表是否存在名为 'id' 的列
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = rec.table_schema
|
||||
AND table_name = rec.table_name
|
||||
AND column_name = 'id'
|
||||
) THEN
|
||||
-- 获取该列关联的序列名(如果是 serial 或 identity 列)
|
||||
seq_name := pg_get_serial_sequence(
|
||||
quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name),
|
||||
'id'
|
||||
);
|
||||
|
||||
IF seq_name IS NOT NULL THEN
|
||||
-- 查询当前表中 id 的最大值
|
||||
EXECUTE format('SELECT MAX(id) FROM %I.%I', rec.table_schema, rec.table_name) INTO max_id;
|
||||
|
||||
IF max_id IS NULL THEN
|
||||
-- 表为空,将序列重置为初始值 1(下次调用 nextval 返回 1)
|
||||
PERFORM setval(seq_name, 1, false);
|
||||
RAISE NOTICE '表 %.% 为空,序列 % 已重置为 1', rec.table_schema, rec.table_name, seq_name;
|
||||
ELSE
|
||||
-- 将序列当前值设置为最大值,下次调用 nextval 返回 max_id + 1
|
||||
PERFORM setval(seq_name, max_id, true);
|
||||
RAISE NOTICE '表 %.% 的序列 % 已设置为 %', rec.table_schema, rec.table_name, seq_name, max_id;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
```
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -115,7 +115,7 @@
|
||||
|
||||
| 序号 | 名称 | 说明 |
|
||||
|-----|-----|-----|
|
||||
| 1.| **腾讯云-删除即将过期证书** | 仅删除未使用的证书 |
|
||||
| 1.| **腾讯云-删除即将过期证书** | 仅删除即将过期且未使用的证书 |
|
||||
| 2.| **腾讯云-部署证书到任意云资源** | 支持负载均衡、CDN、DDoS、直播、点播、Web应用防火墙、API网关、TEO、容器服务、对象存储、轻应用服务器、云原生微服务、云开发 |
|
||||
| 3.| **腾讯云-部署到CDN(废弃)** | 已废弃,请使用v2版 |
|
||||
| 4.| **腾讯云-部署到CDN-v2** | 推荐使用,支持CDN域名以及COS加速域名 |
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
| 1.| **阿里ESA** | 阿里ESA DNS解析 |
|
||||
| 2.| **阿里云** | 阿里云DNS解析提供商 |
|
||||
| 3.| **AWS Route53** | AWS Route53 DNS解析提供商 |
|
||||
| 4.| **Azure DNS** | Azure DNS 解析提供商 |
|
||||
| 5.| **火山引擎** | 火山引擎DNS解析提供商 |
|
||||
| 6.| **京东云** | 京东云DNS解析提供商 |
|
||||
| 7.| **新网(代理方式)** | 新网域名解析(代理方式) |
|
||||
| 8.| **新网** | 新网域名解析 |
|
||||
| 9.| **BIND9 DNS** | 通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录 |
|
||||
| 10.| **cloudflare** | cloudflare dns provider |
|
||||
| 11.| **dns.la** | dns.la |
|
||||
| 12.| **Dynadot** | Dynadot DNS提供商 |
|
||||
| 13.| **godaddy** | GoDaddy |
|
||||
| 14.| **HiPM DNSMgr** | HiPM DNSMgr DNS 解析提供商 |
|
||||
| 15.| **华为云** | 华为云DNS解析提供商 |
|
||||
| 16.| **namesilo** | namesilo dns provider |
|
||||
| 17.| **雨云** | 雨云DNS解析提供商 |
|
||||
| 18.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器 |
|
||||
| 19.| **腾讯云** | 腾讯云域名DNS解析提供者 |
|
||||
| 20.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 |
|
||||
| 4.| **腾讯云** | 腾讯云域名DNS解析提供者 |
|
||||
| 5.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 |
|
||||
| 6.| **Azure DNS** | Azure DNS 解析提供商 |
|
||||
| 7.| **火山引擎** | 火山引擎DNS解析提供商 |
|
||||
| 8.| **京东云** | 京东云DNS解析提供商 |
|
||||
| 9.| **新网(代理方式)** | 新网域名解析(代理方式) |
|
||||
| 10.| **新网** | 新网域名解析 |
|
||||
| 11.| **BIND9 DNS** | 通过 SSH 连接到 BIND9 服务器,使用 nsupdate 命令管理 DNS 记录 |
|
||||
| 12.| **cloudflare** | cloudflare dns provider |
|
||||
| 13.| **dns.la** | dns.la |
|
||||
| 14.| **Dynadot** | Dynadot DNS提供商 |
|
||||
| 15.| **godaddy** | GoDaddy |
|
||||
| 16.| **HiPM DNSMgr** | HiPM DNSMgr DNS 解析提供商 |
|
||||
| 17.| **华为云** | 华为云DNS解析提供商 |
|
||||
| 18.| **namesilo** | namesilo dns provider |
|
||||
| 19.| **雨云** | 雨云DNS解析提供商 |
|
||||
| 20.| **Technitium DNS Server** | Technitium DNS Server 自建DNS服务器 |
|
||||
| 21.| **西部数码** | west dns provider |
|
||||
| 22.| **Google Cloud DNS** | Google Cloud DNS提供商 |
|
||||
| 23.| **Dns提供商Demo** | dns provider示例 |
|
||||
|
||||
+1
-1
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.42.2"
|
||||
"version": "1.43.0"
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"test:unit": "cross-env NODE_ENV=unittest pnpm -r --workspace-concurrency=1 run test:unit",
|
||||
"pub": "echo 1",
|
||||
"dev": "pnpm run -r --parallel compile ",
|
||||
"lint_all": "pnpm run -r --parallel lint ",
|
||||
"pub_all": "node ./scripts/pub-all.js",
|
||||
"release": "time /t >trigger/release.trigger && git add trigger/release.trigger && git commit -m \"build: release\" && git push",
|
||||
"publish_to_atomgit": "node --experimental-json-modules ./scripts/publish-atomgit.js",
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
"ecmaVersion": 2022,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:prettier/recommended",
|
||||
"prettier"
|
||||
@@ -36,5 +32,9 @@
|
||||
"ignoreUrls": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/publishlab/node-acme-client/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.42.6](https://github.com/publishlab/node-acme-client/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.42.5](https://github.com/publishlab/node-acme-client/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.42.4](https://github.com/publishlab/node-acme-client/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.42.3](https://github.com/publishlab/node-acme-client/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.42.2](https://github.com/publishlab/node-acme-client/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"type": "module",
|
||||
"module": "./dist/index.js",
|
||||
"main": "./dist/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.42.2",
|
||||
"@certd/basic": "^1.43.0",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.9.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"scripts": {
|
||||
"before-build": "node -e \"const fs=require('fs');fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('tsconfig.tsbuildinfo',{force:true});\"",
|
||||
"build": "npm run before-build && tsc -p tsconfig.build.json --skipLibCheck",
|
||||
"lint": "eslint \"src/**/*.ts\" \"types/**/*.ts\"",
|
||||
"lint": "eslint --fix \"src/**/*.ts\" \"types/**/*.ts\"",
|
||||
"lint-types": "tsd --files \"types/index.test-d.ts\"",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "mocha -t 60000 \"test/setup.js\" \"test/**/*.spec.js\"",
|
||||
@@ -75,5 +75,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
@@ -1 +1 @@
|
||||
19:56
|
||||
23:03
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -17,7 +17,7 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"async-lock": "^1.4.1",
|
||||
@@ -54,5 +54,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ export class LocalCache<V = any> {
|
||||
cache: Map<string, { value: V; expiresAt: number }>;
|
||||
constructor(opts: { clearInterval?: number } = {}) {
|
||||
this.cache = new Map();
|
||||
const intervalId = setInterval(() => {
|
||||
this.clearExpires();
|
||||
}, opts.clearInterval ?? 5 * 60 * 1000);
|
||||
const intervalId = setInterval(
|
||||
() => {
|
||||
this.clearExpires();
|
||||
},
|
||||
opts.clearInterval ?? 5 * 60 * 1000
|
||||
);
|
||||
intervalId.unref?.();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function isDev() {
|
||||
const nodeEnv = process.env.NODE_ENV || 'dev';
|
||||
return nodeEnv === 'development' || nodeEnv.includes('local') || nodeEnv.startsWith('dev');
|
||||
const nodeEnv = process.env.NODE_ENV || "dev";
|
||||
return nodeEnv === "development" || nodeEnv.includes("local") || nodeEnv.startsWith("dev");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import fs from 'fs';
|
||||
import fs from "fs";
|
||||
function getFileRootDir(rootDir?: string) {
|
||||
if (rootDir == null) {
|
||||
const userHome = process.env.HOME || process.env.USERPROFILE;
|
||||
rootDir = userHome + '/.certd/storage/';
|
||||
rootDir = userHome + "/.certd/storage/";
|
||||
}
|
||||
|
||||
if (!fs.existsSync(rootDir)) {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
import mitt from 'mitt';
|
||||
import mitt from "mitt";
|
||||
export const mitter = mitt();
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,47 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **pipeline:** 修复过期/损坏的安装锁导致的依赖安装失败 ([1017a38](https://github.com/certd/certd/commit/1017a38eb48d6778745d4905d9ec4f6067a261d8))
|
||||
|
||||
### Features
|
||||
|
||||
* **pipeline:** 新增流水线批量编辑、阶段宽度调整功能 ([e5a1844](https://github.com/certd/certd/commit/e5a18448522be88bc504a45a46ad599f3c39ca30))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 统一通知类型常量管理,新增开放接口通知能力 ([edb249c](https://github.com/certd/certd/commit/edb249c2ad3c173dbfd04d8d156f895a6706ffd1))
|
||||
* 优化第三方依赖安装失败日志输出 ([18b2d3a](https://github.com/certd/certd/commit/18b2d3ac2078d0c6636af897f8f66e4eb5ff832c))
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 优化动态加载依赖镜像地址,多次重试 ([5f53b81](https://github.com/certd/certd/commit/5f53b81c75dd242b4260ac08cae14c6d1a08a883))
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 优化vke keubconfig获取方式,改成先查询,如果没有再创建临时config ([604fa5b](https://github.com/certd/certd/commit/604fa5be634d099d797bfee5c2b0f26ce0ac8461))
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **pipeline:** 重构运行时依赖加载逻辑,修复火山引擎DNS解析报runtimeDepsService未初始化的bug ([ec69b8f](https://github.com/certd/certd/commit/ec69b8f11bfd4b20991aef74a72a47182ca79a9d))
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -18,11 +18,11 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.42.2",
|
||||
"@certd/plus-core": "^1.42.2",
|
||||
"@certd/basic": "^1.43.0",
|
||||
"@certd/plus-core": "^1.43.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.2.2"
|
||||
@@ -51,5 +51,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FormItemProps } from "../dt/index.js";
|
||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||
import * as _ from "lodash-es";
|
||||
import { PluginRequestHandleReq } from "../plugin/index.js";
|
||||
import { IRuntimeDepsService, IServiceGetter } from "../service/index.js";
|
||||
import { IServiceGetter, getRuntimeDepsService } from "../service/index.js";
|
||||
|
||||
// export type AccessRequestHandleReqInput<T = any> = {
|
||||
// id?: number;
|
||||
@@ -48,23 +48,13 @@ export type AccessContext = {
|
||||
|
||||
export abstract class BaseAccess implements IAccess {
|
||||
ctx!: AccessContext;
|
||||
runtimeDepsService?: IRuntimeDepsService;
|
||||
|
||||
async importRuntime(specifier: string) {
|
||||
if (!this.runtimeDepsService) {
|
||||
throw new Error("runtimeDepsService 未初始化");
|
||||
}
|
||||
return await this.runtimeDepsService.importRuntime(specifier, this.ctx.logger);
|
||||
return await getRuntimeDepsService().importRuntime(specifier, this.ctx.logger);
|
||||
}
|
||||
|
||||
async setCtx(ctx: AccessContext) {
|
||||
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) {
|
||||
|
||||
@@ -47,7 +47,7 @@ export function AccessInput(input?: AccessInputDefine): PropertyDecorator {
|
||||
};
|
||||
}
|
||||
|
||||
export async function newAccess(type: string, input: any, accessService: IAccessService, ctx?: AccessContext) {
|
||||
export async function newAccess(type: string, input: any, accessService: IAccessService, ctx: AccessContext) {
|
||||
const register = accessRegistry.get(type);
|
||||
if (register == null) {
|
||||
throw new Error(`access ${type} not found`);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Decorator } from "../decorator/index.js";
|
||||
import { ICnameProxyService, IEmailService, IPluginConfigService, IServiceGetter, IUrlService } from "../service/index.js";
|
||||
import { FileStore } from "./file-store.js";
|
||||
import { cloneDeep, forEach, merge } from "lodash-es";
|
||||
import { INotificationService } from "../notification/index.js";
|
||||
import { INotificationService, PipelineNotificationTypes } from "../notification/index.js";
|
||||
import { taskEmitterCreate } from "../service/emit.js";
|
||||
import { RunnableError } from "./exceptions.js";
|
||||
|
||||
@@ -480,7 +480,7 @@ export class Executor {
|
||||
if (notification.type === "email" && notification.options?.receivers) {
|
||||
try {
|
||||
await this.options.emailService?.sendByTemplate({
|
||||
type: "pipelineResult",
|
||||
type: PipelineNotificationTypes.PipelineResult,
|
||||
data: templateData,
|
||||
receivers: notification.options?.receivers,
|
||||
});
|
||||
@@ -496,7 +496,7 @@ export class Executor {
|
||||
useEmail: false,
|
||||
logger: this.logger,
|
||||
body: {
|
||||
notificationType: "pipelineResult",
|
||||
notificationType: PipelineNotificationTypes.PipelineResult,
|
||||
title: subject,
|
||||
content,
|
||||
userId: this.pipeline.userId,
|
||||
|
||||
@@ -47,6 +47,10 @@ export type Stage = Runnable & {
|
||||
concurrency: ConcurrencyStrategy;
|
||||
next: NextStrategy;
|
||||
maxTaskCount?: number;
|
||||
style?: {
|
||||
width?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
|
||||
export type Trigger = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Registrable } from "../registry/index.js";
|
||||
import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js";
|
||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||
import * as _ from "lodash-es";
|
||||
import { IEmailService, IRuntimeDepsService, IServiceGetter } from "../service/index.js";
|
||||
import { IEmailService, IServiceGetter, getRuntimeDepsService } from "../service/index.js";
|
||||
|
||||
export type NotificationBody = {
|
||||
userId?: number;
|
||||
@@ -89,16 +89,16 @@ export abstract class BaseNotification implements INotification {
|
||||
ctx!: NotificationContext;
|
||||
http!: HttpClient;
|
||||
logger!: ILogger;
|
||||
runtimeDepsService?: IRuntimeDepsService;
|
||||
|
||||
async importRuntime(specifier: string) {
|
||||
if (!this.runtimeDepsService) {
|
||||
return await import(specifier);
|
||||
}
|
||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
||||
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||
}
|
||||
|
||||
async doSend(body: NotificationBody) {
|
||||
if (body.content) {
|
||||
const content = body.content?.replace(/\n/g, " \n");
|
||||
body.content = content;
|
||||
}
|
||||
return await this.send(body);
|
||||
}
|
||||
abstract send(body: NotificationBody): Promise<void>;
|
||||
@@ -109,12 +109,6 @@ export abstract class BaseNotification implements INotification {
|
||||
this.ctx = ctx;
|
||||
this.http = ctx.http;
|
||||
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) => {
|
||||
this.define = define;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/// <reference types="mocha" />
|
||||
/// <reference types="node" />
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { PipelineNotificationTypes } from "./constants.js";
|
||||
|
||||
describe("PipelineNotificationTypes", () => {
|
||||
it("contains the pipeline result notification type", () => {
|
||||
assert.equal(PipelineNotificationTypes.PipelineResult, "pipelineResult");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
/** 流水线核心使用的通知类型。业务层通知类型由 @certd/lib-server 扩展。 */
|
||||
export const PipelineNotificationTypes = {
|
||||
PipelineResult: "pipelineResult",
|
||||
} as const;
|
||||
|
||||
export type PipelineNotificationType = (typeof PipelineNotificationTypes)[keyof typeof PipelineNotificationTypes];
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./api.js";
|
||||
export * from "./constants.js";
|
||||
export * from "./registry.js";
|
||||
export * from "./decorator.js";
|
||||
|
||||
@@ -10,7 +10,7 @@ import { INotificationService } from "../notification/index.js";
|
||||
import { Registrable } from "../registry/index.js";
|
||||
import { IPluginConfigService } from "../service/config.js";
|
||||
import { TaskEmitter } from "../service/emit.js";
|
||||
import { ICnameProxyService, IEmailService, IRuntimeDepsService, IServiceGetter, IUrlService } from "../service/index.js";
|
||||
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService, getRuntimeDepsService } from "../service/index.js";
|
||||
|
||||
export type PluginRequestHandleReq<T = any> = {
|
||||
typeName: string;
|
||||
@@ -76,7 +76,7 @@ export type ITaskPlugin = {
|
||||
execute(): Promise<void | string>;
|
||||
onRequest(req: PluginRequestHandleReq<any>): Promise<any>;
|
||||
setCtx(ctx: TaskInstanceContext): Promise<void>;
|
||||
importRuntime?(specifier: string): Promise<any>;
|
||||
importRuntime(specifier: string): Promise<any>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
@@ -150,13 +150,9 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
logger!: ILogger;
|
||||
http!: HttpClient;
|
||||
accessService!: IAccessService;
|
||||
runtimeDepsService!: IRuntimeDepsService;
|
||||
|
||||
async importRuntime(specifier: string) {
|
||||
if (!this.runtimeDepsService) {
|
||||
throw new Error("runtimeDepsService 未初始化");
|
||||
}
|
||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
||||
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||
}
|
||||
|
||||
clearLastStatus() {
|
||||
@@ -178,12 +174,6 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
|
||||
this.logger = ctx.logger;
|
||||
this.accessService = ctx.accessService;
|
||||
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
|
||||
// @ts-ignore
|
||||
if (this.cert && this.cert.crt && this.cert.key) {
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
import assert from "assert";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { RuntimeDepsService, NpmRegistryResolver } from "./runtime.js";
|
||||
|
||||
describe("RuntimeDepsService", () => {
|
||||
it("builds a runtime package manifest in the target directory", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-"));
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return ["https://registry.npmmirror.com"];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[]) {
|
||||
assert.equal(command, "pnpm");
|
||||
assert.equal(args[0], "install");
|
||||
assert.ok(args.includes("--ignore-workspace"));
|
||||
assert.ok(args.includes("--no-frozen-lockfile"));
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
const result = await service.ensureDependencies({ dependencies: { foo: "^1.0.0" } });
|
||||
assert.equal(result.registryUrl, "https://registry.npmmirror.com");
|
||||
assert.ok(fs.existsSync(path.join(rootDir, "package.json")));
|
||||
});
|
||||
|
||||
it("installs direct dependency maps without plugin metadata", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-direct-"));
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[]) {
|
||||
assert.equal(command, "pnpm");
|
||||
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
await service.ensureDependencies({ dependencies: { directPkg: "^1.0.0" } });
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||
assert.deepEqual(manifest.dependencies, { directPkg: "^1.0.0" });
|
||||
});
|
||||
|
||||
it("imports from runtime node_modules without installing", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-import-"));
|
||||
const packageDir = path.join(rootDir, "node_modules", "runtime-only");
|
||||
fs.mkdirSync(packageDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(rootDir, "package.json"), JSON.stringify({ name: "runtime-root", type: "module" }), "utf8");
|
||||
fs.writeFileSync(path.join(packageDir, "package.json"), JSON.stringify({ name: "runtime-only", type: "module", main: "index.js" }), "utf8");
|
||||
fs.writeFileSync(path.join(packageDir, "index.js"), "export const value = 42;\n", "utf8");
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.commandRunner = {
|
||||
async run() {
|
||||
throw new Error("install should not run");
|
||||
},
|
||||
} as any;
|
||||
const mod = await service.importRuntime("runtime-only");
|
||||
assert.equal(mod.value, 42);
|
||||
});
|
||||
|
||||
it("installs configured lazy dependency when import target is missing", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-"));
|
||||
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "lazy-pkg": "^1.2.3" } }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[]) {
|
||||
assert.equal(command, "pnpm");
|
||||
const packageDir = path.join(rootDir, "node_modules", "lazy-pkg", "sub");
|
||||
fs.mkdirSync(packageDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(rootDir, "node_modules", "lazy-pkg", "package.json"), JSON.stringify({ name: "lazy-pkg", type: "module" }), "utf8");
|
||||
fs.writeFileSync(path.join(packageDir, "entry.js"), "export const value = 7;\n", "utf8");
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
const mod = await service.importRuntime("lazy-pkg/sub/entry.js");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||
assert.deepEqual(manifest.dependencies, { "lazy-pkg": "^1.2.3" });
|
||||
assert.equal(mod.value, 7);
|
||||
});
|
||||
|
||||
it("reports lazy dependency installation failure without falling back to project dependencies", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-failed-"));
|
||||
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "failed-pkg": "^1.2.3" } }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[]) {
|
||||
assert.equal(command, "pnpm");
|
||||
return { stdout: "", stderr: "package download failed", code: 1 };
|
||||
},
|
||||
} as any;
|
||||
|
||||
await assert.rejects(() => service.importRuntime("failed-pkg/sub/entry.js"), /动态依赖安装失败: failed-pkg: 动态依赖安装失败: package download failed/);
|
||||
});
|
||||
|
||||
it("resolves scoped package names for lazy imports", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-scoped-"));
|
||||
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { "@scope/lazy": "^2.0.0" } }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[]) {
|
||||
assert.equal(command, "pnpm");
|
||||
const packageDir = path.join(rootDir, "node_modules", "@scope", "lazy", "dist");
|
||||
fs.mkdirSync(packageDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(rootDir, "node_modules", "@scope", "lazy", "package.json"), JSON.stringify({ name: "@scope/lazy", type: "module" }), "utf8");
|
||||
fs.writeFileSync(path.join(packageDir, "index.js"), "export const scoped = true;\n", "utf8");
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
const mod = await service.importRuntime("@scope/lazy/dist/index.js");
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||
assert.deepEqual(manifest.dependencies, { "@scope/lazy": "^2.0.0" });
|
||||
assert.equal(mod.scoped, true);
|
||||
});
|
||||
|
||||
it("reports missing lazy dependency configuration", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-lazy-missing-"));
|
||||
const service = new RuntimeDepsService({ rootDir, lazyDependencies: {} }, null);
|
||||
await assert.rejects(() => service.importRuntime("missing-pkg/sub.js"), /未配置懒加载版本: missing-pkg/);
|
||||
});
|
||||
|
||||
it("rejects lazy dependency installation when disabled", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-disabled-"));
|
||||
const service = new RuntimeDepsService({ rootDir, enabled: false, lazyDependencies: { "lazy-pkg": "^1.0.0" } }, null);
|
||||
service.commandRunner = {
|
||||
async run() {
|
||||
throw new Error("disabled runtime dependencies should not install packages");
|
||||
},
|
||||
} as any;
|
||||
|
||||
await assert.rejects(() => service.importRuntime("lazy-pkg/index.js"), /动态安装依赖未开启/);
|
||||
});
|
||||
|
||||
it("prefers project dependencies over lazy dependency installation", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-project-fallback-"));
|
||||
const service = new RuntimeDepsService({ rootDir, lazyDependencies: { dayjs: "^1.11.0" } }, null);
|
||||
service.commandRunner = {
|
||||
async run() {
|
||||
throw new Error("project dependency should not trigger installation");
|
||||
},
|
||||
} as any;
|
||||
const mod = await service.importRuntime("dayjs");
|
||||
assert.equal(typeof mod.default, "function");
|
||||
});
|
||||
|
||||
it("rejects a second installation while the first installation is running", async () => {
|
||||
const rootDir = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-concurrent-")), ".runtime-deps");
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
|
||||
let finishInstall: () => void;
|
||||
const installFinished = new Promise<void>(resolve => {
|
||||
finishInstall = resolve;
|
||||
});
|
||||
service.commandRunner = {
|
||||
async run() {
|
||||
await installFinished;
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
|
||||
const firstInstall = service.ensureDependencies({ dependencies: { first: "^1.0.0" } });
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
await assert.rejects(() => service.ensureDependencies({ dependencies: { second: "^1.0.0" } }), /动态安装依赖正在执行中/);
|
||||
await assert.rejects(() => service.clearRuntimeDeps(), /仍有依赖正在安装/);
|
||||
finishInstall!();
|
||||
await firstInstall;
|
||||
});
|
||||
|
||||
it("releases the installation lock after an installation failure", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-retry-"));
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
|
||||
let installCount = 0;
|
||||
service.commandRunner = {
|
||||
async run() {
|
||||
installCount += 1;
|
||||
if (installCount === 1) {
|
||||
return { stdout: "", stderr: "installation timeout", code: 1 };
|
||||
}
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
|
||||
await assert.rejects(() => service.ensureDependencies({ dependencies: { first: "^1.0.0" } }), /动态依赖安装失败/);
|
||||
await service.ensureDependencies({ dependencies: { second: "^1.0.0" } });
|
||||
assert.equal(installCount, 2);
|
||||
});
|
||||
|
||||
it("keeps previously installed dependencies when adding a later package", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-merge-"));
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[]) {
|
||||
assert.equal(command, "pnpm");
|
||||
if (args.includes("--version")) {
|
||||
return { stdout: "9.1.0\n", stderr: "", code: 0 };
|
||||
}
|
||||
fs.mkdirSync(path.join(rootDir, "node_modules"), { recursive: true });
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
await service.ensureDependencies({ dependencies: { foo: "^1.0.0" } });
|
||||
await service.ensureDependencies({ dependencies: { bar: "^2.0.0" } });
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
||||
assert.deepEqual(manifest.dependencies, { foo: "^1.0.0", bar: "^2.0.0" });
|
||||
});
|
||||
|
||||
it("does not pass node debugger options to pnpm child process", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-deps-env-"));
|
||||
const oldNodeOptions = process.env.NODE_OPTIONS;
|
||||
const oldInspectorOptions = process.env.VSCODE_INSPECTOR_OPTIONS;
|
||||
process.env.NODE_OPTIONS = "--inspect=127.0.0.1:9229 --max-old-space-size=4096";
|
||||
process.env.VSCODE_INSPECTOR_OPTIONS = '{"inspectorIpc":"test"}';
|
||||
try {
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
service.registryResolver = {
|
||||
async resolveOrdered() {
|
||||
return [""];
|
||||
},
|
||||
} as any;
|
||||
service.commandRunner = {
|
||||
async run(command: string, args: string[], options: { env?: NodeJS.ProcessEnv }) {
|
||||
assert.equal(options.env?.NODE_OPTIONS, "--max-old-space-size=4096");
|
||||
assert.equal(options.env?.VSCODE_INSPECTOR_OPTIONS, undefined);
|
||||
assert.equal(options.env?.CI, "true");
|
||||
assert.equal(options.env?.pnpm_config_confirm_modules_purge, "false");
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
},
|
||||
} as any;
|
||||
await service.ensureDependencies({ dependencies: { foo: "^1.0.0" } });
|
||||
} finally {
|
||||
if (oldNodeOptions == null) {
|
||||
delete process.env.NODE_OPTIONS;
|
||||
} else {
|
||||
process.env.NODE_OPTIONS = oldNodeOptions;
|
||||
}
|
||||
if (oldInspectorOptions == null) {
|
||||
delete process.env.VSCODE_INSPECTOR_OPTIONS;
|
||||
} else {
|
||||
process.env.VSCODE_INSPECTOR_OPTIONS = oldInspectorOptions;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects clearing unexpected runtime dependency path", async () => {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "certd-runtime-clear-invalid-"));
|
||||
const service = new RuntimeDepsService({ rootDir }, null);
|
||||
await assert.rejects(() => service.clearRuntimeDeps(), /动态依赖目录配置异常/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NpmRegistryResolver", () => {
|
||||
it("uses fixed registry without probing", async () => {
|
||||
const resolver = new NpmRegistryResolver({
|
||||
mode: "fixed",
|
||||
fixedUrl: "https://registry.example.com",
|
||||
probeTimeoutMs: 100,
|
||||
cacheTtlMs: 1000,
|
||||
});
|
||||
const result = await resolver.resolveOrdered();
|
||||
assert.deepEqual(result, ["https://registry.example.com"]);
|
||||
});
|
||||
it("returns ordered list via resolveOrdered (fastest first)", async () => {
|
||||
const resolver = new NpmRegistryResolver({
|
||||
mode: "auto",
|
||||
candidates: ["https://slow.example.com", "https://fast.example.com"],
|
||||
probeTimeoutMs: 100,
|
||||
cacheTtlMs: 1000,
|
||||
});
|
||||
resolver.probe = async (registryUrl: string) => ({
|
||||
registryUrl,
|
||||
ok: true,
|
||||
elapsedMs: registryUrl.includes("fast") ? 10 : 50,
|
||||
});
|
||||
const result = await resolver.resolveOrdered();
|
||||
assert.deepEqual(result, ["https://fast.example.com", "https://slow.example.com"]);
|
||||
});
|
||||
it("includes failed registries at the end of resolveOrdered", async () => {
|
||||
const resolver = new NpmRegistryResolver({
|
||||
mode: "auto",
|
||||
candidates: ["https://good.example.com", "https://bad.example.com"],
|
||||
probeTimeoutMs: 100,
|
||||
cacheTtlMs: 1000,
|
||||
});
|
||||
resolver.probe = async (registryUrl: string) => {
|
||||
if (registryUrl.includes("bad")) {
|
||||
return { registryUrl, ok: false, elapsedMs: 200 };
|
||||
}
|
||||
return { registryUrl, ok: true, elapsedMs: 30 };
|
||||
};
|
||||
const result = await resolver.resolveOrdered();
|
||||
assert.deepEqual(result, ["https://good.example.com", "https://bad.example.com"]);
|
||||
});
|
||||
it("returns empty ordered list when no candidates", async () => {
|
||||
const resolver = new NpmRegistryResolver({ mode: "auto", candidates: [] });
|
||||
const result = await resolver.resolveOrdered();
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
it("re-validates cached URL on resolveOrdered call", async () => {
|
||||
let probeCount = 0;
|
||||
const resolver = new NpmRegistryResolver({
|
||||
mode: "auto",
|
||||
candidates: ["https://mirror.example.com"],
|
||||
cacheTtlMs: 60000,
|
||||
});
|
||||
resolver.probe = async (registryUrl: string) => {
|
||||
probeCount++;
|
||||
return { registryUrl, ok: true, elapsedMs: 10 };
|
||||
};
|
||||
const first = await resolver.resolveOrdered();
|
||||
assert.deepEqual(first, ["https://mirror.example.com"]);
|
||||
assert.equal(probeCount, 1);
|
||||
const second = await resolver.resolveOrdered();
|
||||
assert.deepEqual(second, ["https://mirror.example.com"]);
|
||||
assert.equal(probeCount, 2);
|
||||
});
|
||||
});
|
||||
@@ -1,27 +1,529 @@
|
||||
/**
|
||||
* 运行时动态导入函数类型
|
||||
*/
|
||||
export type ImportRuntime = (specifier: string, logger?: ILogger) => Promise<any>;
|
||||
|
||||
/**
|
||||
* 日志接口
|
||||
*/
|
||||
import { logger as defaultLogger } from "@certd/basic";
|
||||
import { spawn } from "child_process";
|
||||
import fs from "fs";
|
||||
import { createRequire } from "module";
|
||||
import path from "path";
|
||||
import { pathToFileURL } from "url";
|
||||
import type { Registry } from "../registry/registry.js";
|
||||
export type ILogger = {
|
||||
info: (message: string) => void;
|
||||
warn?: (message: string) => void;
|
||||
error?: (message: string, ...args: any[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* 运行时依赖服务参数
|
||||
*/
|
||||
export type EnsureRuntimeDepsOptions = {
|
||||
pluginKeys: string | string[];
|
||||
logger?: ILogger;
|
||||
type InstallResult = {
|
||||
registryUrl: string;
|
||||
packageJsonPath: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 运行时依赖服务接口
|
||||
*/
|
||||
export interface IRuntimeDepsService {
|
||||
ensureRuntimeDependencies(options: EnsureRuntimeDepsOptions): Promise<any>;
|
||||
importRuntime: ImportRuntime;
|
||||
type RuntimeImportResolveResult = {
|
||||
resolved: string;
|
||||
packageName: string;
|
||||
};
|
||||
|
||||
type CommandRunnerResult = {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
code: number;
|
||||
};
|
||||
|
||||
type CommandRunner = {
|
||||
run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult>;
|
||||
};
|
||||
|
||||
export type NpmRegistryResolverConfig = {
|
||||
mode?: "auto" | "fixed" | "system";
|
||||
fixedUrl?: string;
|
||||
candidates?: string[];
|
||||
probeTimeoutMs?: number;
|
||||
cacheTtlMs?: number;
|
||||
};
|
||||
|
||||
export type RegistryProbeResult = {
|
||||
registryUrl: string;
|
||||
ok: boolean;
|
||||
elapsedMs: number;
|
||||
};
|
||||
|
||||
export class NpmRegistryResolver {
|
||||
config: NpmRegistryResolverConfig;
|
||||
private cache?: { orderedUrls: string[]; expiresAt: number };
|
||||
|
||||
constructor(config?: NpmRegistryResolverConfig) {
|
||||
this.config = config || {};
|
||||
}
|
||||
|
||||
async resolveOrdered(): Promise<string[]> {
|
||||
const config = this.config;
|
||||
if (config?.mode === "fixed" && config.fixedUrl) {
|
||||
return [config.fixedUrl];
|
||||
}
|
||||
if (config?.mode === "system") {
|
||||
return [];
|
||||
}
|
||||
const cached = this.cache;
|
||||
if (cached && cached.expiresAt > Date.now()) {
|
||||
const fastUrl = cached.orderedUrls[0];
|
||||
if (fastUrl) {
|
||||
const probeResult = await this.probe(fastUrl);
|
||||
if (probeResult.ok) {
|
||||
return cached.orderedUrls;
|
||||
}
|
||||
}
|
||||
this.cache = undefined;
|
||||
}
|
||||
const candidates = (config?.candidates || []).filter(Boolean);
|
||||
if (candidates.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const orderedUrls = await this.internalProbeAll(candidates);
|
||||
this.cache = { orderedUrls, expiresAt: Date.now() + (config?.cacheTtlMs ?? 300_000) };
|
||||
return orderedUrls;
|
||||
}
|
||||
|
||||
private async internalProbeAll(candidates: string[]): Promise<string[]> {
|
||||
const probes = await Promise.allSettled(candidates.map(registryUrl => this.probe(registryUrl)));
|
||||
const okList: RegistryProbeResult[] = [];
|
||||
const failList: RegistryProbeResult[] = [];
|
||||
for (const item of probes) {
|
||||
const result = item.status === "fulfilled" ? item.value : null;
|
||||
if (result && result.ok) {
|
||||
okList.push(result);
|
||||
} else if (result) {
|
||||
failList.push(result);
|
||||
}
|
||||
}
|
||||
okList.sort((a, b) => a.elapsedMs - b.elapsedMs);
|
||||
failList.sort((a, b) => a.elapsedMs - b.elapsedMs);
|
||||
return [...okList.map(r => r.registryUrl), ...failList.map(r => r.registryUrl)];
|
||||
}
|
||||
|
||||
async probe(registryUrl: string): Promise<RegistryProbeResult> {
|
||||
const timeoutMs = this.config?.probeTimeoutMs || 3000;
|
||||
const started = Date.now();
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const res = await fetch(`${registryUrl.replace(/\/$/, "")}/-/ping`, { signal: controller.signal });
|
||||
return { registryUrl, ok: res.ok, elapsedMs: Date.now() - started };
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
} catch {
|
||||
return { registryUrl, ok: false, elapsedMs: Date.now() - started };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type RuntimeDepsConfig = {
|
||||
rootDir?: string;
|
||||
enabled?: boolean;
|
||||
installTimeoutMs?: number;
|
||||
pnpmCommand?: string;
|
||||
lazyDependencies?: Record<string, string>;
|
||||
registry?: NpmRegistryResolverConfig;
|
||||
};
|
||||
|
||||
function normalizeRange(range: string) {
|
||||
return range.trim().replace(/^\^/, "").replace(/^~?/, "");
|
||||
}
|
||||
|
||||
function areRangesCompatible(a: string, b: string) {
|
||||
if (!a || !b) {
|
||||
return true;
|
||||
}
|
||||
if (a === "*" || b === "*") {
|
||||
return true;
|
||||
}
|
||||
const left = normalizeRange(a).split(".");
|
||||
const right = normalizeRange(b).split(".");
|
||||
return left[0] === right[0];
|
||||
}
|
||||
|
||||
let INSTALL_LOCKER: any = null;
|
||||
|
||||
class DefaultCommandRunner implements CommandRunner {
|
||||
async run(command: string, args: string[], options: { cwd: string; timeoutMs: number; env?: NodeJS.ProcessEnv }): Promise<CommandRunnerResult> {
|
||||
return await new Promise<CommandRunnerResult>(resolve => {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let settled = false;
|
||||
const child = spawn(command, args, { cwd: options.cwd, env: options.env, windowsHide: true, shell: process.platform === "win32" });
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
child.kill("SIGTERM");
|
||||
resolve({ stdout, stderr: stderr || `command timeout after ${options.timeoutMs}ms`, code: 1 });
|
||||
}, options.timeoutMs);
|
||||
child.stdout?.on("data", chunk => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
child.stderr?.on("data", chunk => {
|
||||
stderr += chunk.toString();
|
||||
});
|
||||
child.on("error", error => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
resolve({ stdout, stderr: error.message, code: 1 });
|
||||
});
|
||||
child.on("close", code => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
clearTimeout(timer);
|
||||
resolve({ stdout, stderr, code: code || 0 });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class RuntimeDepsService {
|
||||
runtimeDepsRootDir: string;
|
||||
enabled: boolean;
|
||||
installTimeoutMs: number;
|
||||
pnpmCommand: string;
|
||||
lazyDependencies: Record<string, string>;
|
||||
registryResolver!: NpmRegistryResolver;
|
||||
commandRunner: CommandRunner = new DefaultCommandRunner();
|
||||
pluginLazyDependencies: Record<string, string> = {};
|
||||
private registriesMap: Record<string, { registry: Registry<any>; pluginType: string; addonType?: string }> | null = null;
|
||||
|
||||
constructor(config: RuntimeDepsConfig, registries: any) {
|
||||
this.runtimeDepsRootDir = config?.rootDir ?? "./data/.runtime-deps";
|
||||
this.enabled = config?.enabled ?? true;
|
||||
this.installTimeoutMs = config?.installTimeoutMs ?? 60000;
|
||||
this.pnpmCommand = config?.pnpmCommand ?? "";
|
||||
this.lazyDependencies = config?.lazyDependencies ?? {};
|
||||
this.registryResolver = new NpmRegistryResolver(config?.registry);
|
||||
if (registries) {
|
||||
this.setRegistries(registries);
|
||||
}
|
||||
}
|
||||
|
||||
setRegistries(registries: { pluginRegistry?: Registry<any>; accessRegistry?: Registry<any>; notificationRegistry?: Registry<any>; dnsProviderRegistry?: Registry<any>; addonRegistry?: Registry<any> }) {
|
||||
const map: Record<string, { registry: Registry<any>; pluginType: string; addonType?: string }> = {};
|
||||
if (registries.pluginRegistry) {
|
||||
map["plugin"] = { registry: registries.pluginRegistry, pluginType: "plugin" };
|
||||
}
|
||||
if (registries.accessRegistry) {
|
||||
map["access"] = { registry: registries.accessRegistry, pluginType: "access" };
|
||||
}
|
||||
if (registries.notificationRegistry) {
|
||||
map["notification"] = { registry: registries.notificationRegistry, pluginType: "notification" };
|
||||
}
|
||||
if (registries.dnsProviderRegistry) {
|
||||
map["dnsProvider"] = { registry: registries.dnsProviderRegistry, pluginType: "dnsProvider" };
|
||||
}
|
||||
if (registries.addonRegistry) {
|
||||
map["addon"] = { registry: registries.addonRegistry, pluginType: "addon", addonType: "" };
|
||||
}
|
||||
this.registriesMap = map;
|
||||
}
|
||||
|
||||
async ensureDependencies(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
||||
const { dependencies, logger: log } = options;
|
||||
|
||||
if (INSTALL_LOCKER) {
|
||||
throw new Error("动态安装依赖正在执行中,请稍后再试");
|
||||
}
|
||||
INSTALL_LOCKER = true;
|
||||
const installPromise = this.doEnsureInstalled({ dependencies, logger: log }).finally(() => {
|
||||
INSTALL_LOCKER = null;
|
||||
});
|
||||
// 25s 内如果没返回就提前返回
|
||||
// AI注意,不要改我的实现,这里提前在25s就报超时是我故意这么做的实现
|
||||
// 因为前端配置的超时时间是30s,大部分的情况下install会在25s内完成,所以这里提前在25s就报超时,并且容忍60s的install
|
||||
const timeoutPromise: Promise<any> = new Promise((resolve, reject) => setTimeout(() => reject(new Error("安装依赖超时,请稍后重试")), 25 * 1000));
|
||||
return await Promise.race([installPromise, timeoutPromise]);
|
||||
}
|
||||
|
||||
async importRuntime(specifier: string, logger: ILogger = defaultLogger) {
|
||||
if (this.isNativeImportSpecifier(specifier)) {
|
||||
//是否本地模块
|
||||
return await import(specifier);
|
||||
}
|
||||
|
||||
let resolved: string;
|
||||
try {
|
||||
//尝试加载本项目依赖
|
||||
resolved = this.resolveProjectSpecifier(specifier).resolved;
|
||||
} catch (error) {
|
||||
if (!this.isModuleNotFoundError(error)) {
|
||||
throw error;
|
||||
}
|
||||
resolved = await this.resolveLazyOrInstallSpecifier(specifier, logger);
|
||||
}
|
||||
if (!resolved) {
|
||||
throw new Error(`依赖未安装成功: ${specifier}`);
|
||||
}
|
||||
|
||||
return await import(pathToFileURL(resolved).href);
|
||||
}
|
||||
|
||||
//动态获取模块解析
|
||||
private async resolveLazyOrInstallSpecifier(specifier: string, logger: ILogger = defaultLogger) {
|
||||
try {
|
||||
//解析动态目录里面的模块是否存在
|
||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||
} catch (runtimeError: any) {
|
||||
if (!this.isModuleNotFoundError(runtimeError)) {
|
||||
//其他错误
|
||||
throw runtimeError;
|
||||
}
|
||||
if (!this.enabled) {
|
||||
throw new Error("动态安装依赖未开启");
|
||||
}
|
||||
//模块不存在,安装
|
||||
await this.installLazyDependencies(specifier, logger);
|
||||
|
||||
try {
|
||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||
} catch (lazyError: any) {
|
||||
// logger?.error?.(`动态依赖安装失败: ${lazyError.message}`);
|
||||
throw new Error(`依赖加载失败,可能动态依赖未安装成功: ${lazyError.message}`, { cause: lazyError });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//不存在模块动态依赖安装,再获取解析
|
||||
private async installLazyDependencies(specifier: string, logger?: ILogger) {
|
||||
const packageName = this.parsePackageName(specifier);
|
||||
const mergedDeps = this.getMergedLazyDependencies();
|
||||
const lazyRange = mergedDeps[packageName];
|
||||
if (!lazyRange) {
|
||||
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
||||
}
|
||||
try {
|
||||
await this.ensureDependencies({ dependencies: { [packageName]: lazyRange }, logger });
|
||||
} catch (lazyError: any) {
|
||||
// logger?.error?.(`动态依赖安装失败: ${lazyError.message}`);
|
||||
throw new Error(`动态依赖安装失败: ${packageName}: ${lazyError.message}`, { cause: lazyError });
|
||||
}
|
||||
}
|
||||
|
||||
private isNativeImportSpecifier(specifier: string) {
|
||||
return specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:") || specifier.startsWith("node:");
|
||||
}
|
||||
|
||||
//尝试加载runtime依赖
|
||||
private resolveRuntimeSpecifier(specifier: string): RuntimeImportResolveResult {
|
||||
const packageName = this.parsePackageName(specifier);
|
||||
const packageJsonPath = path.join(this.getRuntimeDepsRootDir(), "package.json");
|
||||
const require = createRequire(packageJsonPath);
|
||||
const resolved = require.resolve(specifier);
|
||||
return { packageName, resolved };
|
||||
}
|
||||
|
||||
//尝试加载本地依赖
|
||||
private resolveProjectSpecifier(specifier: string): RuntimeImportResolveResult {
|
||||
const packageName = this.parsePackageName(specifier);
|
||||
const packageJsonPath = path.resolve("package.json");
|
||||
const require = createRequire(packageJsonPath);
|
||||
const resolved = require.resolve(specifier);
|
||||
return { packageName, resolved };
|
||||
}
|
||||
|
||||
private parsePackageName(specifier: string) {
|
||||
if (!specifier || specifier.trim() !== specifier) {
|
||||
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
||||
}
|
||||
const parts = specifier.split("/");
|
||||
if (specifier.startsWith("@")) {
|
||||
if (parts.length < 2 || !parts[0] || !parts[1]) {
|
||||
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
||||
}
|
||||
return `${parts[0]}/${parts[1]}`;
|
||||
}
|
||||
if (!parts[0]) {
|
||||
throw new Error(`动态依赖导入路径无效: ${specifier}`);
|
||||
}
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
private isModuleNotFoundError(error: any) {
|
||||
return error?.code === "MODULE_NOT_FOUND" || error?.code === "ERR_MODULE_NOT_FOUND";
|
||||
}
|
||||
|
||||
private async doEnsureInstalled(options: { dependencies: Record<string, string>; logger?: ILogger }): Promise<InstallResult> {
|
||||
let { dependencies } = options;
|
||||
const log = options.logger || defaultLogger;
|
||||
const rootDir = this.getRuntimeDepsRootDir();
|
||||
if (!fs.existsSync(rootDir)) {
|
||||
fs.mkdirSync(rootDir, { recursive: true });
|
||||
}
|
||||
const packageJsonPath = path.join(rootDir, "package.json");
|
||||
log.info(`第三方依赖安装: ${JSON.stringify(dependencies)}`);
|
||||
dependencies = this.mergeInstalledDependencies(this.readManifestDependencies(packageJsonPath), dependencies);
|
||||
|
||||
const manifest = { name: "certd-runtime-deps", private: true, type: "module", dependencies };
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(manifest, null, 2), "utf8");
|
||||
const allRegistryUrls = await this.registryResolver.resolveOrdered();
|
||||
const urlsToTry = allRegistryUrls.length > 0 ? allRegistryUrls : [""];
|
||||
const command = this.getPnpmCommand();
|
||||
let lastError: string | undefined;
|
||||
for (const tryUrl of urlsToTry) {
|
||||
const args = ["install", "--prod", "--ignore-scripts", "--ignore-workspace", "--no-frozen-lockfile", "--reporter=append-only"];
|
||||
if (tryUrl) {
|
||||
args.push(`--registry=${tryUrl}`);
|
||||
}
|
||||
const tryEnv = this.buildChildEnv(tryUrl);
|
||||
log.info(`开始安装第三方依赖: ${Object.keys(dependencies).join(", ")}${tryUrl ? `,镜像: ${tryUrl}` : ""}`);
|
||||
const result = await this.commandRunner.run(command, args, { cwd: rootDir, timeoutMs: this.installTimeoutMs, env: tryEnv });
|
||||
if (result.code === 0) {
|
||||
log.info(`${result.stdout?.slice(-2000) || "无npm安装日志输出"}`);
|
||||
log.info("第三方依赖安装完成");
|
||||
return { registryUrl: tryUrl, packageJsonPath };
|
||||
}
|
||||
const errOutput = (result.stderr || "").trim();
|
||||
const outOutput = (result.stdout || "").trim();
|
||||
lastError = errOutput || outOutput || "unknown error";
|
||||
log.info(`镜像 ${tryUrl || "默认"} 安装失败,退出码: ${result.code}${urlsToTry.length > 1 ? ",尝试下一个镜像..." : ""}`);
|
||||
log.info(` pnpm stderr: ${(errOutput || "无npm安装日志输出").slice(-2000)}`);
|
||||
if (outOutput) {
|
||||
log.info(` pnpm stdout: ${outOutput.slice(-2000)}`);
|
||||
}
|
||||
}
|
||||
throw new Error(`动态依赖安装失败: ${lastError}`);
|
||||
}
|
||||
|
||||
async clearRuntimeDeps() {
|
||||
const rootDir = this.getRuntimeDepsRootDir();
|
||||
const normalizedRootDir = path.normalize(rootDir);
|
||||
if (!normalizedRootDir.endsWith(path.normalize(".runtime-deps"))) {
|
||||
throw new Error(`动态依赖目录配置异常,拒绝清理: ${rootDir}`);
|
||||
}
|
||||
|
||||
if (INSTALL_LOCKER) {
|
||||
throw new Error("仍有依赖正在安装,请稍后");
|
||||
}
|
||||
|
||||
if (fs.existsSync(rootDir)) {
|
||||
const entries = fs.readdirSync(rootDir);
|
||||
for (const entry of entries) {
|
||||
fs.rmSync(path.join(rootDir, entry), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getMergedLazyDependencies(): Record<string, string> {
|
||||
return { ...this.lazyDependencies, ...this.pluginLazyDependencies };
|
||||
}
|
||||
|
||||
collectPluginDeps(logger?: ILogger) {
|
||||
if (!this.registriesMap) {
|
||||
return;
|
||||
}
|
||||
const deps: Record<string, string> = {};
|
||||
for (const { registry } of Object.values(this.registriesMap)) {
|
||||
const defineList = registry.getDefineList();
|
||||
for (const define of defineList) {
|
||||
const dependPackages = (define as any).dependPackages as Record<string, string> | undefined;
|
||||
if (!dependPackages) {
|
||||
continue;
|
||||
}
|
||||
for (const [pkgName, range] of Object.entries(dependPackages)) {
|
||||
const existing = deps[pkgName];
|
||||
if (existing && !areRangesCompatible(existing, range)) {
|
||||
(logger || defaultLogger).warn?.(`懒加载依赖版本冲突: ${pkgName} => ${existing} vs ${range},保留已有版本`);
|
||||
continue;
|
||||
}
|
||||
deps[pkgName] = range;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.pluginLazyDependencies = deps;
|
||||
(logger || defaultLogger).info(`从插件注册表收集到 ${Object.keys(deps).length} 个懒加载依赖`);
|
||||
}
|
||||
|
||||
refreshPluginDeps(logger?: ILogger) {
|
||||
this.collectPluginDeps(logger);
|
||||
}
|
||||
|
||||
private readManifestDependencies(packageJsonPath: string): Record<string, string> {
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
||||
return manifest.dependencies || {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private mergeInstalledDependencies(installed: Record<string, string>, requested: Record<string, string>) {
|
||||
const dependencies = { ...installed };
|
||||
for (const [packageName, range] of Object.entries(requested)) {
|
||||
const installedRange = dependencies[packageName];
|
||||
if (installedRange && !areRangesCompatible(installedRange, range)) {
|
||||
throw new Error(`动态依赖版本冲突: ${packageName} => installed:${installedRange}, requested:${range}`);
|
||||
}
|
||||
dependencies[packageName] = installedRange || range;
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
private getPnpmCommand() {
|
||||
return this.pnpmCommand || "pnpm";
|
||||
}
|
||||
|
||||
private buildChildEnv(registryUrl: string) {
|
||||
const env = { ...process.env };
|
||||
for (const key of ["NODE_OPTIONS", "VSCODE_INSPECTOR_OPTIONS", "NODE_INSPECTOR_PORT", "NODE_DEBUG"]) {
|
||||
if (!env[key]) {
|
||||
continue;
|
||||
}
|
||||
if (key === "NODE_OPTIONS") {
|
||||
env[key] = this.stripDebugNodeOptions(env[key] as string);
|
||||
} else {
|
||||
delete env[key];
|
||||
}
|
||||
}
|
||||
if (registryUrl) {
|
||||
env.npm_config_registry = registryUrl;
|
||||
env.pnpm_config_registry = registryUrl;
|
||||
}
|
||||
env.CI = env.CI || "true";
|
||||
env.npm_config_confirm_modules_purge = "false";
|
||||
env.pnpm_config_confirm_modules_purge = "false";
|
||||
return env;
|
||||
}
|
||||
|
||||
private stripDebugNodeOptions(value: string) {
|
||||
return value
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.filter(item => !/^--inspect(-brk|-port)?(=|$)/.test(item))
|
||||
.filter(item => !/^--debug(=|$)/.test(item))
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
getRuntimeDepsRootDir() {
|
||||
return path.resolve(this.runtimeDepsRootDir);
|
||||
}
|
||||
}
|
||||
|
||||
let runtimeDepsServiceInstance: RuntimeDepsService | null = null;
|
||||
|
||||
export function initRuntimeDepsService(config: RuntimeDepsConfig, registries: any): RuntimeDepsService {
|
||||
runtimeDepsServiceInstance = new RuntimeDepsService(config, registries);
|
||||
return runtimeDepsServiceInstance;
|
||||
}
|
||||
|
||||
export function getRuntimeDepsService(): RuntimeDepsService {
|
||||
if (!runtimeDepsServiceInstance) {
|
||||
throw new Error("RuntimeDepsService 未初始化");
|
||||
}
|
||||
return runtimeDepsServiceInstance!;
|
||||
}
|
||||
|
||||
export async function importRuntime(specifier: string, logger: ILogger = defaultLogger): Promise<any> {
|
||||
return getRuntimeDepsService().importRuntime(specifier, logger);
|
||||
}
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
@@ -15,7 +15,7 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "npm run build",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
@@ -26,9 +26,10 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"esmock": "^2.7.5",
|
||||
"prettier": "3.3.3",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -18,7 +18,7 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "npm run build",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"nanoid": "^5.0.7"
|
||||
@@ -37,5 +37,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './lib/iframe.client.js';
|
||||
export * from "./lib/iframe.client.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export type IframeMessageData<T> = {
|
||||
action: string;
|
||||
@@ -32,7 +32,7 @@ export class IframeClient {
|
||||
constructor(iframe?: HTMLIFrameElement, onError?: (e: any) => void) {
|
||||
this.iframe = iframe;
|
||||
this.onError = onError;
|
||||
window.addEventListener('message', async (event: MessageEvent<IframeMessageData<any>>) => {
|
||||
window.addEventListener("message", async (event: MessageEvent<IframeMessageData<any>>) => {
|
||||
const data = event.data;
|
||||
if (data.action) {
|
||||
console.log(`收到消息[isSub:${this.isInFrame()}]`, data);
|
||||
@@ -40,20 +40,20 @@ export class IframeClient {
|
||||
const handler = this.handlers[data.action];
|
||||
if (handler) {
|
||||
const res = await handler(data);
|
||||
if (data.id && data.action !== 'reply') {
|
||||
await this.send('reply', res, data.id);
|
||||
if (data.id && data.action !== "reply") {
|
||||
await this.send("reply", res, data.id);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`action:${data.action} 未注册处理器,可能版本过低`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
await this.send('reply', {}, data.id, 500, e.message);
|
||||
await this.send("reply", {}, data.id, 500, e.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.register('reply', async data => {
|
||||
this.register("reply", async data => {
|
||||
const req = this.requestQueue[data.replyId!];
|
||||
if (req) {
|
||||
req.onReply(data);
|
||||
@@ -106,12 +106,12 @@ export class IframeClient {
|
||||
console.log(`send message[isSub:${this.isInFrame()}]:`, reqMessageData);
|
||||
if (!this.iframe) {
|
||||
if (!window.parent) {
|
||||
reject('当前页面不在 iframe 中');
|
||||
reject("当前页面不在 iframe 中");
|
||||
}
|
||||
window.parent.postMessage(reqMessageData, '*');
|
||||
window.parent.postMessage(reqMessageData, "*");
|
||||
} else {
|
||||
//子页面
|
||||
this.iframe.contentWindow?.postMessage(reqMessageData, '*');
|
||||
this.iframe.contentWindow?.postMessage(reqMessageData, "*");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -13,7 +13,7 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "npm run build",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"author": "",
|
||||
"license": "Apache",
|
||||
@@ -35,6 +35,7 @@
|
||||
"chai": "^5.1.0",
|
||||
"config": "^1.30.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"esmock": "^2.7.5",
|
||||
"js-yaml": "^3.11.0",
|
||||
"mocha": "^10.6.0",
|
||||
@@ -62,5 +63,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import jdCloud from "./lib/core.js";
|
||||
import jdService from './lib/service.js'
|
||||
import jdCloud from './lib/core.js';
|
||||
import jdService from './lib/service.js';
|
||||
|
||||
import domainService from './repo/domainservice/v2/domainservice.js'
|
||||
import cdnService from './repo/cdn/v1/cdn.js'
|
||||
import sslService from './repo/ssl/v1/ssl.js'
|
||||
import domainService from './repo/domainservice/v2/domainservice.js';
|
||||
import cdnService from './repo/cdn/v1/cdn.js';
|
||||
import sslService from './repo/ssl/v1/ssl.js';
|
||||
export const JDCloud = jdCloud;
|
||||
export const JDService = jdService;
|
||||
export const JDDomainService = domainService;
|
||||
export const JDCdnService = cdnService;
|
||||
export const JDSslService = sslService;
|
||||
export const JDSslService = sslService;
|
||||
|
||||
@@ -19,5 +19,9 @@
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -18,10 +18,10 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certd/basic": "^1.42.2",
|
||||
"@certd/basic": "^1.43.0",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -38,5 +38,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export type K8sClientOpts = {
|
||||
//暂时没用
|
||||
lookup?: any;
|
||||
skipTLSVerify?: boolean;
|
||||
debug?: boolean;
|
||||
};
|
||||
export class K8sClient {
|
||||
kubeconfig!: KubeConfig;
|
||||
@@ -18,11 +19,13 @@ export class K8sClient {
|
||||
client!: CoreV1Api;
|
||||
logger: ILogger;
|
||||
skipTLSVerify?: boolean;
|
||||
debug?: boolean;
|
||||
constructor(opts: K8sClientOpts) {
|
||||
this.kubeConfigStr = opts.kubeConfigStr;
|
||||
this.logger = opts.logger;
|
||||
this.setLookup(opts.lookup);
|
||||
this.skipTLSVerify = opts.skipTLSVerify;
|
||||
this.debug = opts.debug;
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -86,6 +89,9 @@ export class K8sClient {
|
||||
yml.metadata = {};
|
||||
}
|
||||
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
|
||||
if (this.debug) {
|
||||
this.logger.info("patch yaml body:", JSON.stringify(yml));
|
||||
}
|
||||
const res = await client.patch(yml);
|
||||
return res?.body;
|
||||
}
|
||||
@@ -126,6 +132,9 @@ export class K8sClient {
|
||||
async createSecret(opts: { namespace: string; body: V1Secret }) {
|
||||
const namespace = opts.namespace || "default";
|
||||
this.logger.info("create secret:", opts.body.metadata);
|
||||
if (this.debug) {
|
||||
this.logger.info("create secret body:", JSON.stringify(opts.body));
|
||||
}
|
||||
const created = await this.client.createNamespacedSecret(namespace, opts.body);
|
||||
this.logger.info("new secrets:", opts.body.metadata);
|
||||
return created.body;
|
||||
@@ -162,6 +171,9 @@ export class K8sClient {
|
||||
},
|
||||
opts.body
|
||||
);
|
||||
if (this.debug) {
|
||||
this.logger.info("create secret:", JSON.stringify(body));
|
||||
}
|
||||
const res = await this.createSecret({ namespace, body });
|
||||
this.logger.info(`secret ${secretName} 已创建`);
|
||||
return res;
|
||||
@@ -173,6 +185,9 @@ export class K8sClient {
|
||||
}
|
||||
|
||||
const newSecret = merge(oldSecret.body, opts.body);
|
||||
if (this.debug) {
|
||||
this.logger.info("patch secret:", JSON.stringify(newSecret));
|
||||
}
|
||||
const res = await this.client.replaceNamespacedSecret(secretName, namespace, newSecret);
|
||||
this.logger.info(`secret ${secretName} 已更新`);
|
||||
return res.body;
|
||||
@@ -207,6 +222,9 @@ export class K8sClient {
|
||||
const client = this.kubeconfig.makeApiClient(NetworkingV1Api);
|
||||
const oldIngress = await client.readNamespacedIngress(ingressName, namespace);
|
||||
const newIngress = merge(oldIngress.body, opts.body);
|
||||
if (this.debug) {
|
||||
this.logger.info("patch ingress:", JSON.stringify(newIngress));
|
||||
}
|
||||
const res = await client.replaceNamespacedIngress(ingressName, namespace, newIngress);
|
||||
|
||||
this.logger.info("ingress patched", opts.body);
|
||||
@@ -222,6 +240,7 @@ export class K8sClient {
|
||||
},
|
||||
};
|
||||
for (const ingress of ingressNames) {
|
||||
this.logger.info(`ingress 开始重启:${ingress}`);
|
||||
await this.patchIngress({ namespace, ingressName: ingress, body });
|
||||
this.logger.info(`ingress已重启:${ingress}`);
|
||||
}
|
||||
|
||||
@@ -18,5 +18,9 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"dist/**"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.43.0](https://github.com/certd/certd/compare/v1.42.6...v1.43.0) (2026-08-14)
|
||||
|
||||
### Features
|
||||
|
||||
* 优化开放接口证书自动申请功能 ([6339cdf](https://github.com/certd/certd/commit/6339cdffa9f79505dada5617d8c7fcfacc419a2e))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 统一通知类型常量管理,新增开放接口通知能力 ([edb249c](https://github.com/certd/certd/commit/edb249c2ad3c173dbfd04d8d156f895a6706ffd1))
|
||||
|
||||
## [1.42.6](https://github.com/certd/certd/compare/v1.42.5...v1.42.6) (2026-07-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复正常批量删除流水线报权限不足的bug ([5b50083](https://github.com/certd/certd/commit/5b500830a122c6c42dab054e57fed509050f94da))
|
||||
|
||||
## [1.42.5](https://github.com/certd/certd/compare/v1.42.4...v1.42.5) (2026-07-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复上传到cos报runtimeDepsService未初始化的问题 ([167b303](https://github.com/certd/certd/commit/167b303faeca02cc11cf97e4be2a3df914852167))
|
||||
|
||||
## [1.42.4](https://github.com/certd/certd/compare/v1.42.3...v1.42.4) (2026-07-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **pipeline:** 重构运行时依赖加载逻辑,修复火山引擎DNS解析报runtimeDepsService未初始化的bug ([ec69b8f](https://github.com/certd/certd/commit/ec69b8f11bfd4b20991aef74a72a47182ca79a9d))
|
||||
|
||||
## [1.42.3](https://github.com/certd/certd/compare/v1.42.2...v1.42.3) (2026-07-08)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.42.2](https://github.com/certd/certd/compare/v1.42.1...v1.42.2) (2026-07-07)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.42.2",
|
||||
"version": "1.43.0",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -19,7 +19,7 @@
|
||||
"pub": "npm publish",
|
||||
"compile": "tsc --skipLibCheck --watch",
|
||||
"format": "prettier --write src",
|
||||
"lint": "eslint --fix"
|
||||
"lint": "eslint --fix --ext .ts src"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "greper",
|
||||
@@ -29,11 +29,11 @@
|
||||
],
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@certd/acme-client": "^1.42.2",
|
||||
"@certd/basic": "^1.42.2",
|
||||
"@certd/pipeline": "^1.42.2",
|
||||
"@certd/plugin-lib": "^1.42.2",
|
||||
"@certd/plus-core": "^1.42.2",
|
||||
"@certd/acme-client": "^1.43.0",
|
||||
"@certd/basic": "^1.43.0",
|
||||
"@certd/pipeline": "^1.43.0",
|
||||
"@certd/plugin-lib": "^1.43.0",
|
||||
"@certd/plus-core": "^1.43.0",
|
||||
"@midwayjs/cache": "3.14.0",
|
||||
"@midwayjs/core": "3.20.11",
|
||||
"@midwayjs/i18n": "3.20.13",
|
||||
@@ -69,5 +69,5 @@
|
||||
"typeorm": "^0.3.20",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "7808d028e9c7e1b1e3ebc41f12b023d7b553300b"
|
||||
"gitHead": "246ee83015bf5589adc2a5fa3d1388c8d9a2a252"
|
||||
}
|
||||
|
||||
@@ -253,7 +253,6 @@ export abstract class BaseService<T> {
|
||||
if (!Array.isArray(ids)) {
|
||||
ids = [ids];
|
||||
}
|
||||
ids = this.filterIds(ids);
|
||||
const res = await this.getRepository().find({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
@@ -266,7 +265,7 @@ export abstract class BaseService<T> {
|
||||
},
|
||||
});
|
||||
if (!res || res.length === ids.length) {
|
||||
return;
|
||||
return ids;
|
||||
}
|
||||
throw new PermissionException("权限不足");
|
||||
}
|
||||
@@ -279,7 +278,13 @@ export abstract class BaseService<T> {
|
||||
return item != null && item != "";
|
||||
});
|
||||
}
|
||||
async batchDelete(ids: number[], userId: number, projectId?: number) {
|
||||
async batchDelete(ids: number[], userId: number, projectId?: number): Promise<number> {
|
||||
if (!ids || ids.length === 0) {
|
||||
throw new ValidateException("ids不能为空");
|
||||
}
|
||||
if (!Array.isArray(ids)) {
|
||||
ids = [ids];
|
||||
}
|
||||
ids = this.filterIds(ids);
|
||||
if (userId != null) {
|
||||
const userProjectQuery = this.buildUserProjectQuery(userId, projectId);
|
||||
@@ -295,6 +300,7 @@ export abstract class BaseService<T> {
|
||||
}
|
||||
|
||||
await this.delete(ids);
|
||||
return ids.length;
|
||||
}
|
||||
|
||||
async findOne(options: FindOneOptions<T>) {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export const Constants = {
|
||||
dataDir: './data',
|
||||
dataDir: "./data",
|
||||
role: {
|
||||
defaultUser: 3,
|
||||
},
|
||||
per: {
|
||||
//无需登录
|
||||
guest: '_guest_',
|
||||
guest: "_guest_",
|
||||
//无需登录
|
||||
anonymous: '_guest_',
|
||||
anonymous: "_guest_",
|
||||
//无需登录,有 token 时解析当前用户
|
||||
guestOptionalAuth: '_guestOptionalAuth_',
|
||||
guestOptionalAuth: "_guestOptionalAuth_",
|
||||
//仅需要登录
|
||||
authOnly: '_authOnly_',
|
||||
authOnly: "_authOnly_",
|
||||
//仅需要登录
|
||||
loginOnly: '_authOnly_',
|
||||
loginOnly: "_authOnly_",
|
||||
|
||||
open: '_open_',
|
||||
open: "_open_",
|
||||
},
|
||||
res: {
|
||||
serverError(message: string) {
|
||||
@@ -26,102 +26,106 @@ export const Constants = {
|
||||
},
|
||||
error: {
|
||||
code: 1,
|
||||
message: 'Internal server error',
|
||||
message: "Internal server error",
|
||||
},
|
||||
success: {
|
||||
code: 0,
|
||||
message: 'success',
|
||||
message: "success",
|
||||
},
|
||||
validation: {
|
||||
code: 10,
|
||||
message: '参数错误',
|
||||
message: "参数错误",
|
||||
},
|
||||
needvip: {
|
||||
code: 88,
|
||||
message: '需要VIP',
|
||||
message: "需要VIP",
|
||||
},
|
||||
needsuite: {
|
||||
code: 89,
|
||||
message: '需要购买或升级套餐',
|
||||
message: "需要购买或升级套餐",
|
||||
},
|
||||
loginError: {
|
||||
code: 2,
|
||||
message: '登录失败',
|
||||
message: "登录失败",
|
||||
},
|
||||
codeError: {
|
||||
code: 3,
|
||||
message: '验证码错误',
|
||||
message: "验证码错误",
|
||||
},
|
||||
auth: {
|
||||
code: 401,
|
||||
message: '您还未登录或token已过期',
|
||||
message: "您还未登录或token已过期",
|
||||
},
|
||||
permission: {
|
||||
code: 402,
|
||||
message: '您没有权限',
|
||||
message: "您没有权限",
|
||||
},
|
||||
param: {
|
||||
code: 400,
|
||||
message: '参数错误',
|
||||
message: "参数错误",
|
||||
},
|
||||
notFound: {
|
||||
code: 404,
|
||||
message: '页面/文件/资源不存在',
|
||||
message: "页面/文件/资源不存在",
|
||||
},
|
||||
|
||||
preview: {
|
||||
code: 10001,
|
||||
message: '对不起,预览环境不允许修改此数据',
|
||||
message: "对不起,预览环境不允许修改此数据",
|
||||
},
|
||||
siteOff:{
|
||||
siteOff: {
|
||||
code: 10010,
|
||||
message: '站点已关闭',
|
||||
message: "站点已关闭",
|
||||
},
|
||||
need2fa:{
|
||||
need2fa: {
|
||||
code: 10020,
|
||||
message: '需要2FA认证',
|
||||
message: "需要2FA认证",
|
||||
},
|
||||
openKeyError: {
|
||||
code: 20000,
|
||||
message: 'ApiToken错误',
|
||||
message: "ApiToken错误",
|
||||
},
|
||||
openKeySignError: {
|
||||
code: 20001,
|
||||
message: 'ApiToken签名错误',
|
||||
message: "ApiToken签名错误",
|
||||
},
|
||||
openKeyExpiresError: {
|
||||
code: 20002,
|
||||
message: 'ApiToken时间戳错误',
|
||||
message: "ApiToken时间戳错误",
|
||||
},
|
||||
openKeySignTypeError: {
|
||||
code: 20003,
|
||||
message: 'ApiToken签名类型不支持',
|
||||
message: "ApiToken签名类型不支持",
|
||||
},
|
||||
openParamError: {
|
||||
code: 20010,
|
||||
message: '请求参数错误',
|
||||
message: "请求参数错误",
|
||||
},
|
||||
openCertNotFound: {
|
||||
code: 20011,
|
||||
message: '证书不存在',
|
||||
message: "证书不存在",
|
||||
},
|
||||
openCertNotReady: {
|
||||
code: 20012,
|
||||
message: '证书还未生成',
|
||||
message: "证书还未生成",
|
||||
},
|
||||
openCertApplying: {
|
||||
code: 20013,
|
||||
message: '证书正在申请中,请稍后重新获取',
|
||||
message: "证书正在申请中,请稍后重新获取",
|
||||
},
|
||||
openDomainNoVerifier:{
|
||||
openDomainNoVerifier: {
|
||||
code: 20014,
|
||||
message: '域名校验方式未配置',
|
||||
message: "域名校验方式未配置",
|
||||
},
|
||||
openPipelineError: {
|
||||
code: 20015,
|
||||
message: "流水线执行异常,请稍后重试",
|
||||
},
|
||||
openEmailNotFound: {
|
||||
code: 20021,
|
||||
message: '用户邮箱还未配置',
|
||||
message: "用户邮箱还未配置",
|
||||
},
|
||||
},
|
||||
systemUserId: 0, // 系统级别userid固定为0
|
||||
enterpriseUserId: -1 // 企业模式用户id固定为-1
|
||||
enterpriseUserId: -1, // 企业模式用户id固定为-1
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ALL, Body, Post, Query } from '@midwayjs/core';
|
||||
import { BaseController } from './base-controller.js';
|
||||
import { ALL, Body, Post, Query } from "@midwayjs/core";
|
||||
import { BaseController } from "./base-controller.js";
|
||||
|
||||
export abstract class CrudController<T> extends BaseController {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
abstract getService<T>();
|
||||
|
||||
@Post('/page')
|
||||
@Post("/page")
|
||||
async page(@Body(ALL) body: any) {
|
||||
const pageRet = await this.getService().page({
|
||||
query: body.query ?? {},
|
||||
@@ -16,7 +16,7 @@ export abstract class CrudController<T> extends BaseController {
|
||||
return this.ok(pageRet);
|
||||
}
|
||||
|
||||
@Post('/list')
|
||||
@Post("/list")
|
||||
async list(@Body(ALL) body: any) {
|
||||
const listRet = await this.getService().list({
|
||||
query: body.query ?? {},
|
||||
@@ -25,33 +25,33 @@ export abstract class CrudController<T> extends BaseController {
|
||||
return this.ok(listRet);
|
||||
}
|
||||
|
||||
@Post('/add')
|
||||
@Post("/add")
|
||||
async add(@Body(ALL) bean: any) {
|
||||
delete bean.id;
|
||||
const id = await this.getService().add(bean);
|
||||
return this.ok(id);
|
||||
}
|
||||
|
||||
@Post('/info')
|
||||
async info(@Query('id') id: number) {
|
||||
@Post("/info")
|
||||
async info(@Query("id") id: number) {
|
||||
const bean = await this.getService().info(id);
|
||||
return this.ok(bean);
|
||||
}
|
||||
|
||||
@Post('/update')
|
||||
@Post("/update")
|
||||
async update(@Body(ALL) bean: any) {
|
||||
await this.getService().update(bean);
|
||||
return this.ok(null);
|
||||
}
|
||||
|
||||
@Post('/delete')
|
||||
async delete(@Query('id') id: number) {
|
||||
@Post("/delete")
|
||||
async delete(@Query("id") id: number) {
|
||||
await this.getService().delete([id]);
|
||||
return this.ok(null);
|
||||
}
|
||||
|
||||
@Post('/deleteByIds')
|
||||
async deleteByIds(@Body('ids') ids: number[]) {
|
||||
@Post("/deleteByIds")
|
||||
async deleteByIds(@Body("ids") ids: number[]) {
|
||||
await this.getService().delete(ids);
|
||||
return this.ok(null);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
import { TextException } from "./common-exception.js";
|
||||
/**
|
||||
* 授权异常
|
||||
*/
|
||||
export class AuthException extends BaseException {
|
||||
constructor(message?:string) {
|
||||
super('AuthException', Constants.res.auth.code, message ? message : Constants.res.auth.message);
|
||||
constructor(message?: string) {
|
||||
super("AuthException", Constants.res.auth.code, message ? message : Constants.res.auth.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Need2FAException extends TextException {
|
||||
constructor(message:string,data:any) {
|
||||
super('Need2FAException', Constants.res.need2fa.code, message ? message : Constants.res.need2fa.message,data);
|
||||
constructor(message: string, data: any) {
|
||||
super("Need2FAException", Constants.res.need2fa.code, message ? message : Constants.res.need2fa.message, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
export class BaseException extends Error {
|
||||
code: number;
|
||||
data?:any
|
||||
constructor(name: string, code: number, message: string ,data?:any) {
|
||||
data?: any;
|
||||
constructor(name: string, code: number, message: string, data?: any) {
|
||||
super(message);
|
||||
this.name = name;
|
||||
this.code = code;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 验证码异常
|
||||
*/
|
||||
export class CodeErrorException extends BaseException {
|
||||
constructor(message) {
|
||||
super('CodeErrorException', Constants.res.codeError.code, message ? message : Constants.res.codeError.message);
|
||||
super("CodeErrorException", Constants.res.codeError.code, message ? message : Constants.res.codeError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,3 +21,10 @@ export class TextException extends BaseException {
|
||||
super(name, code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
export class TextedException extends TextException {
|
||||
constructor(res: { code: number; message: string; data?: any; name?: string }) {
|
||||
const { code, message, data, name } = res;
|
||||
super(name || "TextedException", code, message, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
export * from './auth-exception.js';
|
||||
export * from './base-exception.js';
|
||||
export * from './permission-exception.js';
|
||||
export * from './preview-exception.js';
|
||||
export * from './validation-exception.js';
|
||||
export * from './vip-exception.js';
|
||||
export * from './common-exception.js';
|
||||
export * from './not-found-exception.js';
|
||||
export * from './param-exception.js';
|
||||
export * from './site-off-exception.js';
|
||||
export * from './login-error-exception.js'
|
||||
export * from './code-error-exception.js'
|
||||
export * from "./auth-exception.js";
|
||||
export * from "./base-exception.js";
|
||||
export * from "./permission-exception.js";
|
||||
export * from "./preview-exception.js";
|
||||
export * from "./validation-exception.js";
|
||||
export * from "./vip-exception.js";
|
||||
export * from "./common-exception.js";
|
||||
export * from "./not-found-exception.js";
|
||||
export * from "./param-exception.js";
|
||||
export * from "./site-off-exception.js";
|
||||
export * from "./login-error-exception.js";
|
||||
export * from "./code-error-exception.js";
|
||||
export * from "./non-retryable-exception.js";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 通用异常
|
||||
*/
|
||||
export class LoginErrorException extends BaseException {
|
||||
leftCount: number;
|
||||
constructor(message, leftCount: number) {
|
||||
super('LoginErrorException', Constants.res.loginError.code, message ? message : Constants.res.loginError.message);
|
||||
super("LoginErrorException", Constants.res.loginError.code, message ? message : Constants.res.loginError.message);
|
||||
this.leftCount = leftCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import assert from "assert";
|
||||
import { NonRetryableException } from "./non-retryable-exception.js";
|
||||
|
||||
describe("NonRetryableException", () => {
|
||||
it("sets the standard error name and message", () => {
|
||||
const error = new NonRetryableException("cannot retry");
|
||||
|
||||
assert.equal(error.name, "NonRetryableException");
|
||||
assert.equal(error.message, "cannot retry");
|
||||
assert.equal(error instanceof Error, true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export class NonRetryableException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "NonRetryableException";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 资源不存在
|
||||
*/
|
||||
export class NotFoundException extends BaseException {
|
||||
constructor(message) {
|
||||
super('NotFoundException', Constants.res.notFound.code, message ? message : Constants.res.notFound.message);
|
||||
super("NotFoundException", Constants.res.notFound.code, message ? message : Constants.res.notFound.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 参数异常
|
||||
*/
|
||||
export class ParamException extends BaseException {
|
||||
constructor(message) {
|
||||
super('ParamException', Constants.res.param.code, message ? message : Constants.res.param.message);
|
||||
super("ParamException", Constants.res.param.code, message ? message : Constants.res.param.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 授权异常
|
||||
*/
|
||||
export class PermissionException extends BaseException {
|
||||
constructor(message?: string) {
|
||||
super('PermissionException', Constants.res.permission.code, message ? message : Constants.res.permission.message);
|
||||
super("PermissionException", Constants.res.permission.code, message ? message : Constants.res.permission.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 预览模式
|
||||
*/
|
||||
export class PreviewException extends BaseException {
|
||||
constructor(message) {
|
||||
super(
|
||||
'PreviewException',
|
||||
Constants.res.preview.code,
|
||||
message ? message : Constants.res.preview.message
|
||||
);
|
||||
super("PreviewException", Constants.res.preview.code, message ? message : Constants.res.preview.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
*/
|
||||
export class SiteOffException extends BaseException {
|
||||
constructor(message) {
|
||||
super('SiteOffException', Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message);
|
||||
super("SiteOffException", Constants.res.siteOff.code, message ? message : Constants.res.siteOff.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 校验异常
|
||||
*/
|
||||
export class ValidateException extends BaseException {
|
||||
constructor(message) {
|
||||
super('ValidateException', Constants.res.validation.code, message ? message : Constants.res.validation.message);
|
||||
super("ValidateException", Constants.res.validation.code, message ? message : Constants.res.validation.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 需要vip异常
|
||||
*/
|
||||
export class NeedVIPException extends BaseException {
|
||||
constructor(message) {
|
||||
super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
|
||||
super("NeedVIPException", Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
|
||||
}
|
||||
}
|
||||
|
||||
export class NeedSuiteException extends BaseException {
|
||||
constructor(message) {
|
||||
super('NeedSuiteException', Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message);
|
||||
super("NeedSuiteException", Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export * from './base-controller.js';
|
||||
export * from './constants.js';
|
||||
export * from './crud-controller.js';
|
||||
export * from './enum-item.js';
|
||||
export * from './exception/index.js';
|
||||
export * from './result.js';
|
||||
export * from './base-service.js';
|
||||
export * from "./mode.js"
|
||||
export * from "./base-controller.js";
|
||||
export * from "./constants.js";
|
||||
export * from "./crud-controller.js";
|
||||
export * from "./enum-item.js";
|
||||
export * from "./exception/index.js";
|
||||
export * from "./result.js";
|
||||
export * from "./base-service.js";
|
||||
export * from "./mode.js";
|
||||
export * from "./notification-constants.js";
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
let adminMode = "saas"
|
||||
import { logger } from "@certd/basic";
|
||||
import { isPlus } from "@certd/plus-core";
|
||||
|
||||
export function setAdminMode(mode:string = "saas"){
|
||||
adminMode = mode
|
||||
let adminMode = "saas";
|
||||
|
||||
export function setAdminMode(mode: string = "saas") {
|
||||
adminMode = mode;
|
||||
}
|
||||
export function getAdminMode(){
|
||||
return adminMode
|
||||
export function getAdminMode() {
|
||||
return adminMode;
|
||||
}
|
||||
|
||||
export function isEnterprise(){
|
||||
return adminMode === "enterprise"
|
||||
}
|
||||
export function isEnterprise() {
|
||||
const isEnterprise = adminMode === "enterprise";
|
||||
if (!isPlus()) {
|
||||
if (isEnterprise) {
|
||||
logger.warn("不是VIP,无法使用企业项目管理功能,退回普通模式");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return isEnterprise;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/// <reference types="mocha" />
|
||||
/// <reference types="node" />
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { isNotificationType, NotificationTypes } from "./notification-constants.js";
|
||||
|
||||
describe("NotificationTypes", () => {
|
||||
it("includes pipeline and server business notification types", () => {
|
||||
assert.equal(NotificationTypes.PipelineResult, "pipelineResult");
|
||||
assert.equal(NotificationTypes.CertDeployError, "certDeployError");
|
||||
assert.equal(NotificationTypes.ForgotPassword, "forgotPassword");
|
||||
});
|
||||
|
||||
it("recognizes registered notification types only", () => {
|
||||
assert.equal(isNotificationType(NotificationTypes.CertDeployError), true);
|
||||
assert.equal(isNotificationType("unknown"), false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { PipelineNotificationTypes } from "@certd/pipeline";
|
||||
|
||||
/** 服务端业务通知类型,包含流水线核心通知类型。 */
|
||||
export const NotificationTypes = {
|
||||
...PipelineNotificationTypes,
|
||||
Common: "common",
|
||||
CertApplySuccess: "certApplySuccess",
|
||||
CertPullError: "certPullError",
|
||||
CertDeployError: "certDeployError",
|
||||
SiteCheckError: "siteCheckError",
|
||||
SiteCertExpireRemind: "siteCertExpireRemind",
|
||||
DomainExpirationCheck: "domainExpirationCheck",
|
||||
VipExpireRemind: "vipExpireRemind",
|
||||
UserExpireRemind: "userExpireRemind",
|
||||
RegisterCode: "registerCode",
|
||||
ForgotPassword: "forgotPassword",
|
||||
GithubReleaseCheck: "githubReleaseCheck",
|
||||
} as const;
|
||||
|
||||
export type NotificationTypeValue = (typeof NotificationTypes)[keyof typeof NotificationTypes];
|
||||
|
||||
export function isNotificationType(value: string): value is NotificationTypeValue {
|
||||
return Object.values(NotificationTypes).includes(value as NotificationTypeValue);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { IMidwayContainer } from '@midwayjs/core';
|
||||
import { Configuration } from '@midwayjs/core';
|
||||
import { logger } from '@certd/basic';
|
||||
import type { IMidwayContainer } from "@midwayjs/core";
|
||||
import { Configuration } from "@midwayjs/core";
|
||||
import { logger } from "@certd/basic";
|
||||
@Configuration({
|
||||
namespace: 'lib-server',
|
||||
namespace: "lib-server",
|
||||
})
|
||||
export class LibServerConfiguration {
|
||||
async onReady(container: IMidwayContainer) {
|
||||
logger.info('lib start...');
|
||||
logger.info("lib start...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SysSettingsEntity } from './system/index.js';
|
||||
import { AccessEntity } from './user/access/entity/access.js';
|
||||
import { SysSettingsEntity } from "./system/index.js";
|
||||
import { AccessEntity } from "./user/access/entity/access.js";
|
||||
import { AddonEntity } from "./user/index.js";
|
||||
export * from './basic/index.js';
|
||||
export * from './system/index.js';
|
||||
export * from './user/index.js';
|
||||
export { LibServerConfiguration as Configuration } from './configuration.js';
|
||||
export * from "./basic/index.js";
|
||||
export * from "./system/index.js";
|
||||
export * from "./user/index.js";
|
||||
export { LibServerConfiguration as Configuration } from "./configuration.js";
|
||||
|
||||
export const libServerEntities = [SysSettingsEntity, AccessEntity,AddonEntity];
|
||||
export const libServerEntities = [SysSettingsEntity, AccessEntity, AddonEntity];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './service/plus-service.js';
|
||||
export * from './service/file-service.js';
|
||||
export * from './service/encryptor.js';
|
||||
export * from './service/ocr-service.js';
|
||||
export * from './service/executor-queue.js';
|
||||
export * from "./service/plus-service.js";
|
||||
export * from "./service/file-service.js";
|
||||
export * from "./service/encryptor.js";
|
||||
export * from "./service/ocr-service.js";
|
||||
export * from "./service/executor-queue.js";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import crypto from 'crypto';
|
||||
import crypto from "crypto";
|
||||
|
||||
export class Encryptor {
|
||||
secretKey: Buffer;
|
||||
constructor(encryptSecret: string, encoding: BufferEncoding = 'base64') {
|
||||
constructor(encryptSecret: string, encoding: BufferEncoding = "base64") {
|
||||
this.secretKey = Buffer.from(encryptSecret, encoding);
|
||||
}
|
||||
// 加密函数
|
||||
@@ -10,18 +10,18 @@ export class Encryptor {
|
||||
const iv = crypto.randomBytes(16); // 初始化向量
|
||||
// const secretKey = crypto.randomBytes(32);
|
||||
// const key = Buffer.from(secretKey);
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', this.secretKey, iv);
|
||||
const cipher = crypto.createCipheriv("aes-256-cbc", this.secretKey, iv);
|
||||
let encrypted = cipher.update(text);
|
||||
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
||||
return iv.toString("hex") + ":" + encrypted.toString("hex");
|
||||
}
|
||||
|
||||
// 解密函数
|
||||
decrypt(encryptedText: string) {
|
||||
const textParts = encryptedText.split(':');
|
||||
const iv = Buffer.from(textParts.shift(), 'hex');
|
||||
const encrypted = Buffer.from(textParts.join(':'), 'hex');
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this.secretKey), iv);
|
||||
const textParts = encryptedText.split(":");
|
||||
const iv = Buffer.from(textParts.shift(), "hex");
|
||||
const encrypted = Buffer.from(textParts.join(":"), "hex");
|
||||
const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(this.secretKey), iv);
|
||||
let decrypted = decipher.update(encrypted);
|
||||
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||
return decrypted.toString();
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { logger } from "@certd/basic";
|
||||
|
||||
export type TaskItem = {
|
||||
task: ()=>Promise<void>;
|
||||
}
|
||||
task: () => Promise<void>;
|
||||
};
|
||||
|
||||
export class UserTaskQueue{
|
||||
export class UserTaskQueue {
|
||||
userId: number;
|
||||
pendingQueue: TaskItem[] = [];
|
||||
runningQueue: TaskItem[] = [];
|
||||
getMaxRunningCount: ()=>number ;
|
||||
getMaxRunningCount: () => number;
|
||||
|
||||
constructor(req: { userId: number ,getMaxRunningCount: ()=>number }) {
|
||||
constructor(req: { userId: number; getMaxRunningCount: () => number }) {
|
||||
this.userId = req.userId;
|
||||
this.getMaxRunningCount = req.getMaxRunningCount ;
|
||||
this.getMaxRunningCount = req.getMaxRunningCount;
|
||||
}
|
||||
|
||||
addTask(task: TaskItem) {
|
||||
@@ -34,10 +34,10 @@ export class UserTaskQueue{
|
||||
}
|
||||
// 执行任务
|
||||
this.runningQueue.push(task);
|
||||
const call = async ()=>{
|
||||
try{
|
||||
const call = async () => {
|
||||
try {
|
||||
await task.task();
|
||||
}finally{
|
||||
} finally {
|
||||
// 任务执行完成,从运行队列中移除
|
||||
const index = this.runningQueue.indexOf(task);
|
||||
if (index > -1) {
|
||||
@@ -46,17 +46,16 @@ export class UserTaskQueue{
|
||||
// 继续执行下一个任务
|
||||
this.runTask();
|
||||
}
|
||||
}
|
||||
};
|
||||
logger.info(`[user_${this.userId}]执行任务,当前运行队列:${this.runningQueue.length}, 等待队列:${this.pendingQueue.length}`);
|
||||
call()
|
||||
call();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExecutorQueue{
|
||||
export class ExecutorQueue {
|
||||
queues: Record<number, UserTaskQueue> = {};
|
||||
maxRunningCount: number = 10;
|
||||
|
||||
|
||||
setMaxRunningCount(count: number) {
|
||||
this.maxRunningCount = count;
|
||||
}
|
||||
@@ -64,7 +63,7 @@ export class ExecutorQueue{
|
||||
getUserQueue(userId: number) {
|
||||
const userQueue = this.queues[userId];
|
||||
if (!userQueue) {
|
||||
this.queues[userId] = new UserTaskQueue({ userId, getMaxRunningCount: ()=>this.maxRunningCount });
|
||||
this.queues[userId] = new UserTaskQueue({ userId, getMaxRunningCount: () => this.maxRunningCount });
|
||||
}
|
||||
return this.queues[userId];
|
||||
}
|
||||
@@ -73,7 +72,6 @@ export class ExecutorQueue{
|
||||
const userQueue = this.getUserQueue(userId);
|
||||
userQueue.addTask(task);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const executorQueue = new ExecutorQueue();
|
||||
export const executorQueue = new ExecutorQueue();
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import dayjs from 'dayjs';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { cache, logger, utils } from '@certd/basic';
|
||||
import { NotFoundException, ParamException, PermissionException } from '../../../basic/index.js';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import dayjs from "dayjs";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { cache, logger, utils } from "@certd/basic";
|
||||
import { NotFoundException, ParamException, PermissionException } from "../../../basic/index.js";
|
||||
|
||||
export type UploadFileItem = {
|
||||
filename: string;
|
||||
tmpFilePath: string;
|
||||
};
|
||||
const uploadRootDir = './data/upload';
|
||||
export const uploadTmpFileCacheKey = 'tmpfile_key_';
|
||||
const uploadRootDir = "./data/upload";
|
||||
export const uploadTmpFileCacheKey = "tmpfile_key_";
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class FileService {
|
||||
async saveFile(userId: number, tmpCacheKey: any, permission: 'public' | 'private') {
|
||||
async saveFile(userId: number, tmpCacheKey: any, permission: "public" | "private") {
|
||||
if (tmpCacheKey.startsWith(`/${permission}`)) {
|
||||
//已经保存过,不需要再次保存
|
||||
return tmpCacheKey;
|
||||
}
|
||||
let fileName = '';
|
||||
let fileName = "";
|
||||
let tmpFilePath = tmpCacheKey;
|
||||
if (uploadTmpFileCacheKey && tmpCacheKey.startsWith(uploadTmpFileCacheKey)) {
|
||||
const tmpFile: UploadFileItem = cache.get(tmpCacheKey);
|
||||
if (!tmpFile) {
|
||||
throw new ParamException('文件已过期,请重新上传');
|
||||
throw new ParamException("文件已过期,请重新上传");
|
||||
}
|
||||
tmpFilePath = tmpFile.tmpFilePath;
|
||||
fileName = tmpFile.filename || path.basename(tmpFilePath);
|
||||
}
|
||||
if (!tmpFilePath || !fs.existsSync(tmpFilePath)) {
|
||||
throw new Error('文件不存在,请重新上传');
|
||||
throw new Error("文件不存在,请重新上传");
|
||||
}
|
||||
const date = dayjs().format('YYYY_MM_DD');
|
||||
const date = dayjs().format("YYYY_MM_DD");
|
||||
const random = Math.random().toString(36).substring(7);
|
||||
const userIdMd5 = Buffer.from(Buffer.from(userId + '').toString('base64')).toString('hex');
|
||||
const userIdMd5 = Buffer.from(Buffer.from(userId + "").toString("base64")).toString("hex");
|
||||
const key = `/${permission}/${userIdMd5}/${date}/${random}_${fileName}`;
|
||||
let savePath = path.join(uploadRootDir, key);
|
||||
savePath = path.resolve(savePath);
|
||||
@@ -44,7 +44,6 @@ export class FileService {
|
||||
if (!fs.existsSync(parentDir)) {
|
||||
fs.mkdirSync(parentDir, { recursive: true });
|
||||
}
|
||||
// eslint-disable-next-line node/no-unsupported-features/node-builtins
|
||||
const copyFile = utils.promises.promisify(fs.copyFile);
|
||||
await copyFile(tmpFilePath, savePath);
|
||||
try {
|
||||
@@ -58,29 +57,29 @@ export class FileService {
|
||||
|
||||
getFile(key: string, userId?: number, allowAnyPrivateUser = false) {
|
||||
if (!key) {
|
||||
throw new ParamException('参数错误');
|
||||
throw new ParamException("参数错误");
|
||||
}
|
||||
if (key.indexOf('..') >= 0) {
|
||||
if (key.indexOf("..") >= 0) {
|
||||
//安全性判断
|
||||
throw new ParamException('参数错误');
|
||||
throw new ParamException("参数错误");
|
||||
}
|
||||
if (!key.startsWith('/')) {
|
||||
throw new ParamException('参数错误');
|
||||
if (!key.startsWith("/")) {
|
||||
throw new ParamException("参数错误");
|
||||
}
|
||||
const keyArr = key.split('/');
|
||||
const keyArr = key.split("/");
|
||||
const permission = keyArr[1];
|
||||
const userIdMd5 = keyArr[2];
|
||||
if (permission !== 'public' && !allowAnyPrivateUser) {
|
||||
if (permission !== "public" && !allowAnyPrivateUser) {
|
||||
//非公开文件需要验证用户
|
||||
const userIdStr = Buffer.from(Buffer.from(userIdMd5, 'hex').toString('base64')).toString();
|
||||
const userIdStr = Buffer.from(Buffer.from(userIdMd5, "hex").toString("base64")).toString();
|
||||
const userIdInt: number = parseInt(userIdStr, 10);
|
||||
if (userId == null || userIdInt !== userId) {
|
||||
throw new PermissionException('无访问权限');
|
||||
throw new PermissionException("无访问权限");
|
||||
}
|
||||
}
|
||||
const filePath = path.join(uploadRootDir, key);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new NotFoundException('文件不存在');
|
||||
throw new NotFoundException("文件不存在");
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ export class OcrService implements IOcrService {
|
||||
url: "/activation/certd/ocr",
|
||||
method: "post",
|
||||
data: {
|
||||
image: opts.image
|
||||
}
|
||||
image: opts.image,
|
||||
},
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './settings/index.js';
|
||||
export * from './basic/index.js';
|
||||
export * from "./settings/index.js";
|
||||
export * from "./basic/index.js";
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './service/sys-settings-service.js';
|
||||
export * from './service/models.js';
|
||||
export * from './entity/sys-settings.js';
|
||||
export * from "./service/sys-settings-service.js";
|
||||
export * from "./service/models.js";
|
||||
export * from "./entity/sys-settings.js";
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SysSettingsEntity } from '../entity/sys-settings.js';
|
||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from './models.js';
|
||||
import { Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { SysSettingsEntity } from "../entity/sys-settings.js";
|
||||
import { BaseSettings, SysInstallInfo, SysPrivateSettings, SysPublicSettings, SysSecret, SysSecretBackup } from "./models.js";
|
||||
|
||||
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from '@certd/acme-client';
|
||||
import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from '@certd/basic';
|
||||
import { isPlus } from '@certd/plus-core';
|
||||
import * as dns from 'node:dns';
|
||||
import { BaseService, setAdminMode } from '../../../basic/index.js';
|
||||
import { executorQueue } from '../../basic/service/executor-queue.js';
|
||||
import { getAllSslProviderDomains, setSslProviderReverseProxies, setWalkFromAuthoritative } from "@certd/acme-client";
|
||||
import { cache, logger, mergeUtils, setGlobalHeaders, setGlobalProxy } from "@certd/basic";
|
||||
import { isPlus } from "@certd/plus-core";
|
||||
import * as dns from "node:dns";
|
||||
import { BaseService, setAdminMode } from "../../../basic/index.js";
|
||||
import { executorQueue } from "../../basic/service/executor-queue.js";
|
||||
const { merge } = mergeUtils;
|
||||
|
||||
let lastSaveEnvVars = {};
|
||||
@@ -138,7 +138,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
res.reverseProxies[domain] = "";
|
||||
}
|
||||
}
|
||||
return res
|
||||
return res;
|
||||
}
|
||||
|
||||
async savePrivateSettings(bean: SysPrivateSettings) {
|
||||
@@ -149,14 +149,14 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
}
|
||||
|
||||
async reloadSettings() {
|
||||
await this.reloadPrivateSettings()
|
||||
await this.reloadPublicSettings()
|
||||
await this.reloadPrivateSettings();
|
||||
await this.reloadPublicSettings();
|
||||
}
|
||||
|
||||
async reloadPublicSettings() {
|
||||
const publicSetting = await this.getPublicSettings()
|
||||
if (isPlus()){
|
||||
setAdminMode(publicSetting.adminMode )
|
||||
const publicSetting = await this.getPublicSettings();
|
||||
if (isPlus()) {
|
||||
setAdminMode(publicSetting.adminMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
};
|
||||
setGlobalProxy(opts);
|
||||
setGlobalHeaders(this.parseKeyValueText(privateSetting.commonHeaders));
|
||||
|
||||
|
||||
if (privateSetting.dnsResultOrder) {
|
||||
dns.setDefaultResultOrder(privateSetting.dnsResultOrder as any);
|
||||
}
|
||||
@@ -183,29 +183,28 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
this.setEnvironmentVars(privateSetting.environmentVars);
|
||||
|
||||
setWalkFromAuthoritative(privateSetting.acmeWalkFromAuthoritative);
|
||||
|
||||
}
|
||||
|
||||
parseKeyValueText(text: string) {
|
||||
const values = {};
|
||||
if (typeof text !== 'string') {
|
||||
if (typeof text !== "string") {
|
||||
text = "";
|
||||
}
|
||||
text.split('\n').forEach(line => {
|
||||
text.split("\n").forEach(line => {
|
||||
line = line.trim();
|
||||
if (!line || line.startsWith('#')) {
|
||||
return
|
||||
if (!line || line.startsWith("#")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const arr = line.split("#")
|
||||
const arr = line.split("#");
|
||||
if (arr.length > 0) {
|
||||
line = arr[0].trim();
|
||||
}
|
||||
if (!line.includes("=")) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const eqIndex = line.indexOf('=');
|
||||
const eqIndex = line.indexOf("=");
|
||||
const key = line.substring(0, eqIndex).trim();
|
||||
const value = line.substring(eqIndex + 1).trim();
|
||||
if (key && value) {
|
||||
@@ -220,7 +219,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
//先删除旧环境变量
|
||||
if (lastSaveEnvVars) {
|
||||
for (const key in lastSaveEnvVars) {
|
||||
delete process.env[key];
|
||||
delete process.env[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +233,7 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
entity.setting = JSON.stringify(setting);
|
||||
await this.repository.save(entity);
|
||||
} else {
|
||||
throw new Error('该设置不存在');
|
||||
throw new Error("该设置不存在");
|
||||
}
|
||||
cache.delete(`settings.${key}`);
|
||||
}
|
||||
@@ -246,20 +245,20 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
if (settings == null) {
|
||||
const backup = new SysSecretBackup();
|
||||
if (installInfo.siteId == null || privateSettings.encryptSecret == null) {
|
||||
logger.error('备份密钥失败,siteId或encryptSecret为空');
|
||||
logger.error("备份密钥失败,siteId或encryptSecret为空");
|
||||
return;
|
||||
}
|
||||
backup.siteId = installInfo.siteId;
|
||||
backup.encryptSecret = privateSettings.encryptSecret;
|
||||
await this.saveSetting(backup);
|
||||
logger.info('备份密钥成功');
|
||||
logger.info("备份密钥成功");
|
||||
} else {
|
||||
//校验是否有变化
|
||||
if (settings.siteId !== installInfo.siteId) {
|
||||
throw new Error(`siteId与备份不一致,可能是数据异常,请检查:backup=${settings.siteId}, current=${installInfo.siteId}`);
|
||||
}
|
||||
if (settings.encryptSecret !== privateSettings.encryptSecret) {
|
||||
throw new Error('encryptSecret与备份不一致,可能是数据异常,请检查');
|
||||
throw new Error("encryptSecret与备份不一致,可能是数据异常,请检查");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,12 +270,12 @@ export class SysSettingsService extends BaseService<SysSettingsEntity> {
|
||||
//从备份中读取
|
||||
const settings = await this.getSettingByKey(SysSecretBackup.__key__);
|
||||
if (settings == null || !settings.encryptSecret) {
|
||||
throw new Error('密钥备份不存在');
|
||||
throw new Error("密钥备份不存在");
|
||||
}
|
||||
sysSecret.siteId = settings.siteId;
|
||||
sysSecret.encryptSecret = settings.encryptSecret;
|
||||
await this.saveSetting(sysSecret);
|
||||
logger.info('密钥恢复成功');
|
||||
logger.info("密钥恢复成功");
|
||||
return sysSecret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
* 授权配置
|
||||
*/
|
||||
@Entity('cd_access')
|
||||
@Entity("cd_access")
|
||||
export class AccessEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column({ name: 'key_id', comment: 'key_id', length: 100 })
|
||||
@Column({ name: "key_id", comment: "key_id", length: 100 })
|
||||
keyId: string;
|
||||
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number; // 0为系统级别, -1为企业,大于1为用户
|
||||
|
||||
@Column({ comment: '名称', length: 100 })
|
||||
@Column({ comment: "名称", length: 100 })
|
||||
name: string;
|
||||
|
||||
@Column({ comment: '类型', length: 100 })
|
||||
@Column({ comment: "类型", length: 100 })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'subtype', comment: '子类型', length: 100, nullable: true })
|
||||
@Column({ name: "subtype", comment: "子类型", length: 100, nullable: true })
|
||||
subtype: string;
|
||||
|
||||
@Column({ name: 'setting', comment: '设置', length: 10240, nullable: true })
|
||||
@Column({ name: "setting", comment: "设置", length: 10240, nullable: true })
|
||||
setting: string;
|
||||
|
||||
@Column({ name: 'encrypt_setting', comment: '已加密设置', length: 10240, nullable: true })
|
||||
@Column({ name: "encrypt_setting", comment: "已加密设置", length: 10240, nullable: true })
|
||||
encryptSetting: string;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
name: "create_time",
|
||||
comment: "创建时间",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
comment: '修改时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
name: "update_time",
|
||||
comment: "修改时间",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './entity/access.js';
|
||||
export * from './service/access-service.js';
|
||||
export * from './service/access-sys-getter.js';
|
||||
export * from './service/access-getter.js';
|
||||
export * from './service/encrypt-service.js';
|
||||
export * from "./entity/access.js";
|
||||
export * from "./service/access-service.js";
|
||||
export * from "./service/access-sys-getter.js";
|
||||
export * from "./service/access-getter.js";
|
||||
export * from "./service/encrypt-service.js";
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import { IAccessService, IRuntimeDepsService } from "@certd/pipeline";
|
||||
|
||||
export type AccessRuntimeDepsService = IRuntimeDepsService;
|
||||
import { IAccessService } from "@certd/pipeline";
|
||||
|
||||
export class AccessGetter implements IAccessService {
|
||||
userId: number;
|
||||
projectId?: number;
|
||||
runtimeDepsService?: AccessRuntimeDepsService;
|
||||
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
|
||||
) {
|
||||
getter: <T>(id: any, userId?: number, projectId?: number, ignorePermission?: boolean) => Promise<T>;
|
||||
constructor(userId: number, projectId: number, getter: (id: any, userId: number, projectId?: number, ignorePermission?: boolean) => Promise<any>) {
|
||||
this.userId = userId;
|
||||
this.projectId = projectId;
|
||||
this.getter = getter;
|
||||
this.runtimeDepsService = runtimeDepsService;
|
||||
}
|
||||
|
||||
async getById<T = any>(id: any) {
|
||||
return await this.getter<T>(id, this.userId, this.projectId, false, this.runtimeDepsService);
|
||||
return await this.getter<T>(id, this.userId, this.projectId, false);
|
||||
}
|
||||
|
||||
async getCommonById<T = any>(id: any) {
|
||||
return await this.getter<T>(id, 0, null, false, this.runtimeDepsService);
|
||||
return await this.getter<T>(id, 0, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { ApplicationContext, Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import type { IMidwayContainer } from "@midwayjs/core";
|
||||
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { AccessGetter, BaseService, PageReq, PermissionException, ValidateException } from "../../../index.js";
|
||||
import type { AccessRuntimeDepsService } from "./access-getter.js";
|
||||
import { AccessEntity } from "../entity/access.js";
|
||||
import { AccessDefine, accessRegistry, newAccess } from "@certd/pipeline";
|
||||
import { EncryptService } from "./encrypt-service.js";
|
||||
@@ -20,6 +20,9 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
@Inject()
|
||||
encryptService: EncryptService;
|
||||
|
||||
@ApplicationContext()
|
||||
applicationContext: IMidwayContainer;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
@@ -161,7 +164,7 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
};
|
||||
}
|
||||
|
||||
async getAccessById(id: any, checkUserId: boolean, userId?: number, projectId?: number, runtimeDepsService?: AccessRuntimeDepsService): Promise<any> {
|
||||
async getAccessById(id: any, checkUserId: boolean, userId?: number, projectId?: number): Promise<any> {
|
||||
const entity = await this.info(id);
|
||||
if (entity == null) {
|
||||
throw new Error(`该授权配置不存在,请确认是否已被删除:id=${id}`);
|
||||
@@ -184,20 +187,23 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
id: entity.id,
|
||||
...setting,
|
||||
};
|
||||
const taskServiceBuilder: any = await this.applicationContext.getAsync("taskServiceBuilder");
|
||||
const serviceGetter = taskServiceBuilder.create({ userId: userId || 0, projectId });
|
||||
const getAccessById = this.getById.bind(this);
|
||||
const accessGetter = new AccessGetter(userId, projectId, getAccessById, runtimeDepsService);
|
||||
const accessGetter = new AccessGetter(userId, projectId, getAccessById);
|
||||
const accessContext = {
|
||||
logger,
|
||||
http,
|
||||
utils,
|
||||
accessService: accessGetter,
|
||||
serviceGetter,
|
||||
} as any;
|
||||
const access = await newAccess(entity.type, input, accessGetter, accessContext);
|
||||
return access;
|
||||
}
|
||||
|
||||
async getById(id: any, userId: number, projectId?: number, _ignorePermission?: boolean, runtimeDepsService?: AccessRuntimeDepsService): Promise<any> {
|
||||
return await this.getAccessById(id, true, userId, projectId, runtimeDepsService);
|
||||
async getById(id: any, userId: number, projectId?: number, _ignorePermission?: boolean): Promise<any> {
|
||||
return await this.getAccessById(id, true, userId, projectId);
|
||||
}
|
||||
|
||||
decryptAccessEntity(entity: AccessEntity): any {
|
||||
@@ -283,4 +289,15 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
await this.repository.save(newAccess);
|
||||
return newAccess.id;
|
||||
}
|
||||
|
||||
async getDefaultByType({ type, userId, projectId, subtype }) {
|
||||
return await this.repository.findOne({
|
||||
where: {
|
||||
type,
|
||||
userId,
|
||||
projectId,
|
||||
subtype,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IAccessService } from '@certd/pipeline';
|
||||
import { AccessService } from './access-service.js';
|
||||
import { IAccessService } from "@certd/pipeline";
|
||||
import { AccessService } from "./access-service.js";
|
||||
|
||||
export class AccessSysGetter implements IAccessService {
|
||||
accessService: AccessService;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { Encryptor, SysSecret, SysSettingsService } from '../../../system/index.js';
|
||||
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||
import { Encryptor, SysSecret, SysSettingsService } from "../../../system/index.js";
|
||||
|
||||
/**
|
||||
* 授权
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { HttpClient, ILogger, utils } from "@certd/basic";
|
||||
import {upperFirst} from "lodash-es";
|
||||
import {
|
||||
accessRegistry,
|
||||
FormItemProps,
|
||||
IAccessService,
|
||||
IRuntimeDepsService,
|
||||
IServiceGetter,
|
||||
PluginRequestHandleReq,
|
||||
Registrable
|
||||
} from "@certd/pipeline";
|
||||
|
||||
import { upperFirst } from "lodash-es";
|
||||
import { accessRegistry, FormItemProps, IAccessService, IServiceGetter, PluginRequestHandleReq, Registrable, getRuntimeDepsService } from "@certd/pipeline";
|
||||
|
||||
export type AddonRequestHandleReqInput<T = any> = {
|
||||
id?: number;
|
||||
@@ -19,7 +10,7 @@ export type AddonRequestHandleReqInput<T = any> = {
|
||||
|
||||
export type AddonRequestHandleReq<T = any> = {
|
||||
addonType: string;
|
||||
} &PluginRequestHandleReq<AddonRequestHandleReqInput<T>>;
|
||||
} & PluginRequestHandleReq<AddonRequestHandleReqInput<T>>;
|
||||
|
||||
export type AddonInputDefine = FormItemProps & {
|
||||
title: string;
|
||||
@@ -48,8 +39,6 @@ export type AddonInstanceConfig = {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
export interface IAddon {
|
||||
ctx: AddonContext;
|
||||
[key: string]: any;
|
||||
@@ -67,13 +56,9 @@ export abstract class BaseAddon implements IAddon {
|
||||
ctx!: AddonContext;
|
||||
http!: HttpClient;
|
||||
logger!: ILogger;
|
||||
runtimeDepsService?: IRuntimeDepsService;
|
||||
|
||||
async importRuntime(specifier: string) {
|
||||
if (!this.runtimeDepsService) {
|
||||
return await import(specifier);
|
||||
}
|
||||
return await this.runtimeDepsService.importRuntime(specifier, this.logger);
|
||||
return await getRuntimeDepsService().importRuntime(specifier, this.logger);
|
||||
}
|
||||
|
||||
title!: string;
|
||||
@@ -85,7 +70,7 @@ export abstract class BaseAddon implements IAddon {
|
||||
if (accessId == null) {
|
||||
throw new Error("您还没有配置授权");
|
||||
}
|
||||
const accessService = await this.ctx.serviceGetter.get<IAccessService>("accessService")
|
||||
const accessService = await this.ctx.serviceGetter.get<IAccessService>("accessService");
|
||||
let res: any = null;
|
||||
if (isCommon) {
|
||||
res = await accessService.getCommonById(accessId);
|
||||
@@ -118,18 +103,12 @@ export abstract class BaseAddon implements IAddon {
|
||||
this.ctx = ctx;
|
||||
this.http = ctx.http;
|
||||
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;
|
||||
};
|
||||
|
||||
async onRequest(req:AddonRequestHandleReq) {
|
||||
async onRequest(req: AddonRequestHandleReq) {
|
||||
if (!req.action) {
|
||||
throw new Error("action is required");
|
||||
}
|
||||
@@ -147,10 +126,8 @@ export abstract class BaseAddon implements IAddon {
|
||||
}
|
||||
throw new Error(`action ${req.action} not found`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export interface IAddonGetter {
|
||||
getById<T = any>(id: any): Promise<T>;
|
||||
getCommonById<T = any>(id: any): Promise<T>;
|
||||
|
||||
@@ -48,8 +48,8 @@ export function AddonInput(input?: AddonInputDefine): PropertyDecorator {
|
||||
};
|
||||
}
|
||||
|
||||
export async function newAddon(addonType:string,type: string, input: any, ctx: AddonContext) {
|
||||
const key = `${addonType}:${type}`
|
||||
export async function newAddon(addonType: string, type: string, input: any, ctx: AddonContext) {
|
||||
const key = `${addonType}:${type}`;
|
||||
const register = addonRegistry.get(key);
|
||||
if (register == null) {
|
||||
throw new Error(`${addonType} ${type} not found`);
|
||||
|
||||
@@ -1,49 +1,46 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
|
||||
/**
|
||||
*/
|
||||
@Entity('cd_addon')
|
||||
@Entity("cd_addon")
|
||||
export class AddonEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
@Column({ name: 'key_id', comment: 'key_id', length: 100 })
|
||||
@Column({ name: "key_id", comment: "key_id", length: 100 })
|
||||
keyId: string;
|
||||
@Column({ name: 'user_id', comment: '用户id' })
|
||||
@Column({ name: "user_id", comment: "用户id" })
|
||||
userId: number;
|
||||
@Column({ comment: '名称', length: 100 })
|
||||
@Column({ comment: "名称", length: 100 })
|
||||
name: string;
|
||||
|
||||
|
||||
@Column({ name: 'addon_type', comment: 'addon类型', length: 100 })
|
||||
@Column({ name: "addon_type", comment: "addon类型", length: 100 })
|
||||
addonType: string;
|
||||
|
||||
|
||||
@Column({ comment: '类型', length: 100 })
|
||||
@Column({ comment: "类型", length: 100 })
|
||||
type: string;
|
||||
|
||||
@Column({ name: 'setting', comment: '设置', length: 10240, nullable: true })
|
||||
@Column({ name: "setting", comment: "设置", length: 10240, nullable: true })
|
||||
setting: string;
|
||||
|
||||
@Column({ name: 'is_system', comment: '是否系统级别', nullable: false, default: false })
|
||||
@Column({ name: "is_system", comment: "是否系统级别", nullable: false, default: false })
|
||||
isSystem: boolean;
|
||||
|
||||
@Column({ name: 'is_default', comment: '是否默认', nullable: false, default: false })
|
||||
@Column({ name: "is_default", comment: "是否默认", nullable: false, default: false })
|
||||
isDefault: boolean;
|
||||
|
||||
@Column({ name: 'project_id', comment: '项目id' })
|
||||
@Column({ name: "project_id", comment: "项目id" })
|
||||
projectId: number;
|
||||
|
||||
|
||||
@Column({
|
||||
name: 'create_time',
|
||||
comment: '创建时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
name: "create_time",
|
||||
comment: "创建时间",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
createTime: Date;
|
||||
@Column({
|
||||
name: 'update_time',
|
||||
comment: '修改时间',
|
||||
default: () => 'CURRENT_TIMESTAMP',
|
||||
name: "update_time",
|
||||
comment: "修改时间",
|
||||
default: () => "CURRENT_TIMESTAMP",
|
||||
})
|
||||
updateTime: Date;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './api/index.js'
|
||||
export * from './entity/addon.js'
|
||||
export * from './service/addon-service.js'
|
||||
export * from "./api/index.js";
|
||||
export * from "./entity/addon.js";
|
||||
export * from "./service/addon-service.js";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user