Merge branch 'feature/portal-fast-release'
Memind CI / Test, build, and release guards (push) Successful in 3m0s
Memind CI / Test, build, and release guards (push) Successful in 3m0s
Add standard and fast 103 portal release modes.
This commit is contained in:
@@ -29,20 +29,39 @@ node --test db.test.mjs capabilities.test.mjs llm-providers.test.mjs wechat-mp.t
|
||||
|
||||
全绿之后才能进入打包发布,不要跳过。
|
||||
|
||||
## 2. 打包 + 发布(唯一入口)
|
||||
## 2. 打包 + 发布
|
||||
|
||||
103 整包替换有两种模式(详见 `PRODUCTION_RELEASE_RULES.md`):
|
||||
|
||||
| 模式 | 命令 | 适用 |
|
||||
|------|------|------|
|
||||
| **标准发布** | `bash scripts/release-portal-runtime-prod.sh --yes` | 默认;须先灰度验收 + 晋升证据 + 完整 Gate |
|
||||
| **快速发布** | `bash scripts/release-portal-fast-prod.sh --yes` | 变更已在本地/CI/Gate 充分验证,跳过灰度晋升与完整 Gate 重跑 |
|
||||
|
||||
先 dry-run 确认构建产物正常(不会碰生产):
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-runtime-prod.sh --skip-tests --dry-run
|
||||
bash scripts/release-portal-runtime-prod.sh --dry-run
|
||||
# 或快速发布预演
|
||||
bash scripts/release-portal-fast-prod.sh --dry-run
|
||||
```
|
||||
|
||||
确认无误后正式发布(`--skip-tests` 是因为第 1 步已经手动跑过;这里不再重复跑脚本内置的窄范围测试):
|
||||
**标准发布**须先完成 `bash scripts/release-portal-canary-prod.sh --yes` 与灰度验收。
|
||||
|
||||
**快速发布**在第 1 步测试全绿后可直接执行(仍会做 103 备份、goosed 预检、8081 健康检查):
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-runtime-prod.sh --skip-tests --yes
|
||||
bash scripts/release-portal-fast-prod.sh --yes
|
||||
```
|
||||
|
||||
标准发布确认无误后:
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-runtime-prod.sh --yes
|
||||
```
|
||||
|
||||
标准发布禁止 `--skip-tests`。快速发布仅在 `--mode fast` 下允许 `--skip-tests`,且须已自行完成第 1 步验证。
|
||||
|
||||
这个脚本会自动完成:
|
||||
|
||||
1. 本机构建 `.runtime/portal`
|
||||
|
||||
@@ -66,7 +66,9 @@ npm run verify:seo-geo
|
||||
npm run verify:seo-discovery
|
||||
```
|
||||
|
||||
发版脚本(`scripts/release-portal-runtime-prod.sh`)当前在不跳过测试时会执行相关 verify;生产 `103` 发布禁止使用 `--skip-tests`,并且仍须通过完整生产发布守门员。
|
||||
发版脚本(`scripts/release-portal-runtime-prod.sh` / `scripts/release-portal-fast-prod.sh`):
|
||||
- **标准发布**(默认):须灰度晋升证据 + 完整 Gate + `verify:mindspace-publish-guards:full`;禁止 `--skip-tests`。
|
||||
- **快速发布**(`--mode fast`):跳过灰度晋升与完整 Gate 重跑;可复用有效 Gate report 或跑最小 smoke;仍禁止在生产用 `--skip-tests` 代替发布前验证(快速模式仅在 `--mode fast` 下允许 `--skip-tests`,且须已自行验证)。
|
||||
|
||||
### 受保护的关键路径
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ git rebase origin/main
|
||||
1. 本机不允许直接 `rsync` 到 `103` 或 `105`。
|
||||
2. **禁止** SSH 登录 `105` 后直接修改业务源码(含 `scripts/wechat-mp-menu.mjs` 等);必须先本地 commit,再按发布流程上线。详见 [105 服务器变更规范](docs/105-server-operations.md)。
|
||||
3. Portal 生产与测试统一走“本机构建 runtime artifact -> 打包发布”,**禁止**在 `103` 解源码包后 `npm install` / `npm run build`。
|
||||
4. Portal 首次生产入口是 `bash scripts/release-portal-canary-prod.sh`;它只安装并启用用户级灰度候选。`bash scripts/release-portal-runtime-prod.sh` 是整包晋升脚本,在同一候选完成灰度验收且晋升证据校验落地前继续禁止非 dry-run。
|
||||
4. Portal 首次生产入口是 `bash scripts/release-portal-canary-prod.sh`(用户级灰度)。整包替换有两种模式:`bash scripts/release-portal-runtime-prod.sh`(**标准发布**,须灰度晋升证据 + 完整 Gate)或 `bash scripts/release-portal-fast-prod.sh`(**快速发布**,已验证变更可跳过灰度晋升与完整 Gate 重跑,仍须备份与健康检查)。详见 [PRODUCTION_RELEASE_RULES.md](PRODUCTION_RELEASE_RULES.md)。
|
||||
5. 发布只能从已验证的完整 `main` 打整包;分支代码必须先合并进 `main`,禁止从功能分支、单个 commit、单个修复或局部差异单包发布。
|
||||
6. 发布前必须确认 CI 已通过,且没有未合并的关键变更。
|
||||
7. 发布来源必须是可追溯 commit,不允许从不明工作区直接出包。
|
||||
@@ -67,7 +67,7 @@ bash scripts/check-release-ready.sh
|
||||
```
|
||||
|
||||
10. 分支落后 `origin/main`、工作区有未提交或未跟踪改动、处于 detached HEAD、或没有明确批准却从 `main` / `master` 发布,均禁止发版。
|
||||
11. 生产 `103` 发布必须完整通过 [生产发布守门员](docs/production-release-guardian.md);Gate report 必须绑定同一完整 `main` commit 和同一 runtime artifact。生产发布固定执行 Core + 自动影响域;关键共享路径展开到预定义影响域,未映射变更直接阻断并要求先补映射,不再自动执行完整 187 项。所有被选场景成功后仍须取得明确人工批准。
|
||||
11. 生产 `103` 发布须完整通过 [生产发布守门员](docs/production-release-guardian.md) 或使用已文档化的**快速发布**路径(`scripts/release-portal-fast-prod.sh`)。标准发布 Gate report 必须绑定同一完整 `main` commit 和同一 runtime artifact,并执行 Core + 自动影响域;快速发布可复用已有 Gate report 或改跑最小本地验证,但仍须取得明确人工批准。
|
||||
|
||||
## 5. 文档约束
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
2. **禁止 SSH 登录 `105` 直接修改业务代码**(含服务号菜单脚本 `scripts/wechat-mp-menu.mjs`)。105 上文件是部署产物;变更必须:本地 `test-memind` 修改 → Git commit → 正式发布 → 必要时在目标环境执行 API 同步。详见 [docs/105-server-operations.md](docs/105-server-operations.md)。
|
||||
2. **Portal 生产必须是无源码 runtime 模式**:构建只发生在本机 Mac,产物是 `.runtime/portal/`;`103` 只接收 runtime artifact、继承持久目录、启动服务,**禁止**在 `103` 上 `npm install`、`npm run build` 或保留可运行源码树。
|
||||
2. `MindSpace` 独立服务同样必须走单独 runtime artifact:本地 `node scripts/build-mindspace-service-runtime.mjs` -> `bash scripts/release-mindspace-service-prod.sh` -> 上传 `103` -> 备份 `/Users/john/MindSpace` 与共享 `Memind/.env` -> 原子切换到 `/Users/john/MindSpace` -> 健康检查 `127.0.0.1:8082/health` 与 `/mindspace/v1/contract`;禁止手工 SSH 改线上 `/Users/john/MindSpace` 源码。103 当前拓扑见 [docs/103-runtime-topology.md](docs/103-runtime-topology.md)。
|
||||
3. Portal 生产发布必须先构建候选 runtime,并只通过 `scripts/release-portal-canary-prod.sh` 启用用户级灰度。灰度只能命中明确的不可变用户身份,未命中、身份解析失败或候选不健康必须继续走稳定版本。`scripts/release-portal-runtime-prod.sh` 仍是整包替换脚本,在同一候选的灰度验收和晋升证据校验完成前禁止非 dry-run。
|
||||
3. Portal 生产发布提供两种整包替换模式(均须从完整 `main` 打整包,且先通过 `check-release-ready.sh`):
|
||||
- **标准发布**(默认):`bash scripts/release-portal-runtime-prod.sh` 或 `--mode standard`。须先通过 `scripts/release-portal-canary-prod.sh` 完成用户级灰度验收,再校验 103 灰度晋升证据、完整 Core+Impact Gate、`verify:mindspace-publish-guards:full`,最后替换 8081 live 目录。
|
||||
- **快速发布**:`bash scripts/release-portal-fast-prod.sh` 或 `bash scripts/release-portal-runtime-prod.sh --mode fast`。跳过灰度晋升证据、完整 Gate 重跑和 `publish-guards:full`;若当前 commit+artifact 已有有效 Gate report 则复用,否则只跑最小本地 smoke + `test:release-gate:unit`。仍执行 103 备份、goosed 预检、8081 健康检查与明确人工批准。适用于变更已在本地/CI/Gate 充分验证、需尽快整包上线的场景;**禁止**用 `--skip-tests` 替代发布前验证。
|
||||
- 灰度入口仍是 `scripts/release-portal-canary-prod.sh`;快速发布不替代灰度,只是允许在已验证前提下跳过「灰度→晋升」链条直接整包。
|
||||
4. `scripts/release-prod.sh`(源码包发布)已停用,不得再用于 Portal;`rsync_to_server.sh` 与任何面向 `105` 的直接同步脚本也只保留为禁用提示。
|
||||
5. Portal 发布包不得携带运行态资产;`.env`、`data/`、`users/`、`.tailscale/`、`public/plaza-covers/`、`logs/` 只能从线上现有 live 目录继承。`/Users/john/MindSpace` 是独立 MindSpace Service 的生产根目录,不属于 Portal runtime 包;`/Users/john/Project/Memind/MindSpace` 只允许作为旧链路兼容/存量目录处理,不得再被写成 MindSpace Service 的当前根目录。
|
||||
6. runtime artifact 必须包含 `server.mjs` 与 `mindspace-sandbox-mcp.mjs`(`sandbox-fs` 扩展依赖的独立子进程入口)。
|
||||
|
||||
@@ -616,10 +616,13 @@ failed = skipped = blocked = unknown = cleanup_failed = 0
|
||||
`release-gate/release-script.test.mjs`。测试通过不代表已经部署;103 灰度仍须绑定同一
|
||||
commit、artifact 和 Gate report,并完成备份、只读预检及人工批准。
|
||||
|
||||
`scripts/release-portal-runtime-prod.sh` 会替换 8081 live 目录。它只用于同一候选完成灰度
|
||||
验收后的全量晋升;在晋升证据校验完成前继续硬阻断非 dry-run,不能拿灰度入口替代晋升批准。
|
||||
`scripts/release-portal-runtime-prod.sh` 会替换 8081 live 目录。
|
||||
|
||||
`scripts/release-portal-runtime-prod.sh` 必须按以下顺序执行:
|
||||
**标准发布**(默认 `--mode standard`):只用于同一候选完成灰度验收后的全量晋升;在晋升证据校验完成前继续硬阻断非 dry-run。
|
||||
|
||||
**快速发布**(`scripts/release-portal-fast-prod.sh` 或 `--mode fast`):跳过灰度晋升证据与完整 Gate 重跑;若已有有效 Gate report 则复用,否则只跑最小本地验证。仍须 `check-release-ready`、103 备份、goosed 预检、8081 健康检查与明确人工批准。manifest 记录 `release_mode=fast`。
|
||||
|
||||
`scripts/release-portal-runtime-prod.sh` 标准模式必须按以下顺序执行:
|
||||
|
||||
1. `check-release-ready.sh`
|
||||
2. 获取完整 `main` SHA 和远端 CI 状态
|
||||
@@ -632,9 +635,9 @@ commit、artifact 和 Gate report,并完成备份、只读预检及人工批
|
||||
|
||||
以下情况必须直接退出:
|
||||
|
||||
- 使用 `--skip-tests`
|
||||
- **标准发布**使用 `--skip-tests`
|
||||
- 使用范围绕过变量
|
||||
- report 不存在、过期或解析失败
|
||||
- **标准发布**下 report 不存在、过期或解析失败
|
||||
- commit 或 artifact SHA 不一致
|
||||
- 任一被选场景失败、跳过、未知、未执行或清理失败
|
||||
- 影响选择结果不可重现或存在未映射路径
|
||||
|
||||
+25
-5
@@ -43,17 +43,37 @@ npm run check:mindspace-public-links
|
||||
|
||||
6. `public/*.html` 里的**下载/附件**相对链接(如 `report.docx`)必须在同目录真实存在。发版脚本默认只查这类链接(`--downloads-only`);全量资源检查用 `npm run check:mindspace-public-links:all`。失败会阻断发版;确知要带着已知坏链上线时,才可临时 `ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES=1`。
|
||||
|
||||
## 2. Portal runtime 发布唯一流程
|
||||
## 2. Portal runtime 发布流程
|
||||
|
||||
生产发布守门员、Core + Impact Gate report 和本次明确人工批准全部满足后,首次生产动作只使用:
|
||||
103 整包替换提供两种模式:
|
||||
|
||||
### 2.1 标准发布(默认)
|
||||
|
||||
生产发布守门员、Core + Impact Gate report、103 灰度晋升证据和本次明确人工批准全部满足后,首次生产动作使用灰度:
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-canary-prod.sh --yes
|
||||
```
|
||||
|
||||
生产 `103` 禁止使用 `--skip-tests` 或任何范围绕过变量。整包替换脚本
|
||||
`release-portal-runtime-prod.sh` 只用于同一候选完成灰度验收后的全量晋升,在晋升证据
|
||||
校验完成前仍会硬阻断。本文历史记录中的旧绕过命令只用于事故复盘,不构成当前发布授权。
|
||||
灰度验收通过后,整包晋升:
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-runtime-prod.sh --yes
|
||||
```
|
||||
|
||||
标准发布禁止 `--skip-tests` 或任何范围绕过变量。
|
||||
|
||||
### 2.2 快速发布
|
||||
|
||||
变更已在本地/CI/Gate 充分验证、需尽快整包上线时使用:
|
||||
|
||||
```bash
|
||||
bash scripts/release-portal-fast-prod.sh --yes
|
||||
```
|
||||
|
||||
快速发布跳过灰度晋升证据、完整 Gate 重跑和 `publish-guards:full`;若当前 commit+artifact 已有有效 Gate report 则复用,否则只跑最小本地 smoke。仍执行 `check-release-ready`、103 备份、goosed 预检、8081 健康检查与明确人工批准。manifest 记录 `release_mode=fast`。
|
||||
|
||||
快速发布不替代发布前测试(§1.5);禁止用 `--skip-tests` 代替未做的验证。
|
||||
|
||||
这个脚本必须完成这些动作:
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ test('production release verifies gate report before 103 preflight and upload',
|
||||
assert.ok(gateIndex > 0, 'missing gate verifier');
|
||||
assert.ok(preflightIndex > gateIndex, '103 preflight must run after gate verification');
|
||||
assert.ok(uploadIndex > preflightIndex, 'upload must run after preflight');
|
||||
assert.match(source, /生产发布守门员禁止 --skip-tests/);
|
||||
assert.match(source, /标准发布禁止 --skip-tests/);
|
||||
assert.match(source, /禁止 ALLOW_PORTAL_RELEASE_SCOPE_BYPASS/);
|
||||
});
|
||||
|
||||
@@ -47,11 +47,11 @@ test('production stable release verifies canary promotion evidence before gate c
|
||||
path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'),
|
||||
'utf8',
|
||||
);
|
||||
const promotionIndex = source.indexOf('verify-canary-promotion-evidence.mjs');
|
||||
const gateIndex = source.indexOf('verify-release-gate-report.mjs');
|
||||
const promotionIndex = source.indexOf('say "验证 103 灰度晋升证据"');
|
||||
const gateCallIndex = source.indexOf('say "验证与当前 main 和 runtime artifact 绑定的 Gate report"');
|
||||
const impactIndex = source.indexOf('run-release-gate-impact.mjs');
|
||||
assert.ok(promotionIndex > 0, 'missing canary promotion evidence verifier');
|
||||
assert.ok(gateIndex > promotionIndex, 'gate verification must follow promotion evidence');
|
||||
assert.ok(promotionIndex > 0, 'missing canary promotion evidence step');
|
||||
assert.ok(gateCallIndex > promotionIndex, 'gate verification must follow promotion evidence in standard flow');
|
||||
assert.ok(impactIndex > 0, 'missing risk-based impact gate fallback');
|
||||
assert.doesNotMatch(source, /在同一候选完成 103 灰度验收且晋升证据校验落地前,禁止非 dry-run/);
|
||||
assert.match(source, /read_agent_run_status_json/);
|
||||
@@ -125,10 +125,46 @@ test('production release rejects --skip-tests before repository or network prefl
|
||||
{ cwd: ROOT, encoding: 'utf8' },
|
||||
);
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /禁止 --skip-tests/);
|
||||
assert.match(result.stderr, /标准发布禁止 --skip-tests/);
|
||||
assert.doesNotMatch(`${result.stdout}\n${result.stderr}`, /103 只读预检/);
|
||||
});
|
||||
|
||||
test('fast production release allows --skip-tests but still requires check-release-ready', () => {
|
||||
const result = spawnSync(
|
||||
'bash',
|
||||
[
|
||||
path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'),
|
||||
'--mode',
|
||||
'fast',
|
||||
'--skip-tests',
|
||||
'--skip-build',
|
||||
'--dry-run',
|
||||
],
|
||||
{ cwd: ROOT, encoding: 'utf8' },
|
||||
);
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.doesNotMatch(result.stderr, /标准发布禁止 --skip-tests/);
|
||||
assert.match(`${result.stdout}\n${result.stderr}`, /快速发布:跳过额外本地测试/);
|
||||
});
|
||||
|
||||
test('fast release wrapper delegates to runtime prod script with --mode fast', async () => {
|
||||
const source = await fs.readFile(
|
||||
path.join(ROOT, 'scripts', 'release-portal-fast-prod.sh'),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(source, /release-portal-runtime-prod\.sh.*--mode fast/);
|
||||
});
|
||||
|
||||
test('fast production release skips canary promotion evidence in script', async () => {
|
||||
const source = await fs.readFile(
|
||||
path.join(ROOT, 'scripts', 'release-portal-runtime-prod.sh'),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(source, /快速发布:跳过 103 灰度晋升证据/);
|
||||
assert.match(source, /verify-canary-promotion-evidence\.mjs/);
|
||||
assert.match(source, /release_mode=\$\{RELEASE_MODE\}/);
|
||||
});
|
||||
|
||||
test('production canary verifies the exact Gate artifact before any 103 preflight or upload', async () => {
|
||||
const source = await fs.readFile(CANARY_RELEASE, 'utf8');
|
||||
const gateIndex = source.indexOf('verify-release-gate-report.mjs');
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
exec bash "${ROOT}/scripts/release-portal-runtime-prod.sh" --mode fast "$@"
|
||||
@@ -26,40 +26,97 @@ DRY_RUN=0
|
||||
SKIP_TESTS=0
|
||||
SKIP_BUILD=0
|
||||
AUTO_YES=0
|
||||
RELEASE_MODE="${MEMIND_RELEASE_MODE:-standard}"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "${TMP_DIR}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
say() {
|
||||
printf '\n[%s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
bash scripts/release-portal-runtime-prod.sh [--dry-run] [--skip-tests] [--skip-build] [--yes]
|
||||
bash scripts/release-portal-runtime-prod.sh [--mode standard|fast] [--dry-run] [--skip-tests] [--skip-build] [--yes]
|
||||
bash scripts/release-portal-fast-prod.sh [--dry-run] [--skip-build] [--yes]
|
||||
|
||||
说明:
|
||||
1. 本地构建 Portal 无源码 runtime artifact
|
||||
2. 上传到 103(固定公网地址)
|
||||
3. 103 备份当前 Memind 全目录 + 持久目录
|
||||
4. 停止旧 Portal 服务
|
||||
5. 用 runtime artifact 替换 live 目录,只继承持久目录
|
||||
6. 启动 Portal,保持旧端口 8081
|
||||
7. 健康检查通过后重启 m.tkmind.cn 反向隧道 (105:19081 -> 103:8081)
|
||||
8. 旧源码目录移入 archive,不再保留可运行 live 源码
|
||||
发布模式:
|
||||
standard(默认)
|
||||
完整标准发布:灰度晋升证据 + Core+Impact Gate + 完整 MindSpace 守卫 + 103 整包替换。
|
||||
标准流程应先跑 scripts/release-portal-canary-prod.sh,验收后再执行本脚本。
|
||||
|
||||
fast
|
||||
快速发布:跳过灰度晋升证据、完整 Gate 重跑和 publish-guards:full。
|
||||
仍执行 check-release-ready、runtime 构建/校验、103 备份与回滚、goosed 预检、8081 健康检查。
|
||||
若当前 commit+artifact 已有有效 Gate report 则直接复用;否则只跑最小本地 smoke + release-gate 单测。
|
||||
快速发布需要明确人工批准(--yes 或发布前交互确认);全站用户会立即命中新版本。
|
||||
|
||||
环境变量:
|
||||
MEMIND_RELEASE_MODE=standard|fast
|
||||
ALLOW_DIRECT_STABLE_RELEASE=1
|
||||
仅在用户明确批准按 7/23 前规则直接整包时使用:允许 --skip-tests,
|
||||
并跳过 canary 晋升证据与 Core+Impact Gate report。
|
||||
已废弃;等价于 --mode fast。仅保留兼容旧命令。
|
||||
EOF
|
||||
}
|
||||
|
||||
is_fast_release() {
|
||||
[[ "${RELEASE_MODE}" == "fast" ]]
|
||||
}
|
||||
|
||||
run_fast_release_guards() {
|
||||
say "快速发布:运行最小本地验证"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm test -- --test-name-pattern='publish|space|billing|wechat' >/dev/null
|
||||
npm run verify:mindspace-publish-guards >/dev/null
|
||||
npm run verify:page-data >/dev/null
|
||||
npm run test:release-gate:unit >/dev/null
|
||||
)
|
||||
}
|
||||
|
||||
verify_gate_for_release() {
|
||||
if is_fast_release; then
|
||||
if node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}" >/dev/null 2>&1; then
|
||||
say "快速发布:复用当前 commit 的有效 Gate report"
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
return 0
|
||||
fi
|
||||
say "快速发布:无有效 Gate report,跳过 Core+Impact 重跑,改跑最小本地验证"
|
||||
run_fast_release_guards
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}" >/dev/null 2>&1; then
|
||||
say "执行核心场景 + 变更影响域 Gate"
|
||||
DEPLOYED_SHA="${MEMIND_RELEASE_BASE_COMMIT:-}"
|
||||
if [[ -z "${DEPLOYED_SHA}" && "${DRY_RUN}" -ne 1 ]]; then
|
||||
DEPLOYED_SHA="$(
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
|
||||
"grep -E '^git_head=' '${APP_DIR}/.release-manifest.txt' 2>/dev/null | tail -1 | cut -d= -f2-" \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
fi
|
||||
IMPACT_ARGS=(--artifact "${RUNTIME_ROOT}")
|
||||
if [[ -n "${DEPLOYED_SHA}" ]]; then
|
||||
IMPACT_ARGS+=(--deployed-commit "${DEPLOYED_SHA}")
|
||||
fi
|
||||
node "${ROOT}/scripts/run-release-gate-impact.mjs" "${IMPACT_ARGS[@]}"
|
||||
fi
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run) DRY_RUN=1 ;;
|
||||
--skip-tests) SKIP_TESTS=1 ;;
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--yes|-y) AUTO_YES=1 ;;
|
||||
--mode)
|
||||
RELEASE_MODE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--skip-tests) SKIP_TESTS=1; shift ;;
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
--yes|-y) AUTO_YES=1; shift ;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
@@ -70,18 +127,26 @@ while [[ $# -gt 0 ]]; do
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
say() {
|
||||
printf '\n[%s] %s\n' "$(date +%H:%M:%S)" "$*"
|
||||
}
|
||||
case "${RELEASE_MODE}" in
|
||||
standard|fast) ;;
|
||||
*)
|
||||
echo "未知发布模式: ${RELEASE_MODE}(允许 standard 或 fast)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "ALLOW_DIRECT_STABLE_RELEASE 已废弃,按 --mode fast 处理"
|
||||
RELEASE_MODE=fast
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_TESTS}" -eq 1 ]]; then
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "允许 --skip-tests(ALLOW_DIRECT_STABLE_RELEASE=1,按 7/23 前直接整包规则;测试须已在发布前手动跑过)"
|
||||
if is_fast_release; then
|
||||
say "快速发布:跳过额外本地测试(发布前须已自行验证)"
|
||||
else
|
||||
echo "生产发布守门员禁止 --skip-tests。" >&2
|
||||
echo "标准发布禁止 --skip-tests。" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -97,8 +162,8 @@ if [[ "${ALLOW_MINDSPACE_PUBLIC_LINK_ISSUES:-0}" == "1" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "${DRY_RUN}" -eq 0 ]]; then
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "跳过灰度晋升证据(ALLOW_DIRECT_STABLE_RELEASE=1,按 7/23 前直接整包规则)"
|
||||
if is_fast_release; then
|
||||
say "快速发布:跳过 103 灰度晋升证据"
|
||||
else
|
||||
say "验证 103 灰度晋升证据"
|
||||
node "${ROOT}/scripts/verify-canary-promotion-evidence.mjs" --host "${HOST}"
|
||||
@@ -153,7 +218,9 @@ say "本地预检查"
|
||||
check_release_scope
|
||||
|
||||
if [[ "${SKIP_TESTS}" -ne 1 ]]; then
|
||||
if node "${ROOT}/scripts/resolve-release-ci-status.mjs" --commit "$(git -C "${ROOT}" rev-parse HEAD)"; then
|
||||
if is_fast_release; then
|
||||
say "快速发布:本地验证由 Gate 复用检查或 verify_gate_for_release 统一执行"
|
||||
elif node "${ROOT}/scripts/resolve-release-ci-status.mjs" --commit "$(git -C "${ROOT}" rev-parse HEAD)"; then
|
||||
say "Gitea CI already succeeded; skipping duplicate local npm test/verify"
|
||||
else
|
||||
say "运行最小验证"
|
||||
@@ -195,11 +262,15 @@ verify_runtime_artifact() {
|
||||
|
||||
verify_runtime_artifact
|
||||
|
||||
say "验证 MindSpace 发布与聊天 Finish 回归守卫"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm run verify:mindspace-publish-guards:full
|
||||
)
|
||||
if is_fast_release; then
|
||||
say "快速发布:跳过 verify:mindspace-publish-guards:full"
|
||||
else
|
||||
say "验证 MindSpace 发布与聊天 Finish 回归守卫"
|
||||
(
|
||||
cd "${ROOT}"
|
||||
npm run verify:mindspace-publish-guards:full
|
||||
)
|
||||
fi
|
||||
|
||||
verify_mindspace_public_links() {
|
||||
local target_root="${1:-${ROOT}/MindSpace}"
|
||||
@@ -353,27 +424,7 @@ REMOTE
|
||||
}
|
||||
|
||||
say "验证与当前 main 和 runtime artifact 绑定的 Gate report"
|
||||
if [[ "${ALLOW_DIRECT_STABLE_RELEASE:-0}" == "1" ]]; then
|
||||
say "跳过 Core+Impact Gate report(ALLOW_DIRECT_STABLE_RELEASE=1,按 7/23 前直接整包规则)"
|
||||
elif ! node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}" >/dev/null 2>&1; then
|
||||
say "执行核心场景 + 变更影响域 Gate"
|
||||
DEPLOYED_SHA="${MEMIND_RELEASE_BASE_COMMIT:-}"
|
||||
if [[ -z "${DEPLOYED_SHA}" && "${DRY_RUN}" -ne 1 ]]; then
|
||||
DEPLOYED_SHA="$(
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
|
||||
"grep -E '^git_head=' '${APP_DIR}/.release-manifest.txt' 2>/dev/null | tail -1 | cut -d= -f2-" \
|
||||
2>/dev/null || true
|
||||
)"
|
||||
fi
|
||||
IMPACT_ARGS=(--artifact "${RUNTIME_ROOT}")
|
||||
if [[ -n "${DEPLOYED_SHA}" ]]; then
|
||||
IMPACT_ARGS+=(--deployed-commit "${DEPLOYED_SHA}")
|
||||
fi
|
||||
node "${ROOT}/scripts/run-release-gate-impact.mjs" "${IMPACT_ARGS[@]}"
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
else
|
||||
node "${ROOT}/scripts/verify-release-gate-report.mjs" --artifact "${RUNTIME_ROOT}"
|
||||
fi
|
||||
verify_gate_for_release
|
||||
|
||||
if [[ "${DRY_RUN}" -ne 1 ]]; then
|
||||
say "执行 103 只读预检"
|
||||
@@ -389,6 +440,11 @@ if [[ "${AUTO_YES}" -ne 1 && "${DRY_RUN}" -ne 1 ]]; then
|
||||
echo "发布编号: ${RELEASE_ID}"
|
||||
echo "本地 HEAD: $(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
echo "说明: 此次只切换 test-memind Portal 到无源码 runtime,不包含 memindadm / memindplaza"
|
||||
if is_fast_release; then
|
||||
echo "模式: 快速发布(跳过灰度晋升证据与完整 Gate 重跑)"
|
||||
else
|
||||
echo "模式: 标准发布"
|
||||
fi
|
||||
read -r -p "确认继续发布到 103? [y/N] " confirm </dev/tty
|
||||
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 0
|
||||
fi
|
||||
@@ -400,6 +456,7 @@ say "生成发布清单"
|
||||
echo "host=$(hostname)"
|
||||
echo "git_head=$(git -C "${ROOT}" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
echo "git_branch=$(git -C "${ROOT}" branch --show-current 2>/dev/null || echo detached)"
|
||||
echo "release_mode=${RELEASE_MODE}"
|
||||
echo "artifact=.runtime/portal"
|
||||
echo "persisted_items=.env, MindSpace, data, users, .tailscale, public/plaza-covers, logs"
|
||||
} > "${MANIFEST_PATH}"
|
||||
|
||||
Reference in New Issue
Block a user