Compare commits

...

16 Commits

Author SHA1 Message Date
xiaojunnuo
6b109d172f perf: 腾讯云CLB大区增加台北 2026-04-03 11:02:39 +08:00
xiaojunnuo
6b29972399 chore: 修复可选链操作符和DNS管理插件问题
修复多处可选链操作符访问问题,避免潜在的空指针异常
优化DNS管理插件,移除重复的id字段并修正域名匹配逻辑
添加getDomainListPage方法以支持分页查询域名列表
2026-04-03 00:32:00 +08:00
xiaojunnuo
0fcd3c09fd Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-03 00:14:14 +08:00
xiaojunnuo
af503442b8 perf(plugin-dnsmgr): 添加彩虹DNS插件支持
实现彩虹DNS管理系统的插件集成,包括DNS记录创建、查询和删除功能
2026-04-03 00:14:08 +08:00
xiaojunnuo
c875971b71 perf: 优化腾讯云CLB插件支持选择证书id 2026-04-02 23:27:10 +08:00
xiaojunnuo
d1a65922d7 fix: 修复某些情况下报无法修改通知的问题 2026-04-02 16:28:14 +08:00
xiaojunnuo
6ef34f95d5 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-02 14:51:54 +08:00
xiaojunnuo
8b79022179 chore: 1 2026-04-02 09:05:13 +08:00
xiaojunnuo
21aec77e5c perf(spaceship): 新增Spaceship DNS插件和授权模块
添加Spaceship DNS提供商插件和授权模块,支持域名解析管理
更新相关文档和技能说明,优化错误处理和日志记录
移除调试日志,更新README项目列表
2026-04-02 00:10:28 +08:00
xiaojunnuo
74c5259af8 build: release 2026-04-01 00:29:52 +08:00
xiaojunnuo
a3e7d4414d build: publish 2026-03-31 23:59:12 +08:00
xiaojunnuo
986d32eb81 build: trigger build image 2026-03-31 23:59:00 +08:00
xiaojunnuo
de0ae14544 v1.39.8 2026-03-31 23:57:25 +08:00
xiaojunnuo
6b52276fb6 build: prepare to build 2026-03-31 23:54:34 +08:00
xiaojunnuo
a19ea7489c perf: 支持部署证书到百度CCE 2026-03-31 23:52:12 +08:00
xiaojunnuo
14229c2f00 chore: delete agents 2026-03-31 22:27:42 +08:00
69 changed files with 1242 additions and 946 deletions

View File

@@ -1,115 +0,0 @@
---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---
<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>
<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>
## Instruction Priority
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
## How to Access Skills
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills
## The Rule
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
```dot
digraph skill_flow {
"User message received" [shape=doublecircle];
"About to EnterPlanMode?" [shape=doublecircle];
"Already brainstormed?" [shape=diamond];
"Invoke brainstorming skill" [shape=box];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"About to EnterPlanMode?" -> "Already brainstormed?";
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
"Invoke brainstorming skill" -> "Might any skill apply?";
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
```
## Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
## Skill Priority
When multiple skills could apply, use this order:
1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.
## Skill Types
**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
**Flexible** (patterns): Adapt principles to context.
The skill itself tells you which.
## User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

View File

@@ -1,100 +0,0 @@
# Codex Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Codex equivalent |
|-----------------|------------------|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
| Task returns result | `wait` |
| Task completes automatically | `close_agent` to free slot |
| `TodoWrite` (task tracking) | `update_plan` |
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
| `Bash` (run commands) | Use your native shell tools |
## Subagent dispatch requires multi-agent support
Add to your Codex config (`~/.codex/config.toml`):
```toml
[features]
multi_agent = true
```
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
## Named agent dispatch
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
Codex does not have a named agent registry — `spawn_agent` creates generic agents
from built-in roles (`default`, `explorer`, `worker`).
When a skill says to dispatch a named agent type:
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
local prompt template like `code-quality-reviewer-prompt.md`)
2. Read the prompt content
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
4. Spawn a `worker` agent with the filled content as the `message`
| Skill instruction | Codex equivalent |
|-------------------|------------------|
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
### Message framing
The `message` parameter is user-level input, not a system prompt. Structure it
for maximum instruction adherence:
```
Your task is to perform the following. Follow the instructions below exactly.
<agent-instructions>
[filled prompt content from the agent's .md file]
</agent-instructions>
Execute this now. Output ONLY the structured response following the format
specified in the instructions above.
```
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
- End with an explicit execution directive to prevent summarization of the instructions
### When this workaround can be removed
This approach compensates for Codex's plugin system not yet supporting an `agents`
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
skills can dispatch named agent types directly.
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.

View File

@@ -1,33 +0,0 @@
# Gemini CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Gemini CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `read_file` |
| `Write` (file creation) | `write_file` |
| `Edit` (file editing) | `replace` |
| `Bash` (run commands) | `run_shell_command` |
| `Grep` (search file content) | `grep_search` |
| `Glob` (search files by name) | `glob` |
| `TodoWrite` (task tracking) | `write_todos` |
| `Skill` tool (invoke a skill) | `activate_skill` |
| `WebSearch` | `google_web_search` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## No subagent support
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
## Additional Gemini CLI tools
These tools are available in Gemini CLI but have no Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `list_directory` | List files and subdirectories |
| `save_memory` | Persist facts to GEMINI.md across sessions |
| `ask_user` | Request structured input from the user |
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |

View File

@@ -1,115 +0,0 @@
---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---
<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>
<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>
## Instruction Priority
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
## How to Access Skills
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills
## The Rule
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
```dot
digraph skill_flow {
"User message received" [shape=doublecircle];
"About to EnterPlanMode?" [shape=doublecircle];
"Already brainstormed?" [shape=diamond];
"Invoke brainstorming skill" [shape=box];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"About to EnterPlanMode?" -> "Already brainstormed?";
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
"Invoke brainstorming skill" -> "Might any skill apply?";
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
```
## Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
## Skill Priority
When multiple skills could apply, use this order:
1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.
## Skill Types
**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
**Flexible** (patterns): Adapt principles to context.
The skill itself tells you which.
## User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

View File

