@ranger1/dx 0.1.82 → 0.1.83
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/lib/backend-artifact-deploy/artifact-builder.js +17 -3
- package/lib/backend-artifact-deploy/remote-script.js +9 -8
- package/lib/backend-artifact-deploy/remote-transport.js +5 -1
- package/lib/backend-artifact-deploy/runtime-package.js +10 -5
- package/package.json +1 -1
- package/codex/skills/backend-artifact-deploy/SKILL.md +0 -122
- package/codex/skills/backend-artifact-deploy/agents/openai.yaml +0 -4
- package/codex/skills/backend-artifact-deploy/references/deployment-checklist.md +0 -66
|
@@ -8,6 +8,11 @@ import { basenameOrThrow, resolveWithinBase } from './path-utils.js'
|
|
|
8
8
|
import { createRuntimePackage } from './runtime-package.js'
|
|
9
9
|
|
|
10
10
|
const execFileAsync = promisify(execFile)
|
|
11
|
+
const tarEnv = {
|
|
12
|
+
...process.env,
|
|
13
|
+
COPYFILE_DISABLE: '1',
|
|
14
|
+
COPY_EXTENDED_ATTRIBUTES_DISABLE: '1',
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
function assertSafeNamePart(value, label) {
|
|
13
18
|
const text = String(value || '').trim()
|
|
@@ -125,15 +130,21 @@ async function defaultCreateInnerArchive({ stageDir, innerArchivePath }) {
|
|
|
125
130
|
await mkdir(dirname(innerArchivePath), { recursive: true })
|
|
126
131
|
await execFileAsync('tar', ['-czf', innerArchivePath, '.'], {
|
|
127
132
|
cwd: stageDir,
|
|
133
|
+
env: tarEnv,
|
|
128
134
|
})
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
async function defaultWriteChecksum({ archivePath, checksumPath }) {
|
|
138
|
+
const archiveName = basename(archivePath)
|
|
132
139
|
try {
|
|
133
|
-
const { stdout } = await execFileAsync('sha256sum', [
|
|
140
|
+
const { stdout } = await execFileAsync('sha256sum', [archiveName], {
|
|
141
|
+
cwd: dirname(archivePath),
|
|
142
|
+
})
|
|
134
143
|
await writeFile(checksumPath, stdout)
|
|
135
144
|
} catch {
|
|
136
|
-
const { stdout } = await execFileAsync('shasum', ['-a', '256',
|
|
145
|
+
const { stdout } = await execFileAsync('shasum', ['-a', '256', archiveName], {
|
|
146
|
+
cwd: dirname(archivePath),
|
|
147
|
+
})
|
|
137
148
|
await writeFile(checksumPath, stdout)
|
|
138
149
|
}
|
|
139
150
|
}
|
|
@@ -142,7 +153,10 @@ async function defaultCreateBundle({ outputDir, bundlePath, innerArchivePath, ch
|
|
|
142
153
|
await execFileAsync(
|
|
143
154
|
'tar',
|
|
144
155
|
['-czf', bundlePath, basename(innerArchivePath), basename(checksumPath)],
|
|
145
|
-
{
|
|
156
|
+
{
|
|
157
|
+
cwd: outputDir,
|
|
158
|
+
env: tarEnv,
|
|
159
|
+
},
|
|
146
160
|
)
|
|
147
161
|
}
|
|
148
162
|
|
|
@@ -157,15 +157,16 @@ find_single_bundle_file() {
|
|
|
157
157
|
|
|
158
158
|
sha256_check() {
|
|
159
159
|
local checksum_file="$1"
|
|
160
|
-
|
|
161
|
-
sha256sum -c "$checksum_file"
|
|
162
|
-
return
|
|
163
|
-
fi
|
|
164
|
-
local checksum expected file
|
|
160
|
+
local checksum file actual
|
|
165
161
|
checksum="$(awk '{print $1}' "$checksum_file")"
|
|
166
162
|
file="$(awk '{print $2}' "$checksum_file")"
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
file="$(basename "$file")"
|
|
164
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
165
|
+
actual="$(sha256sum "$file" | awk '{print $1}')"
|
|
166
|
+
else
|
|
167
|
+
actual="$(shasum -a 256 "$file" | awk '{print $1}')"
|
|
168
|
+
fi
|
|
169
|
+
[[ "$checksum" == "$actual" ]]
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
run_with_env() {
|
|
@@ -213,7 +214,7 @@ CURRENT_PHASE="extract"
|
|
|
213
214
|
echo "DX_REMOTE_PHASE=extract"
|
|
214
215
|
validate_archive_entries "$ARCHIVE"
|
|
215
216
|
BUNDLE_TEMP_DIR="$(mktemp -d "$APP_ROOT/.bundle-extract.XXXXXX")"
|
|
216
|
-
tar -xzf "$ARCHIVE" -C "$BUNDLE_TEMP_DIR"
|
|
217
|
+
tar -xzf "$ARCHIVE" -C "$BUNDLE_TEMP_DIR"
|
|
217
218
|
|
|
218
219
|
INNER_ARCHIVE="$(find_single_bundle_file "$BUNDLE_TEMP_DIR" 'backend-v*.tgz')"
|
|
219
220
|
INNER_ARCHIVE_SHA256_FILE="$(find_single_bundle_file "$BUNDLE_TEMP_DIR" 'backend-v*.tgz.sha256')"
|
|
@@ -114,5 +114,9 @@ export async function deployBackendArtifactRemotely(config, bundle, deps = {}) {
|
|
|
114
114
|
const phaseModel = createRemotePhaseModel(payload)
|
|
115
115
|
const script = buildRemoteDeployScript(phaseModel)
|
|
116
116
|
const commandResult = await runRemoteScript(config.remote, script)
|
|
117
|
-
|
|
117
|
+
const result = parseRemoteResult(commandResult)
|
|
118
|
+
if (!result.ok) {
|
|
119
|
+
throw new Error(`远端部署失败(${result.phase}): ${result.message}`)
|
|
120
|
+
}
|
|
121
|
+
return result
|
|
118
122
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const UNSUPPORTED_LOCAL_DEP_PATTERN = /^(workspace:|file:|link:)/
|
|
2
|
-
const
|
|
2
|
+
const REQUIRED_DEPENDENCIES = ['prisma', 'tslib', 'dotenv-cli', '@prisma/adapter-pg']
|
|
3
3
|
|
|
4
4
|
function assertSupportedDependencies(dependencies = {}) {
|
|
5
5
|
for (const [name, version] of Object.entries(dependencies)) {
|
|
@@ -13,10 +13,15 @@ function assertSupportedDependencies(dependencies = {}) {
|
|
|
13
13
|
export function createRuntimePackage({ appPackage, rootPackage }) {
|
|
14
14
|
const runtimeDependencies = { ...(appPackage?.dependencies || {}) }
|
|
15
15
|
const appDevDependencies = appPackage?.devDependencies || {}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const rootDependencies = rootPackage?.dependencies || {}
|
|
17
|
+
const rootDevDependencies = rootPackage?.devDependencies || {}
|
|
18
|
+
|
|
19
|
+
for (const dependencyName of REQUIRED_DEPENDENCIES) {
|
|
20
|
+
if (!runtimeDependencies[dependencyName]) {
|
|
21
|
+
runtimeDependencies[dependencyName] =
|
|
22
|
+
appDevDependencies[dependencyName]
|
|
23
|
+
|| rootDependencies[dependencyName]
|
|
24
|
+
|| rootDevDependencies[dependencyName]
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
package/package.json
CHANGED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: backend-artifact-deploy
|
|
3
|
-
description: 将后端部署从“目标机拉源码并编译”改造为“本地构建制品、目标机仅安装运行依赖并启动”的标准流程。用于 Node/NestJS/Nx/Prisma 等后端项目,尤其适合需要无源码部署、支持 dev/staging/prod 多环境、要求双层环境文件覆盖(如 .env.production 与 .env.production.local)、以及需要在 pm2 与 direct 启动方式之间切换的场景。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 后端制品部署
|
|
7
|
-
|
|
8
|
-
## 概览
|
|
9
|
-
|
|
10
|
-
使用该技能时,先识别项目当前的环境变量加载链路与启动链路,再落地“制品打包脚本 + 服务器发布脚本 + 回滚策略”。
|
|
11
|
-
目标是保证目标机器不需要源码编译,同时保持与项目既有环境覆盖规则一致。
|
|
12
|
-
|
|
13
|
-
## 执行流程
|
|
14
|
-
|
|
15
|
-
### 第一步:确认现状链路
|
|
16
|
-
|
|
17
|
-
依次核对:
|
|
18
|
-
|
|
19
|
-
1. 构建链路是否依赖源码目录(例如 dist 里软链回源码 `node_modules`)。
|
|
20
|
-
2. 运行链路如何加载环境变量(是否是两层覆盖,是否通过 `dotenv -e A -e B`)。
|
|
21
|
-
3. 数据库迁移链路是否依赖运行时环境(Prisma `generate` / `migrate deploy` 前是否已加载 env)。
|
|
22
|
-
4. 进程启动是否仅支持 pm2,是否需要 direct 前台测试模式。
|
|
23
|
-
|
|
24
|
-
如果项目已有统一入口(例如 `dx` 或内部脚手架),优先复用该入口,不要绕开既有环境策略。
|
|
25
|
-
|
|
26
|
-
### 第二步:定义制品边界
|
|
27
|
-
|
|
28
|
-
默认采用“轻制品”模式:
|
|
29
|
-
|
|
30
|
-
1. 本地只打包编译产物与必要运行文件,不打包 `node_modules`。
|
|
31
|
-
2. 目标机解压后再安装生产依赖。
|
|
32
|
-
3. 制品命名固定含版本与时间片,例如 `backend-v<version>-<月-日-时-分>.tgz`。
|
|
33
|
-
|
|
34
|
-
制品最小清单应包含:
|
|
35
|
-
|
|
36
|
-
1. 编译产物目录(如 `dist/backend/**`)。
|
|
37
|
-
2. 数据库 schema 与迁移目录(如 `prisma/schema/**`)。
|
|
38
|
-
3. 生产依赖清单(`package.production.json` 重命名为 `package.json`)。
|
|
39
|
-
4. 锁文件(`pnpm-lock.yaml`)。
|
|
40
|
-
5. 启动配置(如 `ecosystem.config.cjs`)。
|
|
41
|
-
6. 双层环境文件(`.env.<env>` 与 `.env.<env>.local`)。
|
|
42
|
-
|
|
43
|
-
### 第三步:实现打包脚本
|
|
44
|
-
|
|
45
|
-
打包脚本应支持参数:
|
|
46
|
-
|
|
47
|
-
1. `--env dev|staging|prod`。
|
|
48
|
-
2. `--version`(默认取后端 `package.json` 版本)。
|
|
49
|
-
3. `--time`(格式 `MM-DD-HH-mm`)。
|
|
50
|
-
|
|
51
|
-
脚本关键行为:
|
|
52
|
-
|
|
53
|
-
1. 按环境构建(`dev -> --dev`,`staging/prod -> --prod`)。
|
|
54
|
-
2. 复制双层环境文件到制品目录。
|
|
55
|
-
3. 不在本地安装运行依赖。
|
|
56
|
-
4. 生成 `tgz`。
|
|
57
|
-
|
|
58
|
-
### 第四步:实现发布脚本
|
|
59
|
-
|
|
60
|
-
发布脚本应支持参数:
|
|
61
|
-
|
|
62
|
-
1. `--archive`(必填)。
|
|
63
|
-
2. `--env dev|staging|prod`。
|
|
64
|
-
3. `--start-mode pm2|direct`(默认 `pm2`)。
|
|
65
|
-
4. `--env-file` 与 `--env-local-file`(可选覆盖路径)。
|
|
66
|
-
5. `--skip-install`、`--skip-migration`、`--skip-pm2`。
|
|
67
|
-
|
|
68
|
-
发布顺序建议:
|
|
69
|
-
|
|
70
|
-
1. 解压到 `releases/<version>`。
|
|
71
|
-
2. 准备双层 env 文件。
|
|
72
|
-
3. 安装生产依赖。
|
|
73
|
-
4. 执行 `prisma generate`。
|
|
74
|
-
5. 执行 `prisma migrate deploy`。
|
|
75
|
-
6. 切换 `current` 软链。
|
|
76
|
-
7. 启动服务(pm2 或 direct)。
|
|
77
|
-
8. 清理旧版本。
|
|
78
|
-
|
|
79
|
-
### 第五步:双层环境加载规则(必须一致)
|
|
80
|
-
|
|
81
|
-
所有关键步骤统一使用相同加载顺序:
|
|
82
|
-
|
|
83
|
-
1. 基础层 `.env.<env>`。
|
|
84
|
-
2. 覆盖层 `.env.<env>.local`。
|
|
85
|
-
|
|
86
|
-
推荐显式写法:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
APP_ENV="<env-name>" pnpm exec dotenv -e ".env.<env-name>" -e ".env.<env-name>.local" -- <command>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
命令示例中的 `<command>` 包括:
|
|
93
|
-
|
|
94
|
-
1. `pnpm exec prisma generate --schema=...`
|
|
95
|
-
2. `pnpm exec prisma migrate deploy --schema=...`
|
|
96
|
-
3. `pm2 startOrReload ...` 或 `node apps/backend/src/main.js`
|
|
97
|
-
|
|
98
|
-
## 验证清单
|
|
99
|
-
|
|
100
|
-
交付前必须至少验证:
|
|
101
|
-
|
|
102
|
-
1. 打包脚本 `--help` 与语法检查通过。
|
|
103
|
-
2. 发布脚本 `--help` 与语法检查通过。
|
|
104
|
-
3. 制品内同时包含 `.env.<env>` 与 `.env.<env>.local`。
|
|
105
|
-
4. 发布脚本在默认路径下能正确识别并使用两层 env。
|
|
106
|
-
5. `start-mode=direct` 可前台启动。
|
|
107
|
-
6. `start-mode=pm2` 可重载或启动。
|
|
108
|
-
7. 版本目录与 `current` 切换正常,可回滚。
|
|
109
|
-
|
|
110
|
-
## 常见陷阱
|
|
111
|
-
|
|
112
|
-
1. 把 env 文件链接到自身,造成坏链路。
|
|
113
|
-
2. 只加载 `.env.<env>`,遗漏 `.local` 覆盖。
|
|
114
|
-
3. 迁移与启动阶段用不同 env 加载逻辑,导致行为不一致。
|
|
115
|
-
4. staging 构建误用 development 或 production 的 env 层。
|
|
116
|
-
5. 打包包含本机 `node_modules`,跨系统运行失败。
|
|
117
|
-
|
|
118
|
-
## 参考资料
|
|
119
|
-
|
|
120
|
-
需要细化实现时,读取:
|
|
121
|
-
|
|
122
|
-
- `references/deployment-checklist.md`
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# 后端制品部署检查清单
|
|
2
|
-
|
|
3
|
-
## 一、改造前采样
|
|
4
|
-
|
|
5
|
-
1. 查找构建后是否存在软链回源码依赖:
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
rg -n "ln -sfn.*node_modules|node_modules.*ln -sfn" <backend-package-json-path>
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
2. 查找环境加载入口:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
rg -n "dotenv -e|dotenv --override|ConfigModule.forRoot|loadEnvironment|env-layers" -S <repo-root>
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
3. 查找启动命令:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
rg -n "start:prod|pm2|node .*main" -S <repo-root>
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## 二、打包脚本最低要求
|
|
24
|
-
|
|
25
|
-
1. 接收 `--env`、`--version`、`--time`。
|
|
26
|
-
2. 制品名包含版本与时间片。
|
|
27
|
-
3. 打入 `.env.<env>` 与 `.env.<env>.local`。
|
|
28
|
-
4. 不打入 `node_modules`(轻制品模式)。
|
|
29
|
-
|
|
30
|
-
## 三、发布脚本最低要求
|
|
31
|
-
|
|
32
|
-
1. 解压到 `releases/<version>` 并切换 `current`。
|
|
33
|
-
2. 支持 `pm2` 与 `direct` 两种启动方式。
|
|
34
|
-
3. 在 install、migrate、start 三阶段都用同一套双层 env 加载顺序。
|
|
35
|
-
4. 支持 `--env-file` 与 `--env-local-file` 覆盖路径。
|
|
36
|
-
|
|
37
|
-
## 四、上线前验证命令
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
bash -n scripts/release/backend-build-release.sh
|
|
41
|
-
bash -n scripts/release/backend-deploy-release.sh
|
|
42
|
-
scripts/release/backend-build-release.sh --env staging
|
|
43
|
-
tar -tzf release/backend/*.tgz | rg "\.env\.staging(\.local)?$"
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## 五、发布后验证
|
|
47
|
-
|
|
48
|
-
1. 进程检查:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
pm2 status
|
|
52
|
-
pm2 logs backend --lines 120
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
2. 健康检查:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
curl -f http://127.0.0.1:3000/health
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
3. 回滚检查:
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
ln -sfn /opt/ai-backend/releases/<old-version> /opt/ai-backend/current
|
|
65
|
-
pm2 reload backend --update-env
|
|
66
|
-
```
|