@wdyy/skills 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.well-known/skills/index.json +2 -2
- package/.well-known/skills/wdyy-deployment-standard/SKILL.md +33 -44
- package/.well-known/skills/wdyy-deployment-standard/agents/openai.yaml +2 -2
- package/.well-known/skills/wdyy-deployment-standard/reference/linux-deployment-rules.md +27 -63
- package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.mjs +19 -9
- package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.test.mjs +16 -2
- package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.mjs +61 -123
- package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.test.mjs +145 -347
- package/.well-known/skills/wdyy-deployment-standard/templates/deploy.sh.template +184 -675
- package/.well-known/skills/wdyy-deployment-standard/templates/docker-compose.yml +35 -0
- package/.well-known/skills/wdyy-deployment-standard/templates/env.example.template +0 -7
- package/.well-known/skills/wdyy-deployment-standard/templates/frontend-container.conf.template +2 -2
- package/.well-known/skills/wdyy-logging-standard/SKILL.md +2 -2
- package/.well-known/skills/wdyy-logging-standard/agents/openai.yaml +1 -1
- package/.well-known/skills/wdyy-logging-standard/reference/logging-rules.md +2 -2
- package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.test.mjs +1 -1
- package/README.md +4 -2
- package/lib/wdyy-cli.js +3 -2
- package/package.json +1 -1
- package/.well-known/skills/wdyy-deployment-standard/templates/docker-compose.blue-green.yml +0 -70
- package/.well-known/skills/wdyy-deployment-standard/templates/nginx-upstream.template.conf +0 -26
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"name": "wdyy-deployment-standard",
|
|
10
|
-
"description": "基于标准五段根 .env,为无 CI/CD
|
|
11
|
-
"files": ["SKILL.md", "agents/openai.yaml", "reference/linux-deployment-rules.md", "scripts/generate-deployment-files.mjs", "scripts/generate-deployment-files.test.mjs", "scripts/validate-deployment-package.mjs", "scripts/validate-deployment-package.test.mjs", "templates/Dockerfile.template", "templates/frontend.Dockerfile.template", "templates/frontend-container.conf.template", "templates/env.example.template", "templates/deploy.sh.template", "templates/docker-compose.
|
|
10
|
+
"description": "基于标准五段根 .env,为无 CI/CD 项目生成确定性的前后端带版本双 Docker 镜像、本地 deploy.sh build 导出包、人工上传,以及服务器直接宿主机端口的交互式运行、替换和停止;不使用蓝绿部署、宿主机 Nginx 或数据库迁移。Use when 规划、实现、审查或执行手工生产部署时。",
|
|
11
|
+
"files": ["SKILL.md", "agents/openai.yaml", "reference/linux-deployment-rules.md", "scripts/generate-deployment-files.mjs", "scripts/generate-deployment-files.test.mjs", "scripts/validate-deployment-package.mjs", "scripts/validate-deployment-package.test.mjs", "templates/Dockerfile.template", "templates/frontend.Dockerfile.template", "templates/frontend-container.conf.template", "templates/env.example.template", "templates/deploy.sh.template", "templates/docker-compose.yml", "templates/dockerignore.template"]
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"name": "wdyy-api-standard",
|
|
@@ -1,74 +1,63 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wdyy-deployment-standard
|
|
3
|
-
description: 基于标准五段根 .env,为无 CI/CD
|
|
3
|
+
description: 基于标准五段根 .env,为无 CI/CD 项目生成确定性的前后端带版本双 Docker 镜像、本地 deploy.sh build 导出包、人工上传,以及服务器直接宿主机端口的交互式运行、替换和停止;不使用蓝绿部署、宿主机 Nginx 或数据库迁移。Use when 规划、实现、审查或执行手工生产部署时。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 企业部署规范
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
把开发、构建、上传和服务器操作分开:开发只用 `pnpm dev
|
|
10
|
+
把开发、构建、上传和服务器操作分开:开发只用 `pnpm dev`;本地项目根目录用 `./deploy.sh build` 生成带版本号的前后端镜像和完整 `deploy/`;工程师人工上传;服务器项目根目录无参数运行 `./deploy.sh`,按菜单直接运行或替换固定服务。
|
|
11
11
|
|
|
12
12
|
## 使用边界
|
|
13
13
|
|
|
14
|
-
1. 先读取目标项目真实的 pnpm scripts、前后端构建输出、健康/版本接口、Dockerfile
|
|
15
|
-
2. 完整读取 [Linux 部署规则](reference/linux-deployment-rules.md),运行 `node scripts/generate-deployment-files.mjs --target <目标项目根目录> --write` 确定性生成受版本控制文件,并向 `.env` 与 `.env.example`
|
|
16
|
-
3.
|
|
17
|
-
4. 工程师人工上传 `deploy/`
|
|
14
|
+
1. 先读取目标项目真实的 pnpm scripts、前后端构建输出、健康/版本接口、Dockerfile 和 `.env`。
|
|
15
|
+
2. 完整读取 [Linux 部署规则](reference/linux-deployment-rules.md),运行 `node scripts/generate-deployment-files.mjs --target <目标项目根目录> --write` 确定性生成受版本控制文件,并向 `.env` 与 `.env.example` 的唯一“部署”段追加同名配置;既有文件不一致时先审查差异,只有工程师确认后才使用 `--force`。
|
|
16
|
+
3. 应用监听和宿主机直接暴露端口只使用 `FRONTEND_URL`、`FRONTEND_PORT`、`BACKEND_URL`、`BACKEND_PORT`;不得增加重复容器端口或健康 URL 配置。
|
|
17
|
+
4. 工程师人工上传 `deploy/` 全部内容作为服务器项目根目录;脚本不得上传、拉取代码、安装依赖或修改宿主机 Nginx。
|
|
18
18
|
5. 生成后先运行生成器的 `--check`,再运行 `scripts/validate-deployment-package.mjs` 和全部行为测试。
|
|
19
19
|
|
|
20
20
|
## 固定契约
|
|
21
21
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- Compose
|
|
25
|
-
-
|
|
26
|
-
-
|
|
22
|
+
- 项目名只允许小写字母、数字和下划线;服务器部署根由上传后的 `deploy.sh` 物理路径确定。
|
|
23
|
+
- 镜像必须精确命名为 `<项目名称>_frontend:<YYYYMMDD-NNN>` 和 `<项目名称>_backend:<YYYYMMDD-NNN>`。
|
|
24
|
+
- Compose 只允许 frontend、backend 两个服务,不设置 `container_name`,不包含颜色、切流或回滚状态。
|
|
25
|
+
- frontend 直接映射 `${DOCKER_BIND_IP}:${FRONTEND_PORT}:${FRONTEND_PORT}`;backend 直接映射 `${DOCKER_BIND_IP}:${BACKEND_PORT}:${BACKEND_PORT}`。
|
|
26
|
+
- 两个容器只把服务器项目根 `./logs` 挂载到 `/app/logs`;日志文件直接位于 `logs/`,不得创建服务、实例、颜色或日期子目录。
|
|
27
|
+
- 服务器部署操作追加写入 `./logs/deploy.log`;前端 Nginx 写入 `frontend-access.log` 和 `frontend-error.log`;后端遵循 `wdyy-logging-standard`。
|
|
27
28
|
- 根 `.env` 原样复制到 `deploy/.env` 并人工上传,但必须被 `.dockerignore` 排除,且不得进入版本目录或任一镜像。
|
|
28
|
-
- `FRONTEND_BASE_IMAGE` 与 `BACKEND_BASE_IMAGE`
|
|
29
|
-
- 环境文件固定按“前端、后端、数据库、API
|
|
30
|
-
-
|
|
31
|
-
- 相同输入生成的 deploy.sh、Compose、Nginx 配置必须字节一致;版本指针、镜像归档和 manifest 可随发布变化。
|
|
29
|
+
- `.env.example` 内置 `FRONTEND_BASE_IMAGE=nginx:stable` 与 `BACKEND_BASE_IMAGE=node:24-alpine3.24`;`.env` 可显式覆盖。
|
|
30
|
+
- 环境文件固定按“前端、后端、数据库、API、部署”排列;部署段只包含 `PROJECT_NAME`、`DOCKER_BIND_IP`、`FRONTEND_BASE_IMAGE`、`BACKEND_BASE_IMAGE`。
|
|
31
|
+
- 发布链路不接受数据库迁移参数,也不打包、校验或执行迁移文件。
|
|
32
32
|
|
|
33
33
|
## 工作流
|
|
34
34
|
|
|
35
35
|
1. 使用 `pnpm dev` 完成开发测试。
|
|
36
|
-
2.
|
|
37
|
-
3. 依次通过测试、lint
|
|
38
|
-
4. 人工把 `deploy/`
|
|
39
|
-
5.
|
|
40
|
-
6.
|
|
41
|
-
7. 通过 `restart`、`stop`、`status`、`rollback <version>` 管理当前项目;`remove` 只删除本项目容器、网络及两个精确镜像仓库。
|
|
42
|
-
|
|
43
|
-
## Nginx 多项目隔离
|
|
44
|
-
|
|
45
|
-
- 运维只在服务器初次接入时让共享 Nginx include `/xxzx/home/*/nginx/site.conf`;部署脚本不得编辑该全局配置。
|
|
46
|
-
- 每个项目只写自身 `nginx/site.conf` 和 `nginx/active-upstreams.conf`。
|
|
47
|
-
- upstream 名称由严格项目名确定,不接受自由文本配置。
|
|
48
|
-
- 在共享文件锁内检查其他项目配置和运行中容器的实际发布端口、安装候选配置、运行进程级 `nginx -t` 和 reload;失败时恢复本项目旧文件。
|
|
49
|
-
- 其他项目配置错误可以阻止本次 reload,但不得强制跳过检查或改写其他项目文件。
|
|
36
|
+
2. 在本地项目根目录执行不带额外参数的 `./deploy.sh build`;从 `AGENTS.md` 唯一“发布记录”表生成 `YYYYMMDD-NNN`。
|
|
37
|
+
3. 依次通过测试、lint、类型检查、构建、双镜像标签检查、包校验和配置确定性检查;成功后才替换 `deploy/` 并写发布记录。
|
|
38
|
+
4. 人工把 `deploy/` 全部内容上传为服务器项目根目录;生产值不得通过命令行传入。
|
|
39
|
+
5. 在服务器项目根目录无参数执行 `./deploy.sh`,选择:运行或替换前端、运行或替换后端、运行或替换前后端、停止。
|
|
40
|
+
6. 运行或替换会载入所选服务的目标版本镜像、强制重建并健康检查;停止会同时停止 frontend 和 backend。
|
|
50
41
|
|
|
51
42
|
## 禁止事项
|
|
52
43
|
|
|
53
|
-
-
|
|
54
|
-
-
|
|
44
|
+
- 不得使用蓝绿、滚动切流、活动/历史颜色、宿主机 Nginx、自动回滚或旧版 start/replace/restart/remove/status/rollback 命令。
|
|
45
|
+
- 不得接受人工 build 版本参数,不得扫描版本目录替代发布账本或 `release.env`。
|
|
46
|
+
- 不得使用 Bash `source`/`eval` 解析 `.env` 或版本指针。
|
|
55
47
|
- 不得使用镜像 fallback、固定 `/srv`、固定 URL/IP/端口、PM2、systemd 或自动安装。
|
|
56
|
-
- 不得重新引入 `FRONTEND_CONTAINER_PORT`、`BACKEND_CONTAINER_PORT`、`BACKEND_LISTEN_HOST`
|
|
57
|
-
- 不得先停止活动颜色再启动新颜色,不得分别切换前后端,不得在验证前 reload。
|
|
48
|
+
- 不得重新引入 `PROJECT_HTTP_PORT`、`FRONTEND_BLUE_PORT`、`FRONTEND_GREEN_PORT`、`BACKEND_BLUE_PORT`、`BACKEND_GREEN_PORT`、`FRONTEND_CONTAINER_PORT`、`BACKEND_CONTAINER_PORT`、`BACKEND_LISTEN_HOST` 或任何 `*_HEALTH_URL`、`*_VERSION_URL`。
|
|
58
49
|
- 不得自动执行 psql、迁移脚本或数据库回滚。
|
|
59
|
-
-
|
|
60
|
-
- 不得以字符串关键字存在代替命令行为、失败恢复和资源边界测试。
|
|
50
|
+
- 不得以字符串关键字存在代替命令行为、失败传播和资源边界测试。
|
|
61
51
|
|
|
62
52
|
## Verification
|
|
63
53
|
|
|
64
|
-
- [ ]
|
|
65
|
-
- [ ]
|
|
66
|
-
- [ ]
|
|
67
|
-
- [ ]
|
|
68
|
-
- [ ]
|
|
69
|
-
- [ ]
|
|
70
|
-
- [ ]
|
|
54
|
+
- [ ] 两个镜像仓库名、版本标签和 OCI version label 精确匹配发布版本。
|
|
55
|
+
- [ ] Compose 只含 frontend、backend,使用根 `.env`、直接宿主机端口和 `../../logs:/app/logs`。
|
|
56
|
+
- [ ] 服务器无参数菜单只有四项;独立和同时替换不会操作未选择服务;停止同时停止两服务。
|
|
57
|
+
- [ ] `.env` 与 `.env.example` 五段、变量顺序一致,无蓝绿、Nginx、重复端口或日志目录配置。
|
|
58
|
+
- [ ] 部署、前端和后端日志直接位于项目根 `logs/`,容器替换或停止后仍保留。
|
|
59
|
+
- [ ] 不存在数据库迁移参数、迁移包内容、上传命令或数据库执行路径。
|
|
60
|
+
- [ ] 连续两次构建的 deploy.sh 和 Compose 一致,失败不写发布记录、不暴露半成品。
|
|
71
61
|
- [ ] `deploy/` 含根 `.env`,但版本目录、Docker 上下文和镜像不含秘密文件。
|
|
72
|
-
- [ ] 替换或删除容器后,项目根 `logs/` 内容仍保留。
|
|
73
62
|
|
|
74
|
-
|
|
63
|
+
任一步失败必须明确报告具体阶段并返回非零;不得静默跳过、伪造成功或删除失败证据。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "wdyy-deployment-standard"
|
|
3
|
-
short_description: "Build
|
|
4
|
-
default_prompt: "Use $wdyy-deployment-standard to generate and validate
|
|
3
|
+
short_description: "Build and operate direct dual-image deployments"
|
|
4
|
+
default_prompt: "Use $wdyy-deployment-standard to generate and validate a versioned dual-image manual deployment with direct host ports and an interactive server menu."
|
|
@@ -9,17 +9,16 @@ deploy.sh
|
|
|
9
9
|
.dockerignore
|
|
10
10
|
src/frontend/Dockerfile
|
|
11
11
|
src/backend/Dockerfile
|
|
12
|
-
scripts/deployment/docker-compose.
|
|
12
|
+
scripts/deployment/docker-compose.yml
|
|
13
13
|
scripts/deployment/frontend-container.conf.template
|
|
14
|
-
scripts/deployment/nginx-site.conf
|
|
15
14
|
.env.example
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
这些文件必须由 `scripts/generate-deployment-files.mjs --target <project-root> --write` 按固定映射原子生成,templates
|
|
17
|
+
这些文件必须由 `scripts/generate-deployment-files.mjs --target <project-root> --write` 按固定映射原子生成,templates 是唯一源。既有差异默认失败,工程师审查确认后才允许 `--force`。生成后执行 `--check`。相同输入连续构建时 deploy.sh 与 Compose 必须字节一致。
|
|
19
18
|
|
|
20
19
|
## 根 .env
|
|
21
20
|
|
|
22
|
-
根 `.env` 是本地 build、上传包和服务器 Compose 的唯一配置文件。`.env` 与 `.env.example` 固定按“前端、后端、数据库、API
|
|
21
|
+
根 `.env` 是本地 build、上传包和服务器 Compose 的唯一配置文件。`.env` 与 `.env.example` 固定按“前端、后端、数据库、API、部署”排列。部署前至少要求:
|
|
23
22
|
|
|
24
23
|
```dotenv
|
|
25
24
|
FRONTEND_URL=0.0.0.0
|
|
@@ -33,35 +32,25 @@ DB_PASSWORD=
|
|
|
33
32
|
DB_NAME=
|
|
34
33
|
DB_SCHEMA=
|
|
35
34
|
PROJECT_NAME=example_project
|
|
36
|
-
|
|
37
|
-
PROJECT_HTTP_PORT=9099
|
|
38
|
-
DOCKER_BIND_IP=127.0.0.1
|
|
39
|
-
FRONTEND_BLUE_PORT=19091
|
|
40
|
-
FRONTEND_GREEN_PORT=19092
|
|
41
|
-
BACKEND_BLUE_PORT=19093
|
|
42
|
-
BACKEND_GREEN_PORT=19094
|
|
35
|
+
DOCKER_BIND_IP=0.0.0.0
|
|
43
36
|
FRONTEND_BASE_IMAGE=nginx:stable
|
|
44
37
|
BACKEND_BASE_IMAGE=node:24-alpine3.24
|
|
45
|
-
DATABASE_MIGRATION_MODE=none
|
|
46
38
|
```
|
|
47
39
|
|
|
48
|
-
`
|
|
40
|
+
`FRONTEND_PORT`、`BACKEND_PORT` 同时是容器监听端口与宿主机直接暴露端口,二者必须不同且位于 1–65535。`PROJECT_NAME` 只允许 `^[a-z][a-z0-9_]*$`。`.env` 权限必须为 `0600` 或只读的 `0400`。脚本按数据解析环境文件,不得 `source` 或 `eval`。
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`.env` 权限必须为 `0600` 或只读的 `0400`。部署脚本只把明确的部署键载入自身环境;数据库密码、JWT 等其他应用键保留在文件中供 Compose 注入容器,不得改变脚本的 `PATH`、Shell 选项或命令行为。
|
|
53
|
-
|
|
54
|
-
可选路径键为 `FRONTEND_DOCKERFILE`、`BACKEND_DOCKERFILE`、`MIGRATIONS_SOURCE`、`MIGRATION_RUNNER_SOURCE`、`DEPLOY_CONFIG_DIR`;未设置时只能使用与目标项目真实结构一致且已验证的标准路径。
|
|
42
|
+
可选路径键为 `FRONTEND_DOCKERFILE`、`BACKEND_DOCKERFILE`、`DOCKERIGNORE_FILE`、`DEPLOY_CONFIG_DIR` 和 `COMPOSE_SOURCE`;它们不属于标准 `.env.example`,只在目标项目确有非标准结构且已确认时使用。
|
|
55
43
|
|
|
56
44
|
## Docker 与日志
|
|
57
45
|
|
|
58
46
|
- 前端镜像:`${PROJECT_NAME}_frontend:${RELEASE_VERSION}`。
|
|
59
47
|
- 后端镜像:`${PROJECT_NAME}_backend:${RELEASE_VERSION}`。
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
48
|
+
- Compose project 使用 `$PROJECT_NAME`,只定义 frontend、backend,不设置 `container_name`。
|
|
49
|
+
- frontend 端口映射为 `${DOCKER_BIND_IP}:${FRONTEND_PORT}:${FRONTEND_PORT}`;backend 同理使用 `BACKEND_PORT`。
|
|
50
|
+
- 两个服务均使用 `../../.env`,且只挂载 `../../logs:/app/logs`。
|
|
51
|
+
- 前端 Nginx access/error 直接写 `/app/logs/frontend-access.log`、`/app/logs/frontend-error.log`。
|
|
52
|
+
- 后端日志直接写 `/app/logs`;服务器菜单操作追加到部署根 `logs/deploy.log`。
|
|
53
|
+
- `.dockerignore` 必须排除 `.env`、秘密、deploy、logs、Git、依赖缓存和虚拟环境。
|
|
65
54
|
|
|
66
55
|
## 发布账本与本地包
|
|
67
56
|
|
|
@@ -75,7 +64,7 @@ DATABASE_MIGRATION_MODE=none
|
|
|
75
64
|
| 20260808-001 | 2026-08-08 09:30:00 +08:00 |
|
|
76
65
|
```
|
|
77
66
|
|
|
78
|
-
build
|
|
67
|
+
build 使用项目级原子锁,严格验证账本后生成当天下一序号。全部门禁、双镜像、包结构和确定性校验通过后才写账本。
|
|
79
68
|
|
|
80
69
|
```text
|
|
81
70
|
deploy/
|
|
@@ -86,50 +75,25 @@ deploy/
|
|
|
86
75
|
├── frontend-image.tar
|
|
87
76
|
├── backend-image.tar
|
|
88
77
|
├── manifest.sha256
|
|
89
|
-
|
|
90
|
-
├── nginx/site.conf
|
|
91
|
-
└── database/ # 仅 manual 模式
|
|
92
|
-
├── migrations/
|
|
93
|
-
└── apply-migrations.sh
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
`release.env` 只包含当前版本。manifest 覆盖版本目录内除自身外的全部普通文件,禁止符号链接、硬链接秘密、绝对路径和 `..`。根 `.env` 可上传,但版本目录与镜像不得包含它。
|
|
97
|
-
|
|
98
|
-
## 服务器部署和 Nginx
|
|
99
|
-
|
|
100
|
-
上传目标必须是 `${SERVER_PROJECTS_ROOT}/${PROJECT_NAME}`,生产值为 `/xxzx/home/<项目名称>`。脚本通过自身物理路径验证该关系,不依赖调用者当前目录。
|
|
101
|
-
|
|
102
|
-
共享 Nginx 主配置由运维预先 include `/xxzx/home/*/nginx/site.conf`。项目脚本不得写 `/etc/nginx` 或其他项目,只维护:
|
|
103
|
-
|
|
104
|
-
```text
|
|
105
|
-
<project-root>/nginx/site.conf
|
|
106
|
-
<project-root>/nginx/active-upstreams.conf
|
|
78
|
+
└── docker/docker-compose.yml
|
|
107
79
|
```
|
|
108
80
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
## 命令
|
|
81
|
+
`release.env` 只包含当前版本。manifest 覆盖版本目录内除自身外的全部普通文件。根 `.env` 可上传,但版本目录与镜像不得包含它。
|
|
112
82
|
|
|
113
|
-
|
|
114
|
-
- `./deploy.sh replace`:只允许已有活动状态且 release.env 指向不同新版本。
|
|
115
|
-
- `./deploy.sh restart`:重新创建并验证当前颜色的两个容器,不改变流量颜色和版本。
|
|
116
|
-
- `./deploy.sh stop`:停止当前颜色的两个容器,保留所有文件和状态。
|
|
117
|
-
- `./deploy.sh remove`:删除 Compose project 下四个容器及网络,并删除两个精确项目镜像仓库的全部标签;保留日志、包、状态和 Nginx 文件。
|
|
118
|
-
- `./deploy.sh status`:报告 active/previous 和四服务状态。
|
|
119
|
-
- `./deploy.sh rollback <version>`:只允许 previous 中已保留且健康的成对容器。
|
|
83
|
+
## 服务器菜单
|
|
120
84
|
|
|
121
|
-
|
|
85
|
+
工程师把 `deploy/` 内容完整上传为服务器项目根目录,随后无参数执行 `./deploy.sh`:
|
|
122
86
|
|
|
123
|
-
|
|
87
|
+
1. 运行或替换前端:验证包和前端镜像标签,只载入并重建 frontend,验证 `/health` 与 `/version`。
|
|
88
|
+
2. 运行或替换后端:验证包和后端镜像标签,只载入并重建 backend,验证 `/health` 与 `/version`。
|
|
89
|
+
3. 运行或替换前后端:载入并重建两个服务,两个服务全部验证成功后报告成功。
|
|
90
|
+
4. 停止:同时停止 frontend 和 backend,保留容器、镜像、上传文件和日志。
|
|
124
91
|
|
|
125
|
-
|
|
126
|
-
- `DATABASE_MIGRATION_MODE=manual`:必须打包完整迁移目录和可执行脚本。
|
|
127
|
-
- deploy.sh 的任何服务器命令都不得调用迁移脚本、psql 或数据库客户端。
|
|
128
|
-
- build 完成和服务器 start/replace 必须明确提示 manual 操作尚需工程师单独执行,不得声称已迁移。
|
|
92
|
+
菜单操作使用 `release.env` 指向的版本,不接受版本参数。非法输入必须在 Docker 状态变更前失败。部署根始终由脚本物理路径确定。
|
|
129
93
|
|
|
130
|
-
##
|
|
94
|
+
## 数据库和失败边界
|
|
131
95
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
96
|
+
- 部署 Skill 不接受数据库迁移参数,不打包、校验、提示或执行迁移文件。
|
|
97
|
+
- 固定服务替换允许短暂停机;失败时返回非零并记录阶段,不自动回滚或伪造成功。
|
|
98
|
+
- 脚本不得上传文件、拉取代码、安装软件、修改宿主机 Nginx 或操作其他项目资源。
|
|
99
|
+
- 旧蓝绿容器、状态和宿主机 Nginx 配置必须由工程师按旧部署实际状态单独迁移,新脚本不得猜测并自动删除。
|
|
@@ -10,9 +10,8 @@ const mappings = [
|
|
|
10
10
|
['dockerignore.template', '.dockerignore', 0o644],
|
|
11
11
|
['frontend.Dockerfile.template', 'src/frontend/Dockerfile', 0o644],
|
|
12
12
|
['Dockerfile.template', 'src/backend/Dockerfile', 0o644],
|
|
13
|
-
['docker-compose.
|
|
13
|
+
['docker-compose.yml', 'scripts/deployment/docker-compose.yml', 0o644],
|
|
14
14
|
['frontend-container.conf.template', 'scripts/deployment/frontend-container.conf.template', 0o644],
|
|
15
|
-
['nginx-upstream.template.conf', 'scripts/deployment/nginx-site.conf', 0o644],
|
|
16
15
|
];
|
|
17
16
|
|
|
18
17
|
const sectionNames = ['前端', '后端', '数据库', 'API', '部署'];
|
|
@@ -22,11 +21,16 @@ const baseKeys = new Map([
|
|
|
22
21
|
['数据库', ['DB_URL', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME', 'DB_SCHEMA']],
|
|
23
22
|
]);
|
|
24
23
|
const deploymentKeys = [
|
|
25
|
-
'PROJECT_NAME', '
|
|
26
|
-
'
|
|
27
|
-
'FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE', 'DATABASE_MIGRATION_MODE',
|
|
24
|
+
'PROJECT_NAME', 'DOCKER_BIND_IP',
|
|
25
|
+
'FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE',
|
|
28
26
|
];
|
|
29
27
|
|
|
28
|
+
const removedDeploymentKeys = new Set([
|
|
29
|
+
'PROJECT_HTTP_PORT', 'FRONTEND_BLUE_PORT', 'FRONTEND_GREEN_PORT',
|
|
30
|
+
'BACKEND_BLUE_PORT', 'BACKEND_GREEN_PORT', 'SERVER_PROJECTS_ROOT',
|
|
31
|
+
'NGINX_SOURCE', 'LOG_DIR', 'HOST_LOG_DIR',
|
|
32
|
+
]);
|
|
33
|
+
|
|
30
34
|
function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults, exampleValues) {
|
|
31
35
|
const normalized = text.replaceAll('\r\n', '\n');
|
|
32
36
|
const sections = new Map(sectionNames.map((name) => [name, []]));
|
|
@@ -47,7 +51,7 @@ function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults,
|
|
|
47
51
|
const entry = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
48
52
|
if (!entry) throw new Error(`${relativeTarget}:${index + 1} must use KEY=VALUE syntax`);
|
|
49
53
|
if (seenKeys.has(entry[1])) throw new Error(`${relativeTarget} contains duplicate key: ${entry[1]}`);
|
|
50
|
-
if (['FRONTEND_CONTAINER_PORT', 'BACKEND_CONTAINER_PORT', 'BACKEND_LISTEN_HOST'].includes(entry[1]) || /_(?:HEALTH|VERSION)_URL$/.test(entry[1])) {
|
|
54
|
+
if (removedDeploymentKeys.has(entry[1]) || ['FRONTEND_CONTAINER_PORT', 'BACKEND_CONTAINER_PORT', 'BACKEND_LISTEN_HOST'].includes(entry[1]) || /_(?:HEALTH|VERSION)_URL$/.test(entry[1])) {
|
|
51
55
|
throw new Error(`${relativeTarget} contains legacy duplicate configuration: ${entry[1]}`);
|
|
52
56
|
}
|
|
53
57
|
seenKeys.add(entry[1]);
|
|
@@ -73,8 +77,14 @@ function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults,
|
|
|
73
77
|
if (actualDeploymentKeys.length !== 0 && actualDeploymentKeys.join('|') !== deploymentKeys.join('|')) {
|
|
74
78
|
throw new Error(`${relativeTarget} # 部署 keys must be empty or exactly: ${deploymentKeys.join(', ')}`);
|
|
75
79
|
}
|
|
76
|
-
if (requireExampleDefaults && sections.get('部署').some(({ value }) => value !== '')) {
|
|
77
|
-
throw new Error(`${relativeTarget} deployment example values must be empty`);
|
|
80
|
+
if (requireExampleDefaults && sections.get('部署').some(({ key, value }) => !['FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE'].includes(key) && value !== '')) {
|
|
81
|
+
throw new Error(`${relativeTarget} deployment example values must be empty except base-image defaults`);
|
|
82
|
+
}
|
|
83
|
+
if (requireExampleDefaults) {
|
|
84
|
+
for (const key of ['FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE']) {
|
|
85
|
+
const entry = sections.get('部署').find((item) => item.key === key);
|
|
86
|
+
if (entry && entry.value !== exampleValues.get(key)) throw new Error(`${relativeTarget} must keep the standard default for ${key}`);
|
|
87
|
+
}
|
|
78
88
|
}
|
|
79
89
|
return {
|
|
80
90
|
extended: actualDeploymentKeys.length === deploymentKeys.length,
|
|
@@ -83,7 +93,7 @@ function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults,
|
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
function extendDeploymentSection(text) {
|
|
86
|
-
return `${text.trimEnd()}\n${deploymentKeys.map((key) => `${key}
|
|
96
|
+
return `${text.trimEnd()}\n${deploymentKeys.map((key) => `${key}=${['FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE'].includes(key) ? exampleValues.get(key) : ''}`).join('\n')}\n`;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
const args = process.argv.slice(2);
|
package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.test.mjs
CHANGED
|
@@ -34,11 +34,16 @@ test('确定性生成全部标准文件并通过 check', async () => {
|
|
|
34
34
|
const checked = run(root, '--check');
|
|
35
35
|
assert.equal(checked.status, 0, checked.stderr);
|
|
36
36
|
assert.match(checked.stdout, /verified deterministic deployment files/);
|
|
37
|
-
assert.match(await readFile(join(root, 'deploy.sh'), 'utf8'), /
|
|
37
|
+
assert.match(await readFile(join(root, 'deploy.sh'), 'utf8'), /server_menu/);
|
|
38
|
+
assert.match(await readFile(join(root, 'scripts/deployment/docker-compose.yml'), 'utf8'), /^ frontend:[\s\S]*^ backend:/m);
|
|
39
|
+
await assert.rejects(stat(join(root, 'scripts/deployment/docker-compose.blue-green.yml')));
|
|
38
40
|
const environment = await readFile(join(root, '.env'), 'utf8');
|
|
39
41
|
const example = await readFile(join(root, '.env.example'), 'utf8');
|
|
40
42
|
assert.match(environment, /^# 前端\nFRONTEND_URL=0\.0\.0\.0\nFRONTEND_PORT=5173$/m);
|
|
41
|
-
assert.match(environment, /^# 部署\nPROJECT_NAME=\
|
|
43
|
+
assert.match(environment, /^# 部署\nPROJECT_NAME=\nDOCKER_BIND_IP=/m);
|
|
44
|
+
assert.doesNotMatch(environment, /PROJECT_HTTP_PORT|BLUE_PORT|GREEN_PORT/);
|
|
45
|
+
assert.match(environment, /^FRONTEND_BASE_IMAGE=nginx:stable$/m);
|
|
46
|
+
assert.match(environment, /^BACKEND_BASE_IMAGE=node:24-alpine3\.24$/m);
|
|
42
47
|
assert.equal(example, environment);
|
|
43
48
|
if (process.platform !== 'win32') assert.equal((await stat(join(root, '.env'))).mode & 0o777, 0o600);
|
|
44
49
|
});
|
|
@@ -104,3 +109,12 @@ test('重复或冲突参数被明确拒绝', async () => {
|
|
|
104
109
|
assert.notEqual(duplicateTarget.status, 0);
|
|
105
110
|
assert.match(duplicateTarget.stderr, /Duplicate or missing --target argument/);
|
|
106
111
|
});
|
|
112
|
+
|
|
113
|
+
test('旧蓝绿和宿主机 Nginx 配置键被明确拒绝', async () => {
|
|
114
|
+
const root = await projectRoot();
|
|
115
|
+
const envPath = join(root, '.env');
|
|
116
|
+
await writeFile(envPath, (await readFile(envPath, 'utf8')).replace('# 部署\n', '# 部署\nPROJECT_HTTP_PORT=9099\n'), { mode: 0o600 });
|
|
117
|
+
const generated = run(root, '--write');
|
|
118
|
+
assert.notEqual(generated.status, 0);
|
|
119
|
+
assert.match(generated.stderr, /legacy duplicate configuration/);
|
|
120
|
+
});
|