@@ -1,100 +0,0 @@
# Codex Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Codex equivalent |
|-----------------|------------------|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
| Task returns result | `wait` |
| Task completes automatically | `close_agent` to free slot |
| `TodoWrite` (task tracking) | `update_plan` |
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
| `Bash` (run commands) | Use your native shell tools |
## Subagent dispatch requires multi-agent support
Add to your Codex config (`~/.codex/config.toml`):
```toml
[features]
multi_agent = true
```
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
## Named agent dispatch
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
Codex does not have a named agent registry — `spawn_agent` creates generic agents
from built-in roles (`default`, `explorer`, `worker`).
When a skill says to dispatch a named agent type:
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
local prompt template like `code-quality-reviewer-prompt.md`)
2. Read the prompt content
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
4. Spawn a `worker` agent with the filled content as the `message`
| Skill instruction | Codex equivalent |
|-------------------|------------------|
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
### Message framing
The `message` parameter is user-level input, not a system prompt. Structure it
for maximum instruction adherence:
```
Your task is to perform the following. Follow the instructions below exactly.
<agent-instructions>
[filled prompt content from the agent's .md file]
</agent-instructions>
Execute this now. Output ONLY the structured response following the format
specified in the instructions above.
```
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
- End with an explicit execution directive to prevent summarization of the instructions
### When this workaround can be removed
This approach compensates for Codex's plugin system not yet supporting an `agents`
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
skills can dispatch named agent types directly.
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.

View File

@@ -1,33 +0,0 @@
# Gemini CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Gemini CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `read_file` |
| `Write` (file creation) | `write_file` |
| `Edit` (file editing) | `replace` |
| `Bash` (run commands) | `run_shell_command` |
| `Grep` (search file content) | `grep_search` |
| `Glob` (search files by name) | `glob` |
| `TodoWrite` (task tracking) | `write_todos` |
| `Skill` tool (invoke a skill) | `activate_skill` |
| `WebSearch` | `google_web_search` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## No subagent support
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
## Additional Gemini CLI tools
These tools are available in Gemini CLI but have no Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `list_directory` | List files and subdirectories |
| `save_memory` | Persist facts to GEMINI.md across sessions |
| `ask_user` | Request structured input from the user |
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |

View File

@@ -163,6 +163,16 @@ async doRequest(req: { action: string, data?: any }) {
}
```
--- 开发技巧:实现统一的 API 请求封装
**好处:**
- **代码复用**:避免在每个 API 方法中重复编写相同的 header 设置和错误处理逻辑
- **错误处理一致**:统一捕获和处理各种错误情况,确保错误信息格式统一
- **日志记录完善**:集中记录详细的错误信息,便于调试和问题排查
- **接口调用简化**:调用方只需关注业务逻辑,无需关心底层请求细节
- **易于维护**:统一修改 API 调用方式时,只需修改一处代码
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
@@ -170,9 +180,12 @@ async doRequest(req: { action: string, data?: any }) {
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`
4. **错误处理**API 调用失败时应抛出明确的错误信息。
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
6. **统一接口调用方法**:封装统一的 API 请求方法,避免在每个 API 方法调用中重复编写错误处理逻辑。
## 完整示例
### 示例 1: 通用授权插件
```typescript
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';

View File

@@ -6,9 +6,8 @@ Access存储用户的第三放应用的授权数据比如用户名密码
Task 部署任务插件它继承AbstractTaskPlugin类被流水线调用execute方法将证书部署到对应的应用上
DnsProvider: DNS提供商插件它用于在ACME申请证书时给域名添加txt解析记录。
在开始工作前,请阅读并加载.trae/skills下面的技能根据skills进行相应的插件开发
当开发过程中遇到问题需要参考plugins目录下的其他插件或者用户提醒你更好的做法时你需要总结经验更新相应的skills让skills越来越完善能够在以后得新插件开发中具备指导意义。
一般调用的api接口文档会比较复杂你不知道接口是什么时请务必询问用户让用户提供API接口文档
完成开发后无需测试,通知用户自己去测试
注意事项:
1、使用技能在开始工作前请阅读并加载.trae/skills下面的技能根据skills进行相应的插件开发
2、迭代技能当开发过程用户提醒你更好的做法时你需要总结经验更新相应的skills让skills越来越完善能够在以后得新插件开发中具备指导意义。
3、一般调用的api接口文档会比较复杂你不知道接口是什么时请务必询问用户让用户提供API接口文档
4、完成开发后无需测试通知用户自己去测试

View File

@@ -126,6 +126,8 @@ if (isDev()) {
## 完整示例
### 示例:通用 DNS Provider
```typescript
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DemoAccess } from './access.js';

View File

@@ -1,115 +0,0 @@
---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---
<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>
<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>
## Instruction Priority
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
## How to Access Skills
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills
## The Rule
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
```dot
digraph skill_flow {
"User message received" [shape=doublecircle];
"About to EnterPlanMode?" [shape=doublecircle];
"Already brainstormed?" [shape=diamond];
"Invoke brainstorming skill" [shape=box];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"About to EnterPlanMode?" -> "Already brainstormed?";
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
"Invoke brainstorming skill" -> "Might any skill apply?";
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
```
## Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
## Skill Priority
When multiple skills could apply, use this order:
1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.
## Skill Types
**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
**Flexible** (patterns): Adapt principles to context.
The skill itself tells you which.
## User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

View File

@@ -1,100 +0,0 @@
# Codex Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Codex equivalent |
|-----------------|------------------|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
| Task returns result | `wait` |
| Task completes automatically | `close_agent` to free slot |
| `TodoWrite` (task tracking) | `update_plan` |
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
| `Bash` (run commands) | Use your native shell tools |
## Subagent dispatch requires multi-agent support
Add to your Codex config (`~/.codex/config.toml`):
```toml
[features]
multi_agent = true
```
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
## Named agent dispatch
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
Codex does not have a named agent registry — `spawn_agent` creates generic agents
from built-in roles (`default`, `explorer`, `worker`).
When a skill says to dispatch a named agent type:
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
local prompt template like `code-quality-reviewer-prompt.md`)
2. Read the prompt content
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
4. Spawn a `worker` agent with the filled content as the `message`
| Skill instruction | Codex equivalent |
|-------------------|------------------|
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
### Message framing
The `message` parameter is user-level input, not a system prompt. Structure it
for maximum instruction adherence:
```
Your task is to perform the following. Follow the instructions below exactly.
<agent-instructions>
[filled prompt content from the agent's .md file]
</agent-instructions>
Execute this now. Output ONLY the structured response following the format
specified in the instructions above.
```
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
- End with an explicit execution directive to prevent summarization of the instructions
### When this workaround can be removed
This approach compensates for Codex's plugin system not yet supporting an `agents`
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
skills can dispatch named agent types directly.
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.

