@wdyy/skills 0.1.5 → 0.1.7

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.
Files changed (24) hide show
  1. package/.well-known/skills/index.json +3 -3
  2. package/.well-known/skills/wdyy-deployment-standard/SKILL.md +51 -46
  3. package/.well-known/skills/wdyy-deployment-standard/agents/openai.yaml +3 -3
  4. package/.well-known/skills/wdyy-deployment-standard/reference/linux-deployment-rules.md +133 -7
  5. package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.mjs +213 -0
  6. package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.test.mjs +106 -0
  7. package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.mjs +201 -32
  8. package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.test.mjs +548 -51
  9. package/.well-known/skills/wdyy-deployment-standard/templates/Dockerfile.template +11 -4
  10. package/.well-known/skills/wdyy-deployment-standard/templates/deploy.sh.template +835 -142
  11. package/.well-known/skills/wdyy-deployment-standard/templates/docker-compose.blue-green.yml +48 -12
  12. package/.well-known/skills/wdyy-deployment-standard/templates/dockerignore.template +17 -0
  13. package/.well-known/skills/wdyy-deployment-standard/templates/env.example.template +30 -0
  14. package/.well-known/skills/wdyy-deployment-standard/templates/frontend-container.conf.template +24 -0
  15. package/.well-known/skills/wdyy-deployment-standard/templates/frontend.Dockerfile.template +7 -0
  16. package/.well-known/skills/wdyy-deployment-standard/templates/nginx-upstream.template.conf +17 -17
  17. package/.well-known/skills/wdyy-logging-standard/SKILL.md +8 -7
  18. package/.well-known/skills/wdyy-logging-standard/agents/openai.yaml +2 -2
  19. package/.well-known/skills/wdyy-logging-standard/reference/logging-rules.md +4 -3
  20. package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.test.mjs +50 -1
  21. package/.well-known/skills/wdyy-logging-standard/templates/logger.template.ts +7 -0
  22. package/README.md +2 -2
  23. package/lib/wdyy-cli.js +35 -9
  24. package/package.json +1 -1
@@ -1,34 +1,70 @@
1
1
  services:
2
+ frontend-blue:
3
+ image: ${FRONTEND_IMAGE:?FRONTEND_IMAGE is required}
4
+ env_file: ../../.env
5
+ environment:
6
+ INSTANCE_ID: blue
7
+ NGINX_ENVSUBST_FILTER: "^(INSTANCE_ID|FRONTEND_URL|FRONTEND_PORT|RELEASE_VERSION)$"
8
+ ports:
9
+ - "${DOCKER_BIND_IP}:${FRONTEND_BLUE_PORT}:${FRONTEND_PORT}"
10
+ volumes:
11
+ - ../../logs:/app/logs
12
+ restart: unless-stopped
13
+ healthcheck:
14
+ test: ["CMD-SHELL", "probe_host=$${FRONTEND_URL}; [ \"$${probe_host}\" != 0.0.0.0 ] || probe_host=127.0.0.1; wget -qO- \"http://$${probe_host}:$${FRONTEND_PORT}/health\""]
15
+ interval: 10s
16
+ timeout: 3s
17
+ retries: 6
18
+
2
19
  backend-blue:
3
- image: ${BACKEND_BLUE_IMAGE:-backend-blue:not-configured}
4
- env_file: ../../.env.production
20
+ image: ${BACKEND_IMAGE:?BACKEND_IMAGE is required}
21
+ env_file: ../../.env
5
22
  environment:
6
23
  INSTANCE_ID: blue
7
- LOG_DIR: /var/log/application
24
+ HOST: ${BACKEND_URL}
25
+ PORT: ${BACKEND_PORT}
26
+ ports:
27
+ - "${DOCKER_BIND_IP}:${BACKEND_BLUE_PORT}:${BACKEND_PORT}"
28
+ volumes:
29
+ - ../../logs:/app/logs
30
+ restart: unless-stopped
31
+ healthcheck:
32
+ test: ["CMD-SHELL", "probe_host=$${BACKEND_URL}; [ \"$${probe_host}\" != 0.0.0.0 ] || probe_host=127.0.0.1; wget -qO- \"http://$${probe_host}:$${BACKEND_PORT}/health\""]
33
+ interval: 10s
34
+ timeout: 3s
35
+ retries: 6
36
+
37
+ frontend-green:
38
+ image: ${FRONTEND_IMAGE:?FRONTEND_IMAGE is required}
39
+ env_file: ../../.env
40
+ environment:
41
+ INSTANCE_ID: green
42
+ NGINX_ENVSUBST_FILTER: "^(INSTANCE_ID|FRONTEND_URL|FRONTEND_PORT|RELEASE_VERSION)$"
8
43
  ports:
9
- - "127.0.0.1:${BLUE_PORT:-3001}:3000"
44
+ - "${DOCKER_BIND_IP}:${FRONTEND_GREEN_PORT}:${FRONTEND_PORT}"
10
45
  volumes:
11
- - ${HOST_LOG_DIR:?set HOST_LOG_DIR}/backend/blue:/var/log/application
46
+ - ../../logs:/app/logs
12
47
  restart: unless-stopped
13
48
  healthcheck:
14
- test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
49
+ test: ["CMD-SHELL", "probe_host=$${FRONTEND_URL}; [ \"$${probe_host}\" != 0.0.0.0 ] || probe_host=127.0.0.1; wget -qO- \"http://$${probe_host}:$${FRONTEND_PORT}/health\""]
15
50
  interval: 10s
16
51
  timeout: 3s
17
52
  retries: 6
18
53
 
19
54
  backend-green:
20
- image: ${BACKEND_GREEN_IMAGE:-backend-green:not-configured}
21
- env_file: ../../.env.production
55
+ image: ${BACKEND_IMAGE:?BACKEND_IMAGE is required}
56
+ env_file: ../../.env
22
57
  environment:
23
58
  INSTANCE_ID: green
24
- LOG_DIR: /var/log/application
59
+ HOST: ${BACKEND_URL}
60
+ PORT: ${BACKEND_PORT}
25
61
  ports:
26
- - "127.0.0.1:${GREEN_PORT:-3002}:3000"
62
+ - "${DOCKER_BIND_IP}:${BACKEND_GREEN_PORT}:${BACKEND_PORT}"
27
63
  volumes:
28
- - ${HOST_LOG_DIR:?set HOST_LOG_DIR}/backend/green:/var/log/application
64
+ - ../../logs:/app/logs
29
65
  restart: unless-stopped
30
66
  healthcheck:
31
- test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
67
+ test: ["CMD-SHELL", "probe_host=$${BACKEND_URL}; [ \"$${probe_host}\" != 0.0.0.0 ] || probe_host=127.0.0.1; wget -qO- \"http://$${probe_host}:$${BACKEND_PORT}/health\""]
32
68
  interval: 10s
33
69
  timeout: 3s
34
70
  retries: 6