View File

@@ -1,33 +0,0 @@
# Gemini CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Gemini CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `read_file` |
| `Write` (file creation) | `write_file` |
| `Edit` (file editing) | `replace` |
| `Bash` (run commands) | `run_shell_command` |
| `Grep` (search file content) | `grep_search` |
| `Glob` (search files by name) | `glob` |
| `TodoWrite` (task tracking) | `write_todos` |
| `Skill` tool (invoke a skill) | `activate_skill` |
| `WebSearch` | `google_web_search` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## No subagent support
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
## Additional Gemini CLI tools
These tools are available in Gemini CLI but have no Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `list_directory` | List files and subdirectories |
| `save_memory` | Persist facts to GEMINI.md across sessions |
| `ask_user` | Request structured input from the user |
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -211,3 +211,4 @@ https://certd.handfree.work/
| --------- |--------- |----------- |
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具无需FQ解决github无法访问的问题 |
| [winsvc-manager](https://github.com/greper/winsvc-manager/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/greper/winsvc-manager?logo=github"/> | 可视化包装应用成为一个Windows服务使其后台运行 |

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -1,5 +1,5 @@
# 任务插件
`128` 款任务插件
`129` 款任务插件
## 1. 证书申请
| 序号 | 名称 | 说明 |
@@ -155,8 +155,9 @@
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **百度云-部署证书到负载均衡** | 部署到百度云负载均衡包括BLB、APPBLB |
| 2.| **百度云-部署证书到CDN** | 部署到百度云CDN |
| 3.| **百度云-上传到证书托管** | 上传证书到百度云证书托管中心 |
| 2.| **百度云-部署到CCE** | 部署到百度云CCE集群Ingress等通过Secret管理证书的应用 |
| 3.| **百度云-部署证书到CDN** | 部署到百度云CDN |
| 4.| **百度云-上传到证书托管** | 上传证书到百度云证书托管中心 |
## 12. 七牛云
| 序号 | 名称 | 说明 |

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.39.7"
"version": "1.39.8"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/publishlab/node-acme-client/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/acme-client
## [1.39.7](https://github.com/publishlab/node-acme-client/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.39.7",
"@certd/basic": "^1.39.8",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",
@@ -70,5 +70,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/basic
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
01:02
23:54

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -47,5 +47,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -271,7 +271,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
}
const originalRequest = error.config || {};
logger.info(`config`, originalRequest);
// logger.info(`config`, originalRequest);
const retry = originalRequest.retry || {};
if (retry.status && retry.status.includes(status)) {
if (retry.max > 0 && retry.count < retry.max) {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -18,8 +18,8 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/basic": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -1,17 +1,16 @@
import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { domainUtils, HttpRequestConfig, ILogger, logger, optionsUtils, utils } from "@certd/basic";
import { HttpClient } from "@certd/basic";
import { domainUtils, HttpClient, HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
import dayjs from "dayjs";
import { IPluginConfigService } from "../service/config.js";
import { cloneDeep, upperFirst } from "lodash-es";
import { INotificationService } from "../notification/index.js";
import { TaskEmitter } from "../service/emit.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { PageSearch } from "../context/index.js";
import { FileStore } from "../core/file-store.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
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, IServiceGetter, IUrlService } from "../service/index.js";
export type PluginRequestHandleReq<T = any> = {
typeName: string;

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-huawei
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -24,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-iframe
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/jdcloud
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.39.7",
"version": "1.39.8",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -56,5 +56,5 @@
"fetch"
]
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -14,10 +14,11 @@
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"pub": "npm publish"
"pub": "npm publish",
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.7",
"@certd/basic": "^1.39.8",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +33,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -59,9 +59,9 @@ export class K8sClient {
const yml = loadYaml<KubernetesObject>(manifest);
const client = this.getKubeClient();
try {
this.logger.info("apply yaml:", yml);
await client.create(yml);
} catch (e) {
this.logger.error("apply error", e.response?.body);
if (e.response?.body?.reason === "AlreadyExists") {
//patch
this.logger.info("patch existing resource: ", yml.metadata?.name);
@@ -70,13 +70,26 @@ export class K8sClient {
yml.metadata = {};
}
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
await client.patch(yml);
return;
const res = await client.patch(yml);
return res?.body;
}
throw e;
}
}
async applyPatch(manifest: string) {
const yml = loadYaml<KubernetesObject>(manifest);
const client = this.getKubeClient();
this.logger.info("patch yaml:", yml);
const existing = await client.read(yml as any);
if (!yml.metadata) {
yml.metadata = {};
}
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
const res = await client.patch(yml);
return res?.body;
}
/**
*
* @param localRecords { [domain]:{ip:'xxx.xx.xxx'} }
@@ -112,6 +125,7 @@ export class K8sClient {
*/
async createSecret(opts: { namespace: string; body: V1Secret }) {
const namespace = opts.namespace || "default";
this.logger.info("create secret:", opts.body.metadata);
const created = await this.client.createNamespacedSecret(namespace, opts.body);
this.logger.info("new secrets:", opts.body.metadata);
return created.body;
@@ -152,6 +166,8 @@ export class K8sClient {
this.logger.info(`secret ${secretName} 已创建`);
return res;
}
throw new Error(`secret ${secretName} 不存在`);
}
throw e;
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-server
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.39.7",
"version": "1.39.8",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -28,11 +28,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plugin-lib": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plugin-lib": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -64,5 +64,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.39.7",
"version": "1.39.8",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/plugin-cert
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,10 +17,10 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plugin-lib": "^1.39.7",
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plugin-lib": "^1.39.8",
"psl": "^1.9.0",
"punycode.js": "^2.3.1"
},
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.39.7",
"version": "1.39.8",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -22,10 +22,10 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.11",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -57,5 +57,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "de0ae14544f1c3da4923dddc6a1a3bea4db295e7"
}

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.39.7",
"version": "1.39.8",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/lib-iframe": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -21,7 +21,8 @@ import { defineComponent, reactive, ref, watch, inject } from "vue";
import CertAccessModal from "./access/index.vue";
import { createAccessApi } from "../api";
import { message } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
import { useProjectStore } from "/@/store/project";
export default defineComponent({
name: "AccessSelector",
components: { CertAccessModal },
@@ -71,11 +72,27 @@ export default defineComponent({
emitValue(null);
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value) {
if (pipeline && pipeline?.value && target?.value && pipeline.value.userId !== target.value.userId) {
message.error("对不起,您不能修改他人流水线的授权");
return;
const userId = userStore.userInfo.id;
const isEnterprice = projectStore.isEnterprise;
if (pipeline?.value) {
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error(`对不起,您不能修改其他项目流水线的授权`);
return;
}
} else {
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的授权`);
return;
}
}
}
if (value == null) {
selectedId.value = "";
target.value = null;

View File

@@ -48,6 +48,7 @@ import createCrudOptions from "../crud";
import { addonProvide } from "../common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
@@ -127,13 +128,24 @@ function clear() {
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
return;
if (pipeline.value) {
const userId = userStore.userInfo.id;
const isEnterprice = projectStore.isEnterprise;
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error(`对不起,您不能修改其他项目流水线的${props.addonType}设置`);
return;
}
} else {
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
return;
}
}
}
emit("change", value);
emit("update:modelValue", value);

View File

@@ -42,6 +42,7 @@ import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
@@ -127,13 +128,23 @@ function clear() {
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
const isEnterprice = projectStore.isEnterprise;
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error("对不起,您不能修改其他项目流水线的通知");
return;
}
} else {
if (pipeline?.value?.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
}
}
emit("change", value);
emit("update:modelValue", value);

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -7,6 +7,7 @@ title: 阿里云-部署证书至CDN
icon: svg:icon-aliyun
group: aliyun
desc: 自动部署域名证书至阿里云CDN
runStrategy: 0
input:
endpoint:
title: 证书服务接入点
@@ -59,36 +60,6 @@ input:
type: aliyun
required: true
order: 0
domainName:
title: CDN加速域名
component:
name: remote-select
vModel: value
mode: tags
type: plugin
typeName: DeployCertToAliyunCDN
action: onGetDomainList
search: false
pager: false
multi: true
watches:
- certDomains
- accessId
- certDomains
- accessId
required: true
mergeScript: |2-
return {
component:{
form: ctx.compute(({form})=>{
return form
})
},
}
helper: 你在阿里云上配置的CDN加速域名比如:certd.docmirror.cn
order: 0
certRegion:
title: 证书所在地域
helper: cn-hangzhou和ap-southeast-1默认cn-hangzhou。国际站用户建议使用ap-southeast-1。
@@ -106,7 +77,49 @@ input:
title: 证书名称
helper: 上传后将以此名称作为前缀备注
order: 0
output: {}
domainMatchMode:
title: 域名匹配模式
helper: 根据证书匹配根据证书域名自动匹配DCDN加速域名自动部署新增加速域名自动感知自动新增部署
component:
name: a-select
options:
- label: 手动选择
value: manual
- label: 根据证书匹配
value: auto
value: manual
order: 0
domainName:
title: CDN加速域名
component:
name: remote-select
vModel: value
mode: tags
type: plugin
typeName: DeployCertToAliyunCDN
action: onGetDomainList
search: false
pager: true
multi: true
watches:
- certDomains
- accessId
- certDomains
- accessId
required: true
mergeScript: |2-
return {
show: ctx.compute(({form})=>{
return form.domainMatchMode === "manual"
})
}
helper: 你在阿里云上配置的CDN加速域名比如:certd.docmirror.cn
order: 0
output:
deployedList:
title: 已部署过的DCDN加速域名
pluginType: deploy
type: builtIn
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-cdn/index.js

View File

@@ -7,6 +7,7 @@ title: 阿里云-部署证书至DCDN
icon: svg:icon-aliyun
group: aliyun
desc: 依赖证书申请前置任务自动部署域名证书至阿里云DCDN
runStrategy: 0
input:
cert:
title: 域名证书
@@ -47,6 +48,18 @@ input:
title: 证书名称
helper: 上传后将以此名称作为前缀备注
order: 0
domainMatchMode:
title: 域名匹配模式
helper: 根据证书匹配根据证书域名自动匹配DCDN加速域名自动部署新增加速域名自动感知自动新增部署
component:
name: a-select
options:
- label: 手动选择
value: manual
- label: 根据证书匹配
value: auto
value: manual
order: 0
domainName:
title: DCDN加速域名
component:
@@ -56,7 +69,7 @@ input:
type: plugin
action: onGetDomainList
search: false
pager: false
pager: true
multi: true
watches:
- certDomains
@@ -66,17 +79,17 @@ input:
required: true
mergeScript: |2-
return {
component:{
form: ctx.compute(({form})=>{
return form
})
},
}
return {
show: ctx.compute(({form})=>{
return form.domainMatchMode === "manual"
})
}
helper: 你在阿里云上配置的DCDN加速域名比如:certd.docmirror.cn
order: 0
output: {}
output:
deployedList:
title: 已部署过的DCDN加速域名
pluginType: deploy
type: builtIn
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.js

View File

@@ -0,0 +1,110 @@
showRunStrategy: false
default:
strategy:
runStrategy: 1
name: DeployCertToBaiduCce
title: 百度云-部署到CCE
icon: ant-design:cloud-outlined
desc: 部署到百度云CCE集群Ingress等通过Secret管理证书的应用
group: baidu
needPlus: true
input:
cert:
title: 域名证书
helper: 请选择前置任务输出的域名证书
component:
name: output-selector
from:
- ':cert:'
required: true
order: 0
accessId:
title: Access授权
helper: 百度云授权AccessKey、SecretKey
component:
name: access-selector
type: baidu
required: true
order: 0
regionId:
title: 大区
component:
name: a-auto-complete
vModel: value
options:
- value: bj
label: 北京
- value: gz
label: 广州
- value: su
label: 苏州
- value: bd
label: 保定
- value: fwh
label: 武汉
- value: hkg
label: 香港
- value: yq
label: 阳泉
- value: cd
label: 成都
- value: nj
label: 南京
placeholder: 集群所属大区
required: true
order: 0
clusterId:
title: 集群id
component:
placeholder: 集群id
required: true
order: 0
secretName:
title: 保密字典Id
component:
placeholder: 保密字典Id
helper: 原本存储证书的secret的name
required: true
order: 0
namespace:
title: 命名空间
value: default
component:
placeholder: 命名空间
required: true
order: 0
kubeconfigType:
title: Kubeconfig类型
value: public
component:
name: a-auto-complete
vModel: value
options:
- value: vpc
label: VPC私网IP (BLB VPCIP)
- value: public
label: 公网IP (BLB EIP)
placeholder: 选择集群连接端点类型
helper: VPC类型使用私网IP连接需要certd运行在同一网络环境public类型使用公网IP连接
required: true
order: 0
skipTLSVerify:
title: 忽略证书校验
required: false
helper: 是否忽略证书校验
component:
name: a-switch
vModel: checked
order: 0
createOnNotFound:
title: Secret自动创建
helper: 如果Secret不存在则创建百度云的自动创建secret有问题
value: false
component:
name: a-switch
vModel: checked
order: 0
output: {}
pluginType: deploy
type: builtIn
scriptFilePath: /plugins/plugin-plus/baidu/plugins/plugin-deploy-to-cce.js

View File

@@ -35,6 +35,19 @@ input:
type: k8s
required: true
order: 0
strategy:
title: 应用策略
helper: 选择使用apply创建或更新还是patch补丁更新
component:
name: a-select
options:
- label: apply(创建)
value: apply
- label: patch(更新)
value: patch
value: apply
required: true
order: 0
yamlContent:
title: yaml
required: true

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.39.7",
"version": "1.39.8",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -50,20 +50,20 @@
"@aws-sdk/client-route-53": "^3.964.0",
"@aws-sdk/client-s3": "^3.964.0",
"@aws-sdk/client-sts": "^3.990.0",
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/commercial-core": "^1.39.7",
"@certd/acme-client": "^1.39.8",
"@certd/basic": "^1.39.8",
"@certd/commercial-core": "^1.39.8",
"@certd/cv4pve-api-javascript": "^8.4.2",
"@certd/jdcloud": "^1.39.7",
"@certd/lib-huawei": "^1.39.7",
"@certd/lib-k8s": "^1.39.7",
"@certd/lib-server": "^1.39.7",
"@certd/midway-flyway-js": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plugin-cert": "^1.39.7",
"@certd/plugin-lib": "^1.39.7",
"@certd/plugin-plus": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/jdcloud": "^1.39.8",
"@certd/lib-huawei": "^1.39.8",
"@certd/lib-k8s": "^1.39.8",
"@certd/lib-server": "^1.39.8",
"@certd/midway-flyway-js": "^1.39.8",
"@certd/pipeline": "^1.39.8",
"@certd/plugin-cert": "^1.39.8",
"@certd/plugin-lib": "^1.39.8",
"@certd/plugin-plus": "^1.39.8",
"@certd/plus-core": "^1.39.8",
"@google-cloud/publicca": "^1.3.0",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.185",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.185",

View File

@@ -44,4 +44,5 @@
// export * from './plugin-lib/index.js'
// export * from './plugin-plus/index.js'
// export * from './plugin-cert/index.js'
// export * from './plugin-zenlayer/index.js'
// export * from './plugin-zenlayer/index.js'
export * from './plugin-dnsmgr/index.js'

View File

@@ -156,7 +156,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
if(maxDays < 2){
maxDays = 2;
}
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半,调整为${maxDays}`);
this.logger.warn(`为避免每次运行都更新证书,更新天数自动减半(即证书最大时长${totalDays}天减半),调整为${maxDays}`);
}
return {

View File

@@ -0,0 +1,144 @@
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';
@IsAccess({
name: 'dnsmgr',
title: '彩虹DNS',
icon: 'clarity:plugin-line',
desc: '彩虹DNS管理系统授权',
})
export class DnsmgrAccess extends BaseAccess {
@AccessInput({
title: '系统地址',
component: {
name: "a-input",
allowClear: true,
placeholder: 'https://dnsmgr.example.com',
},
required: true,
})
endpoint = '';
@AccessInput({
title: '用户ID',
component: {
name: "a-input",
allowClear: true,
placeholder: '123456',
},
required: true,
})
uid = '';
@AccessInput({
title: 'API密钥',
required: true,
encrypt: true,
})
key = '';
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
async onTestRequest() {
await this.GetDomainList({});
return "ok";
}
async GetDomainList(req: PageSearch): Promise<PageRes<DomainRecord>> {
this.ctx.logger.info(`获取域名列表req:${JSON.stringify(req)}`);
const pager = new Pager(req);
const resp = await this.doRequest({
url: '/api/domain',
data: {
offset: pager.getOffset(),
limit: pager.pageSize,
kw: req.searchKey,
},
});
const total = resp?.total || 0;
let list = resp?.rows?.map((item: any) => {
return {
domain: item.name,
...item,
};
});
return {
total,
list,
};
}
async createDnsRecord(domainId: string, record: string, value: string, type: string, domain: string) {
this.ctx.logger.info(`创建DNS记录${record} ${type} ${value}`);
const resp = await this.doRequest({
url: `/api/record/add/${domainId}`,
data: {
name: record.replace(`.${domain}`, ''),
type,
value,
line: 'default',
ttl: 600,
},
});
return resp;
}
async getDnsRecords(domainId: string, type?: string, name?: string, value?: string) {
this.ctx.logger.info(`获取DNS记录列表domainId=${domainId}, type=${type}, name=${name}`);
const resp = await this.doRequest({
url: `/api/record/data/${domainId}`,
data: {
type,
subdomain: name,
value,
},
});
return resp;
}
async deleteDnsRecord(domainId: string, recordId: string) {
this.ctx.logger.info(`删除DNS记录domainId=${domainId}, recordId=${recordId}`);
const resp = await this.doRequest({
url: `/api/record/delete/${domainId}`,
data: {
recordid: recordId,
},
});
return resp;
}
async doRequest(req: { url: string; data?: any }) {
const timestamp = Math.floor(Date.now() / 1000);
const sign = this.ctx.utils.hash.md5(`${this.uid}${timestamp}${this.key}`);
const url = `${this.endpoint}${req.url}`;
const res = await this.ctx.http.request({
url,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
uid: this.uid,
timestamp,
sign,
...req.data,
},
});
if (res.code !== undefined && res.code !== 0) {
throw new Error(res.msg || '请求失败');
}
return res;
}
}

View File

@@ -0,0 +1,71 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DnsmgrAccess } from './access.js';
import { PageRes, PageSearch } from '@certd/pipeline';
type DnsmgrRecord = {
domainId: string;
name: string;
value: string;
};
@IsDnsProvider({
name: 'dnsmgr',
title: '彩虹DNS',
desc: '彩虹DNS管理系统',
icon: 'clarity:plugin-line',
accessType: 'dnsmgr',
order: 99,
})
export class DnsmgrDnsProvider extends AbstractDnsProvider<DnsmgrRecord> {
access!: DnsmgrAccess;
async onInstance() {
this.access = this.ctx.access as DnsmgrAccess;
this.logger.debug('access', this.access);
}
async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
const domainList = await this.access.GetDomainList({ searchKey: domain });
const domainInfo = domainList.list?.find((item: any) => item.name === domain);
if (!domainInfo) {
throw new Error(`未找到域名:${domain}`);
}
const name = fullRecord.replace(`.${domain}`, '');
const res = await this.access.createDnsRecord(domainInfo.id, fullRecord, value, type, domain);
return { domainId: domainInfo.id, name, value,res };
}
async removeRecord(options: RemoveRecordOptions<DnsmgrRecord>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value, record);
if (record && record.domainId) {
const records = await this.access.getDnsRecords(record.domainId, 'TXT', record.name, record.value);
if (records && records.rows && records.rows.length > 0) {
const recordToDelete = records.rows[0];
await this.access.deleteDnsRecord(record.domainId, recordToDelete.RecordId);
}
}
this.logger.info('删除域名解析成功:', fullRecord, value);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const res = await this.access.GetDomainList(req);
res.list = res.list?.map((item: any) => {
return {
id: item.id,
domain: item.name,
};
});
return res;
}
}
new DnsmgrDnsProvider();

View File

@@ -0,0 +1,2 @@
export * from './access.js';
export * from './dns-provider.js';

View File

@@ -1,3 +1,4 @@
export * from "./plugin-deploy-to-cdn.js";
export * from "./plugin-deploy-to-blb.js";
export * from "./plugin-upload-to-baidu.js";
export * from "./plugin-deploy-to-cce.js";

View File

@@ -0,0 +1,245 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { utils } from "@certd/basic";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { BaiduAccess } from "../access.js";
import { BaiduYunClient } from "../client.js";
@IsTaskPlugin({
name: "DeployCertToBaiduCce",
title: "百度云-部署到CCE",
icon: "ant-design:cloud-outlined",
desc: "部署到百度云CCE集群Ingress等通过Secret管理证书的应用",
group: pluginGroups.baidu.key,
needPlus: true,
input: {},
output: {},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class DeployCertToBaiduCcePlugin extends AbstractTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames],
},
required: true,
})
cert!: CertInfo;
@TaskInput({
title: "Access授权",
helper: "百度云授权AccessKey、SecretKey",
component: {
name: "access-selector",
type: "baidu",
},
required: true,
})
accessId!: string;
@TaskInput({
title: "大区",
component: {
name: "a-auto-complete",
vModel: "value",
options: [
{ value: "bj", label: "北京" },
{ value: "gz", label: "广州" },
{ value: "su", label: "苏州" },
{ value: "bd", label: "保定" },
{ value: "fwh", label: "武汉" },
{ value: "hkg", label: "香港" },
{ value: "yq", label: "阳泉" },
{ value: "cd", label: "成都" },
{ value: "nj", label: "南京" },
],
placeholder: "集群所属大区",
},
required: true,
})
regionId!: string;
@TaskInput({
title: "集群id",
component: {
placeholder: "集群id",
},
required: true,
})
clusterId!: string;
@TaskInput({
title: "保密字典Id",
component: {
placeholder: "保密字典Id",
},
helper: "原本存储证书的secret的name",
required: true,
})
secretName!: string | string[];
@TaskInput({
title: "命名空间",
value: "default",
component: {
placeholder: "命名空间",
},
required: true,
})
namespace = "default";
@TaskInput({
title: "Kubeconfig类型",
value: "public",
component: {
name: "a-auto-complete",
vModel: "value",
options: [
{ value: "vpc", label: "VPC私网IP (BLB VPCIP)" },
{ value: "public", label: "公网IP (BLB EIP)" },
],
placeholder: "选择集群连接端点类型",
},
helper: "VPC类型使用私网IP连接需要certd运行在同一网络环境public类型使用公网IP连接",
required: true,
})
kubeconfigType!: string;
@TaskInput({
title: "忽略证书校验",
required: false,
helper: "是否忽略证书校验",
component: {
name: "a-switch",
vModel: "checked",
},
})
skipTLSVerify!: boolean;
@TaskInput({
title: "Secret自动创建",
helper: "如果Secret不存在则创建百度云的自动创建secret有问题",
value: false,
component: {
name: "a-switch",
vModel: "checked",
},
})
createOnNotFound: boolean;
K8sClient: any;
async onInstance() {
const sdk = await import("@certd/lib-k8s");
this.K8sClient = sdk.K8sClient;
}
async execute(): Promise<void> {
this.logger.info("开始部署证书到百度云CCE");
const { regionId, clusterId, kubeconfigType, cert } = this;
const access = (await this.getAccess(this.accessId)) as BaiduAccess;
const client = new BaiduYunClient({
access,
logger: this.logger,
http: this.ctx.http,
});
const kubeConfigStr = await this.getKubeConfig(client, clusterId, regionId, kubeconfigType);
this.logger.info("kubeconfig已成功获取");
const k8sClient = new this.K8sClient({
kubeConfigStr,
logger: this.logger,
skipTLSVerify: this.skipTLSVerify,
});
await this.patchCertSecret({ cert, k8sClient });
await utils.sleep(5000);
try {
await this.restartIngress({ k8sClient });
} catch (e) {
this.logger.warn(`重启ingress失败:${e.message}`);
}
}
async restartIngress(options: { k8sClient: any }) {
const { k8sClient } = options;
const { namespace } = this;
const body = {
metadata: {
labels: {
certd: this.appendTimeSuffix("certd"),
},
},
};
const ingressList = await k8sClient.getIngressList({ namespace });
this.logger.info("ingressList:", JSON.stringify(ingressList));
if (!ingressList || !ingressList.items) {
return;
}
const ingressNames = ingressList.items
.filter((item: any) => {
if (!item.spec.tls) {
return false;
}
for (const tls of item.spec.tls) {
if (tls.secretName === this.secretName) {
return true;
}
}
return false;
})
.map((item: any) => {
return item.metadata.name;
});
for (const ingress of ingressNames) {
await k8sClient.patchIngress({ namespace, ingressName: ingress, body, createOnNotFound: this.createOnNotFound });
this.logger.info(`ingress已重启:${ingress}`);
}
}
async patchCertSecret(options: { cert: CertInfo; k8sClient: any }) {
const { cert, k8sClient } = options;
const crt = cert.crt;
const key = cert.key;
const crtBase64 = Buffer.from(crt).toString("base64");
const keyBase64 = Buffer.from(key).toString("base64");
const { namespace, secretName } = this;
const body = {
data: {
"tls.crt": crtBase64,
"tls.key": keyBase64,
},
metadata: {
labels: {
certd: this.appendTimeSuffix("certd"),
},
},
};
let secretNames: any = secretName;
if (typeof secretName === "string") {
secretNames = [secretName];
}
for (const secret of secretNames) {
await k8sClient.patchSecret({ namespace, secretName: secret, body ,createOnNotFound: this.createOnNotFound});
this.logger.info(`cert secret已更新: ${secret}`);
}
}
async getKubeConfig(client: BaiduYunClient, clusterId: string, regionId: string, kubeconfigType: string) {
const res = await client.doRequest({
host: `cce.${regionId}.baidubce.com`,
uri: `/v2/kubeconfig/${clusterId}/${kubeconfigType}`,
method: "get",
});
return res.kubeConfig;
}
}
new DeployCertToBaiduCcePlugin();