@@ -0,0 +1,17 @@
1
+ .env
2
+ .env.*
3
+ !.env.example
4
+ *.pem
5
+ *.key
6
+ id_rsa
7
+ id_ed25519
8
+ credentials
9
+ credentials.*
10
+ deploy/
11
+ logs/
12
+ .git/
13
+ node_modules/
14
+ venv/
15
+ .pnpm-store/
16
+ .deploy-build.lock/
17
+ .deploy-build.*
@@ -0,0 +1,30 @@
1
+ # 前端
2
+ FRONTEND_URL=0.0.0.0
3
+ FRONTEND_PORT=5173
4
+
5
+ # 后端
6
+ BACKEND_URL=0.0.0.0
7
+ BACKEND_PORT=3000
8
+
9
+ # 数据库
10
+ DB_URL=
11
+ DB_PORT=5432
12
+ DB_USER=
13
+ DB_PASSWORD=
14
+ DB_NAME=
15
+ DB_SCHEMA=
16
+
17
+ # API
18
+
19
+ # 部署
20
+ PROJECT_NAME=
21
+ SERVER_PROJECTS_ROOT=
22
+ PROJECT_HTTP_PORT=
23
+ DOCKER_BIND_IP=
24
+ FRONTEND_BLUE_PORT=
25
+ FRONTEND_GREEN_PORT=
26
+ BACKEND_BLUE_PORT=
27
+ BACKEND_GREEN_PORT=
28
+ FRONTEND_BASE_IMAGE=
29
+ BACKEND_BASE_IMAGE=
30
+ DATABASE_MIGRATION_MODE=
@@ -0,0 +1,24 @@
1
+ server {
2
+ listen ${FRONTEND_URL}:${FRONTEND_PORT};
3
+ server_name _;
4
+ root /usr/share/nginx/html;
5
+ index index.html;
6
+ access_log /app/logs/frontend-${INSTANCE_ID}-access.log;
7
+ error_log /app/logs/frontend-${INSTANCE_ID}-error.log warn;
8
+
9
+ location = /health {
10
+ access_log off;
11
+ default_type text/plain;
12
+ return 200 'ok';
13
+ }
14
+
15
+ location = /version {
16
+ access_log off;
17
+ default_type text/plain;
18
+ return 200 '${RELEASE_VERSION}';
19
+ }
20
+
21
+ location / {
22
+ try_files $uri $uri/ /index.html;
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ ARG FRONTEND_BASE_IMAGE
2
+ FROM ${FRONTEND_BASE_IMAGE}
3
+ ARG RELEASE_VERSION
4
+ ENV RELEASE_VERSION=${RELEASE_VERSION}
5
+ LABEL org.opencontainers.image.version=${RELEASE_VERSION}
6
+ COPY src/frontend/dist/ /usr/share/nginx/html/
7
+ COPY scripts/deployment/frontend-container.conf.template /etc/nginx/templates/default.conf.template
@@ -1,26 +1,26 @@
1
- include /srv/<project>/deploy/nginx/backend-active.conf;
1
+ # wdyy-project: __PROJECT_NAME__
2
+ # wdyy-port: __PROJECT_HTTP_PORT__
3
+ # wdyy-docker-ports: __DOCKER_HOST_PORTS__
4
+ include "__DEPLOY_ROOT__/nginx/active-upstreams.conf";
2
5
 
3
6
  server {
7
+ listen __PROJECT_HTTP_PORT__;
8
+ server_name _;
9
+
4
10
  location /api/ {
5
- proxy_pass http://backend_active;
11
+ proxy_pass http://__BACKEND_UPSTREAM_NAME__;
12
+ proxy_set_header Host $host;
13
+ proxy_set_header X-Real-IP $remote_addr;
14
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
15
+ proxy_set_header X-Forwarded-Proto $scheme;
6
16
  proxy_set_header X-Trace-Id $request_id;
7
17
  }
8
18
 
9
- location /releases/ {
10
- root /srv/<project>;
11
- try_files $uri =404;
12
- }
13
-
14
19
  location / {
15
- root /srv/<project>/current;
16
- try_files $uri $uri/ /index.html;
17
- }
18
-
19
- location = /index.html {
20
- add_header Cache-Control "no-cache, no-store, must-revalidate" always;
21
- }
22
-
23
- location ~* ^/releases/.+\.(?:js|css|woff2?|png|jpg|svg)$ {
24
- add_header Cache-Control "public, max-age=31536000, immutable" always;
20
+ proxy_pass http://__FRONTEND_UPSTREAM_NAME__;
21
+ proxy_set_header Host $host;
22
+ proxy_set_header X-Real-IP $remote_addr;
23
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
24
+ proxy_set_header X-Forwarded-Proto $scheme;
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: wdyy-logging-standard
3
- description: 为 NestJS 和 Vue 项目实现前端异常上报、后端统一结构化 JSON 日志、traceId 传递、完整原始入参与 2MB 文件轮转。Use when 编写日志、错误处理、接口日志或审查生产可观测性时。
3
+ description: 为 NestJS 和 Vue 项目实现仅写入项目根目录 ./logs 的结构化 JSON 日志、前端异常上报、traceId 传递、完整原始入参与 2MB 文件轮转。Use when 编写日志、错误处理、接口日志或审查生产可观测性时。
4
4
  ---
5
5
 
6
6
  # 企业日志规范
@@ -19,7 +19,7 @@ description: 为 NestJS 和 Vue 项目实现前端异常上报、后端统一结
19
19
 
20
20
  ## 输入与输出
21
21
 
22
- - 输入:服务名、实例标识、环境、请求上下文、`LOG_DIR`。
22
+ - 输入:服务名、实例标识、环境和请求上下文;应用从项目根目录启动。
23
23
  - 输出:统一 logger、前端异常上报客户端、HTTP 日志中间件、日志文件命名规则和日志验证结果。
24
24
  - 使用 [日志规则](reference/logging-rules.md) 和 [logger 模板](templates/logger.template.ts)。
25
25
 
@@ -31,27 +31,28 @@ description: 为 NestJS 和 Vue 项目实现前端异常上报、后端统一结
31
31
  4. HTTP 请求日志记录完整的原始 `query`(查询参数)和 `body`(请求体);授权请求可记录实际用户、模式、表和权限等入参。服务端返回成功时记录 `result: success`;返回失败时记录 `result: failure`,并在 `response` 中记录完整失败内容。HTTP `statusCode` 为 100–399 时为成功,400–599 时为失败。
32
32
  5. 日志不执行脱敏、数组截断或大对象裁剪;`body` 必须保留完整原始入参。
33
33
  6. Vue 捕获未处理异常和 Promise 拒绝并上报后端;浏览器不得尝试写服务器文件。
34
- 7. 后端直接按 `LOG_DIR/yyyy-mm-dd_hh24-mm-ss.log` 写入,禁止创建子目录。若同一秒需要创建多个文件,依次使用 `yyyy-mm-dd_hh24-mm-ss_1.log`、`yyyy-mm-dd_hh24-mm-ss_2.log` 等递增后缀;既有日志文件不得重命名。最新文件超过 2MB 时创建新文件,并测试轮转。
34
+ 7. 后端只在项目根目录创建 `./logs`,并直接按 `./logs/yyyy-mm-dd_hh24-mm-ss.log` 写入;不得读取 `LOG_DIR`、`HOST_LOG_DIR` 或其他目录配置,不得在 `logs` 中创建任何子目录。使用排他创建避免并发覆盖;同一秒发生冲突时依次使用 `_1`、`_2` 等递增后缀。最新文件超过 2MB 时仍在 `./logs` 创建新文件,既有文件不得移动、重命名或覆盖。
35
35
 
36
36
  ## 禁止事项
37
37
 
38
38
  - 不得将 `message` 仅限制为 `success` 或 `error`。
39
39
  - 不得用 console 输出替代业务日志。
40
+ - 不得将日志写入项目根目录 `./logs` 之外,或按服务、实例、日期、blue/green 颜色创建日志子目录。
40
41
 
41
42
  ## Red Flags
42
43
 
43
44
  - 业务代码直接调用 console,或 `body` 未保留完整原始入参。
44
45
  - 请求日志缺失 traceId、状态码或耗时。
45
- - 同秒创建的日志文件覆盖既有文件,或前端异常未进入统一后端日志。
46
+ - 日志路径可被环境变量改变、生成了日志子目录、同秒文件覆盖既有文件,或前端异常未进入统一后端日志。
46
47
 
47
48
  ## Verification
48
49
 
49
50
  - [ ] 正常、异常和 HTTP 请求日志均为可解析 JSON。
50
51
  - [ ] traceId 跨入口与内部调用可追踪。
51
- - [ ] 完整 HTTP 入参、服务端成功/失败结果映射、失败响应内容与同秒递增命名均有测试覆盖,轮转阈值可验证。
52
+ - [ ] 完整 HTTP 入参、服务端成功/失败结果映射、失败响应内容、固定 `./logs` 路径、同秒递增命名与轮转阈值均有测试覆盖。
52
53
  - [ ] 前端异常上报经过后端身份校验。
53
- - [ ] 日志直接写入持久化挂载的 `LOG_DIR`,容器替换后日志仍保留。
54
- - [ ] `LOG_DIR` 缺失或不可写时服务以明确错误停止。
54
+ - [ ] 日志直接写入项目根目录 `./logs`,且容器替换后日志仍保留。
55
+ - [ ] 项目根目录或 `./logs` 不可写时服务以明确错误停止。
55
56
 
56
57
  ## Common Rationalizations
57
58
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Enterprise Logging Standard"
3
- short_description: "Implement full-context structured logs and frontend error reporting"
4
- default_prompt: "Use $wdyy-logging-standard to implement full-context structured logs, result recording, and frontend error reporting."
3
+ short_description: "Write full-context logs only to project-root ./logs"
4
+ default_prompt: "Use $wdyy-logging-standard to implement full-context structured logs that write only to project-root ./logs."
@@ -3,10 +3,11 @@
3
3
  - 日志为一行 JSON,最小字段见 `logger.template.ts`。
4
4
  - `logger.template.ts` 必须可在未安装 `@types/node` 的项目中独立类型检查;读取环境变量时通过 `globalThis` 声明最小运行时形状,不直接依赖 Node.js 全局类型。
5
5
  - `timestamp` 使用服务器本地时间 `YYYY-MM-DD HH:mm:ss`,精确到秒;不得包含毫秒、`T` 或 `Z`。
6
- - 文件直接写入 `LOG_DIR`,不得创建服务、实例或日期子目录。文件名使用服务器本地时间 `yyyy-mm-dd_hh24-mm-ss.log`;同一秒内创建多个文件时,后续文件依次为 `yyyy-mm-dd_hh24-mm-ss_1.log`、`yyyy-mm-dd_hh24-mm-ss_2.log`。最新日志文件大于 2MB 时先创建新文件再写入,既有日志文件不得重命名或覆盖。
6
+ - 应用必须从项目根目录启动,只允许创建项目根目录 `./logs`,并将文件直接写入该目录;不得读取 `LOG_DIR`、`HOST_LOG_DIR` 或其他目录配置,不得创建服务、实例、日期、blue/green 颜色或其他日志子目录。
7
+ - 文件名使用服务器本地时间 `yyyy-mm-dd_hh24-mm-ss.log`。使用排他创建;同一秒内文件名冲突时依次尝试 `yyyy-mm-dd_hh24-mm-ss_1.log`、`yyyy-mm-dd_hh24-mm-ss_2.log`,不得先检查后覆盖写入。最新日志文件大于 2MB 时在同一 `./logs` 中创建新文件,既有日志文件不得移动、重命名或覆盖。
7
8
  - 日志仅记录关键业务事件、请求和异常;`params`、`query`、`body` 和 `response` 均保留完整原始值,不执行脱敏、数组截断或大对象裁剪。授权请求可记录实际用户、模式、表和权限等入参。
8
9
  - HTTP 日志必须保留 `statusCode`。服务端成功返回时 `result` 为 `success`;失败返回时 `result` 为 `failure`,并在 `response` 中保留完整失败内容。100–399 为成功,400–599 为失败。
9
10
  - HTTP 入口和内部服务调用必须透传 traceId。
10
- - 每条日志必须包含 `instanceId`;通过同秒递增后缀避免多个实例或进程覆盖同一日志文件。
11
+ - 每条日志必须包含 `instanceId`;blue、green 与其他进程共享同一 `./logs`,通过排他创建和同秒递增后缀避免覆盖。
11
12
  - Vue 未处理异常和 Promise 拒绝必须上报后端;后端校验请求身份后写入统一日志。
12
- - 日志目录必须挂载到容器外部持久路径,替换容器不得删除历史日志。
13
+ - 容器必须将宿主机项目根目录 `./logs` 挂载到应用根目录 `/app/logs`,替换容器不得删除历史日志。
@@ -4,7 +4,12 @@ import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { afterEach, test } from 'node:test';
6
6
  import { spawnSync } from 'node:child_process';
7
- import { createLogFileName } from '../templates/logger.template.ts';
7
+ import {
8
+ createLogFileName,
9
+ createLogFilePath,
10
+ LOG_DIRECTORY,
11
+ MAX_LOG_FILE_SIZE_BYTES,
12
+ } from '../templates/logger.template.ts';
8
13
 
9
14
  const validator = new URL('./validate-log-entry.mjs', import.meta.url);
10
15
  const temporaryDirectories = [];
@@ -34,6 +39,50 @@ test('同一秒日志文件使用递增后缀', () => {
34
39
  );
35
40
  });
36
41
 
42
+ test('日志路径固定在项目根目录的扁平 logs 目录', () => {
43
+ const date = new Date(2026, 6, 28, 9, 5, 3);
44
+
45
+ assert.equal(LOG_DIRECTORY, './logs');
46
+ assert.deepEqual(
47
+ [createLogFilePath(date), createLogFilePath(date, 1), createLogFilePath(date, 2)],
48
+ [
49
+ './logs/2026-07-28_09-05-03.log',
50
+ './logs/2026-07-28_09-05-03_1.log',
51
+ './logs/2026-07-28_09-05-03_2.log',
52
+ ],
53
+ );
54
+ assert.ok(
55
+ [createLogFilePath(date), createLogFilePath(date, 1), createLogFilePath(date, 2)]
56
+ .every((path) => path.split('/').length === 3),
57
+ );
58
+ });
59
+
60
+ test('旧日志目录环境变量不能改变固定路径', () => {
61
+ const previousLogDir = process.env.LOG_DIR;
62
+ const previousHostLogDir = process.env.HOST_LOG_DIR;
63
+ process.env.LOG_DIR = '/tmp/legacy-logs';
64
+ process.env.HOST_LOG_DIR = '/srv/legacy-logs';
65
+
66
+ try {
67
+ assert.equal(
68
+ createLogFilePath(new Date(2026, 6, 28, 9, 5, 3)),
69
+ './logs/2026-07-28_09-05-03.log',
70
+ );
71
+ } finally {
72
+ if (previousLogDir === undefined) delete process.env.LOG_DIR;
73
+ else process.env.LOG_DIR = previousLogDir;
74
+ if (previousHostLogDir === undefined) delete process.env.HOST_LOG_DIR;
75
+ else process.env.HOST_LOG_DIR = previousHostLogDir;
76
+ }
77
+ });
78
+
79
+ test('2MB 轮转阈值使用同一 logs 目录的下一个候选路径', () => {
80
+ const date = new Date(2026, 6, 28, 9, 5, 3);
81
+
82
+ assert.equal(MAX_LOG_FILE_SIZE_BYTES, 2 * 1024 * 1024);
83
+ assert.equal(createLogFilePath(date, 3), './logs/2026-07-28_09-05-03_3.log');
84
+ });
85
+
37
86
  test('蓝绿实例日志必须包含 instanceId', async () => {
38
87
  const result = await validate({
39
88
  timestamp: '2026-07-09 08:00:00',
@@ -24,6 +24,9 @@ export type LogEntry = LogContext & {
24
24
 
25
25
  const pad = (value: number) => String(value).padStart(2, '0');
26
26
 
27
+ export const LOG_DIRECTORY = './logs' as const;
28
+ export const MAX_LOG_FILE_SIZE_BYTES = 2 * 1024 * 1024;
29
+
27
30
  type RuntimeEnvironment = Record<string, string | undefined>;
28
31
 
29
32
  const getRuntimeEnvironment = (): RuntimeEnvironment => {
@@ -44,6 +47,10 @@ export const createLogFileName = (date = new Date(), collisionIndex = 0): string
44
47
  return `${baseName}${collisionIndex > 0 ? `_${collisionIndex}` : ''}.log`;
45
48
  };
46
49
 
50
+ export const createLogFilePath = (date = new Date(), collisionIndex = 0): string => (
51
+ `${LOG_DIRECTORY}/${createLogFileName(date, collisionIndex)}`
52
+ );
53
+
47
54
  const resultForStatusCode = (statusCode: number): LogEntry['result'] => {
48
55
  if (statusCode >= 100 && statusCode <= 399) return 'success';
49
56
  if (statusCode >= 400 && statusCode <= 599) return 'failure';
package/README.md CHANGED
@@ -18,7 +18,7 @@ OpenSpec 单项变更的需求、设计、任务、实
18
18
  | `wdyy-api-standard` | 根据“获取XX数据”“使用XX接口”等需求定位接口 reference,并按已确认契约写入调用方式。 |
19
19
  | `wdyy-bug-record` | 手动汇总当前任务已解决的问题,并按模板追加至 `docs/bug_record.md`。 |
20
20
  | `wdyy-logging-standard` | 结构化日志、敏感字段移除、traceId、前端异常上报与日志轮转。 |
21
- | `wdyy-deployment-standard` | 人工 SCP 交付、版本化前端资源、Docker 蓝绿发布与回滚。 |
21
+ | `wdyy-deployment-standard` | 扩充标准五段环境配置,生成人工交付、双 Docker 蓝绿发布与回滚文件。 |
22
22
 
23
23
  ## 使用方式
24
24
 
@@ -29,7 +29,7 @@ npm install -g @wdyy/skills
29
29
  wdyy init
30
30
  ```
31
31
 
32
- `wdyy init` 会在系统用户目录 `.agents/skills/` 安装或更新 5 个受管理 Skill,不在目标项目创建 `.agents/skills/`。它还会执行 `git init`,创建或补齐 `.gitignore` 的 `.env`、`node_modules/`、`venv/`、`.pnpm-store/`、`logs/`、`dist/`、`.DS_Store`、`*.tar.gz` 规则,创建 `src/frontend`、`src/backend`、`database`、`deploy`、`docs`、`scripts`、`tests` 和空 `.env.example`,并执行 `openspec init`、创建或追加 `docs/bug_record.md` 模板。若目标项目已有 `AGENTS.md`,原文件保持不变,命令将生成 `AGENTS_new.md`。
32
+ `wdyy init` 会在系统用户目录 `.agents/skills/` 安装或更新 5 个受管理 Skill,不在目标项目创建 `.agents/skills/`。它还会执行 `git init`,创建或补齐 `.gitignore` 的 `.env`、`node_modules/`、`venv/`、`.pnpm-store/`、`logs/`、`dist/`、`.DS_Store`、`*.tar.gz` 规则,创建 `src/frontend`、`src/backend`、`database`、`deploy`、`docs`、`scripts`、`tests`,并以相同的“前端、后端、数据库、API、部署”结构创建缺失的 `.env` `.env.example`。默认前端监听 `0.0.0.0:5173`、后端监听 `0.0.0.0:3000`、数据库端口为 `5432`;已有环境文件保持不变。随后命令执行 `openspec init`、创建或追加 `docs/bug_record.md` 模板。若目标项目已有 `AGENTS.md`,原文件保持不变,命令将生成 `AGENTS_new.md`。
33
33
 
34
34
  ### 仅更新已安装的 Skill
35
35
 
package/lib/wdyy-cli.js CHANGED
@@ -34,7 +34,28 @@ const requiredGitIgnoreEntries = [
34
34
  '*.tar.gz'
35
35
  ];
36
36
 
37
- const defaultProjectLayout = [...defaultProjectDirectories, '.env.example'];
37
+ const defaultEnvironmentContent = `# 前端
38
+ FRONTEND_URL=0.0.0.0
39
+ FRONTEND_PORT=5173
40
+
41
+ # 后端
42
+ BACKEND_URL=0.0.0.0
43
+ BACKEND_PORT=3000
44
+
45
+ # 数据库
46
+ DB_URL=
47
+ DB_PORT=5432
48
+ DB_USER=
49
+ DB_PASSWORD=
50
+ DB_NAME=
51
+ DB_SCHEMA=
52
+
53
+ # API
54
+
55
+ # 部署
56
+ `;
57
+
58
+ const defaultProjectLayout = [...defaultProjectDirectories, '.env', '.env.example'];
38
59
 
39
60
  const generatedAgentsContent = `# 企业开发规则
40
61
 
@@ -49,7 +70,7 @@ const generatedAgentsContent = `# 企业开发规则
49
70
 
50
71
  默认项目目录为 ${defaultProjectLayout.map((path) => `\`${path}\``).join('、')}。
51
72
 
52
- 所有 URL、端口和 IP 地址必须从 \`.env\` 文件读取,禁止在代码中硬编码;不得提交秘密或生产环境 \`.env\`。
73
+ 所有 URL、端口和 IP 地址必须从项目根目录的 \`.env\` 文件读取,禁止在代码中硬编码;不得提交秘密或生产环境 \`.env\`。
53
74
 
54
75
  项目开发完成后必须创建 \`README.md\`,内容包括项目技术栈、目录结构和安装部署说明。
55
76
 
@@ -158,13 +179,18 @@ async function createDefaultProjectLayout(targetRoot, writtenPaths) {
158
179
  if (!existed) writtenPaths.push(destination);
159
180
  }
160
181
 
161
- const envExamplePath = join(targetRoot, '.env.example');
162
- try {
163
- await access(envExamplePath);
164
- } catch (error) {
165
- if (error.code !== 'ENOENT') throw error;
166
- await writeFile(envExamplePath, '', 'utf8');
167
- writtenPaths.push(envExamplePath);
182
+ for (const environmentFile of ['.env', '.env.example']) {
183
+ const environmentPath = join(targetRoot, environmentFile);
184
+ try {
185
+ await access(environmentPath);
186
+ } catch (error) {
187
+ if (error.code !== 'ENOENT') throw error;
188
+ await writeFile(environmentPath, defaultEnvironmentContent, {
189
+ encoding: 'utf8',
190
+ mode: environmentFile === '.env' ? 0o600 : 0o644
191
+ });
192
+ writtenPaths.push(environmentPath);
193
+ }
168
194
  }
169
195
  console.log(`已准备默认项目目录: ${defaultProjectDirectories.join('、')}`);
170
196
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdyy/skills",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "企业内部开发 Skill 与项目初始化命令",
5
5
  "type": "module",
6
6
  "bin": {