View File

@@ -62,6 +62,21 @@ export class K8sApplyPlugin extends AbstractPlusTaskPlugin {
// })
// namespace!: string;
@TaskInput({
title: "应用策略",
helper: "选择使用apply创建或更新还是patch补丁更新",
component: {
name: "a-select",
options: [
{ label: "apply(创建)", value: "apply" },
{ label: "patch(更新)", value: "patch" },
],
},
value: "apply",
required: true,
})
strategy!: string;
@TaskInput({
title: "yaml",
required: true,
@@ -112,8 +127,13 @@ export class K8sApplyPlugin extends AbstractPlusTaskPlugin {
try {
// this.logger.info("apply yaml:", compiledYaml);
// this.logger.info("apply yamlDoc:", JSON.stringify(doc));
const res = await client.apply(compiledYaml);
this.logger.info("apply result:", res);
if (this.strategy === "apply") {
await client.apply(compiledYaml);
this.logger.info("apply success");
} else {
await client.applyPatch(compiledYaml);
this.logger.info("patch success");
}
} catch (e) {
if (e.response?.body) {
throw new Error(JSON.stringify(e.response.body));

View File

@@ -0,0 +1,148 @@
import { IsAccess, AccessInput, BaseAccess, PageSearch } from "@certd/pipeline";
@IsAccess({
name: "spaceship",
title: "Spaceship.com 授权",
icon: "clarity:plugin-line",
desc: "Spaceship.com API 授权插件"
})
export class SpaceshipAccess extends BaseAccess {
@AccessInput({
title: "API Key",
component: {
placeholder: "请输入 API Key"
},
required: true,
encrypt: true,
helper: "前往 [获取 API Key](https://www.spaceship.com/application/api-manager/)"
})
apiKey = "";
@AccessInput({
title: "API Secret",
component: {
name: "a-input-password",
vModel: "value",
placeholder: "请输入 API Secret"
},
required: true,
encrypt: true
})
apiSecret = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "测试 API 连接是否正常"
})
testRequest = true;
async onTestRequest() {
await this.GetDomainList({});
return "ok";
}
async doRequest(options: {
url: string;
method: 'GET' | 'POST' | 'DELETE';
params?: any;
data?: any;
}) {
const headers = {
"X-Api-Key": this.apiKey,
"X-Api-Secret": this.apiSecret
};
try {
const res = await this.ctx.http.request({
url: options.url,
method: options.method,
headers,
params: options.params,
data: options.data
});
return res;
} catch (error: any) {
const errorMsg = [];
const status = error.status || error.response?.status;
if (error.response) {
const headers = error.response.headers;
const data = error.response.data;
errorMsg.push(`API 请求失败: ${status}`);
if (headers['spaceship-error-code']) {
errorMsg.push(`错误代码: ${headers['spaceship-error-code']}`);
}
if (headers['spaceship-operation-id']) {
errorMsg.push(`操作ID: ${headers['spaceship-operation-id']}`);
}
if (data && data.detail) {
errorMsg.push(`错误详情: ${data.detail}`);
}
this.ctx.logger.error(`Spaceship API 错误: ${errorMsg.join(' | ')}`);
} else if (error.request) {
errorMsg.push(`请求发送失败: ${error.message}`);
this.ctx.logger.error(`Spaceship API 请求发送失败: ${error.message}`);
} else {
errorMsg.push(`请求配置错误: ${error.message}`);
this.ctx.logger.error(`Spaceship API 请求配置错误: ${error.message}`);
}
const error2 = new Error(errorMsg.join(' | '));
//@ts-ignore
error2.status = status;
throw error2;
}
}
async GetDomainList(req: PageSearch) {
const take = req.pageSize || 100;
const skip = ((req.pageNo || 1) - 1) * take;
const res = await this.doRequest({
url: "https://spaceship.dev/api/v1/domains",
method: "GET",
params: {
take,
skip
}
});
return {
total: res.total || 0,
list: res.items || []
};
}
async getDomainInfo(domain: string) {
try {
const res = await this.doRequest({
url: `https://spaceship.dev/api/v1/domains/${domain}`,
method: "GET"
});
return res;
} catch (error: any) {
if (error.status === 404) {
throw new Error(`域名 ${domain} 不存在于当前账号中`);
}
throw error;
}
}
getCacheKey() {
const hashStr = this.apiKey + this.apiSecret;
const hashCode = this.ctx.utils.hash.sha256(hashStr);
return `spaceship-${hashCode}`;
}
}
new SpaceshipAccess();

View File

@@ -0,0 +1,95 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { SpaceshipAccess } from "./access.js";
import { PageRes, PageSearch } from "@certd/pipeline";
export type SpaceshipRecord = {
id: string;
name: string;
type: string;
content: string;
domainId: string;
};
@IsDnsProvider({
name: "spaceship",
title: "Spaceship",
desc: "Spaceship 域名解析",
icon: "clarity:plugin-line",
accessType: "spaceship",
order: 99
})
export class SpaceshipProvider extends AbstractDnsProvider<SpaceshipRecord> {
access!: SpaceshipAccess;
async onInstance() {
this.access = this.ctx.access as SpaceshipAccess;
}
async createRecord(options: CreateRecordOptions): Promise<SpaceshipRecord> {
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
await this.access.getDomainInfo(domain);
const recordRes = await this.access.doRequest({
url: `https://spaceship.dev/api/v1/domains/${domain}/records`,
method: "POST",
data: {
force: false,
items: [
{
type: type,
value: value,
name: hostRecord,
ttl: 300
}
]
}
});
return {
id: recordRes.items[0].id,
name: hostRecord,
type: type,
content: value,
domainId: domain
};
}
async removeRecord(options: RemoveRecordOptions<SpaceshipRecord>): Promise<void> {
const recordRes = options.recordRes;
this.logger.info("删除域名解析:", recordRes);
await this.access.doRequest({
url: `https://spaceship.dev/api/v1/domains/${recordRes.domainId}/records`,
method: "DELETE",
data: {
Records: [
{
type: recordRes.type,
value: recordRes.content,
name: recordRes.name
}
]
}
});
this.logger.info("删除域名解析成功:", recordRes.name);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const res = await this.access.GetDomainList(req);
const list = res.list.map((item: any) => ({
domain: item.name,
id: item.name
}));
return {
total: res.total || 0,
list: list || []
};
}
}
new SpaceshipProvider();

View File

@@ -0,0 +1,2 @@
import "./access.js";
import "./dns-provider.js";

View File

@@ -1,7 +1,7 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import dayjs from 'dayjs';
import { TencentAccess } from '../../../plugin-lib/tencent/index.js';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import { CertApplyPluginNames, CertInfo } from '@certd/plugin-cert';
@IsTaskPlugin({
name: 'DeployCertToTencentCLB',
title: '腾讯云-部署到CLB',
@@ -15,6 +15,31 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
},
})
export class DeployCertToTencentCLB extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'UploadCertToTencent'],
},
required: true,
})
cert!: string | CertInfo;
@TaskInput({
title: 'Access提供者',
helper: 'access授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
})
accessId!: string;
@TaskInput({
title: '大区',
component: {
@@ -40,20 +65,18 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
{ value: 'na-siliconvalley' },
{ value: 'na-toronto' },
{ value: 'sa-saopaulo' },
{ value: 'ap-taipei' },
],
helper: '如果列表中没有,您可以手动输入',
},
required: true,
})
region!: string;
@TaskInput({
title: '证书名称前缀',
})
certName!: string;
@TaskInput({
title: '负载均衡ID',
helper: '如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个',
required: true,
})
loadBalancerId!: string;
@@ -78,26 +101,10 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
domain!: string | string[];
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
},
required: true,
title: '证书名称前缀',
})
cert!: any;
certName!: string;
@TaskInput({
title: 'Access提供者',
helper: 'access授权',
component: {
name: 'access-selector',
type: 'tencent',
},
required: true,
})
accessId!: string;
client: any;
async onInstance() {
@@ -234,12 +241,23 @@ export class DeployCertToTencentCLB extends AbstractTaskPlugin {
return name + '-' + dayjs().format('YYYYMMDD-HHmmss');
}
buildProps() {
const certId = this.cert as string;
const certInfo = this.cert as CertInfo;
if (typeof this.cert === 'string') {
return {
Certificate: {
CertId: certId,
},
LoadBalancerId: this.loadBalancerId,
ListenerId: this.listenerId,
};
}
return {
Certificate: {
SSLMode: 'UNIDIRECTIONAL', // 单向认证
CertName: this.appendTimeSuffix(this.certName || this.cert.domain),
CertKey: this.cert.key,
CertContent: this.cert.crt,
CertName: this.appendTimeSuffix(this.certName || "certd"),
CertKey: certInfo.key,
CertContent: certInfo.crt,
},
LoadBalancerId: this.loadBalancerId,
ListenerId: this.listenerId,

View File

@@ -1 +1 @@
01:07
23:58

View File

@@ -1 +1 @@
01:28
00:29

View File

@@ -1,32 +0,0 @@
{
"compileOnSave": true,
"compilerOptions": {
// 这样就可以对 `this` 上的数据属性进行更严格的推断`
"noImplicitAny": true,
"allowJs": true,
"target": "esnext",
"module": "esnext",
"strict": true,
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks" :false,
"inlineSourceMap": true,
"baseUrl": ".",
"outDir": "dist",
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules",
"vite.config.ts"
]
}