Merge branch 'master' of http://82.157.175.59:3000/GenarrativeAI/Genarrative
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -13,12 +13,14 @@
|
|||||||
重点补充:RPG 创作与运行时脚本职责地图见 [RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md](./reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md)。
|
重点补充:RPG 创作与运行时脚本职责地图见 [RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md](./reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md)。
|
||||||
- [PRD](./prd):产品需求与阶段计划;新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md)。
|
- [PRD](./prd):产品需求与阶段计划;新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md)。
|
||||||
|
|
||||||
|
SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
|
||||||
|
|
||||||
## 推荐阅读顺序
|
## 推荐阅读顺序
|
||||||
|
|
||||||
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。
|
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。
|
||||||
2. 再看 [工程审查总览](./audits/engineering/README.md) 和 [文本审计总览](./audits/text/README.md),了解当前风险。
|
2. 再看 [工程审查总览](./audits/engineering/README.md) 和 [文本审计总览](./audits/text/README.md),了解当前风险。
|
||||||
3. 需要排期时看 [规划与优先级](./planning/README.md)。
|
3. 需要排期时看 [规划与优先级](./planning/README.md)。
|
||||||
4. 需要补方案时进入 [系统设计](./design/README.md) / [技术方案](./technical/README.md);涉及后端先看 [当前后端实现基线](./technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md)。
|
4. 需要补方案时进入 [系统设计](./design/README.md) / [技术方案](./technical/README.md);涉及后端先看 [当前后端实现基线](./technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md),涉及 SpacetimeDB 表结构变更时再看 [表结构变更约束](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。
|
||||||
5. 需要对齐目标边界时再进入 [PRD](./prd)。
|
5. 需要对齐目标边界时再进入 [PRD](./prd)。
|
||||||
|
|
||||||
## 分类规则
|
## 分类规则
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ Genarrative-Database-Export
|
|||||||
1. `DATABASE`:目标 SpacetimeDB 数据库名;留空时读取仓库环境变量。
|
1. `DATABASE`:目标 SpacetimeDB 数据库名;留空时读取仓库环境变量。
|
||||||
2. `SERVER`:SpacetimeDB server 别名,默认 `maincloud`。
|
2. `SERVER`:SpacetimeDB server 别名,默认 `maincloud`。
|
||||||
3. `SERVER_URL`:显式服务地址;填写后优先于 `SERVER`。
|
3. `SERVER_URL`:显式服务地址;填写后优先于 `SERVER`。
|
||||||
4. `ROOT_DIR`:可选,透传给 `spacetime --root-dir`。
|
4. `DEPLOY_DIRECTORY`:固定部署目录,默认 `/var/lib/jenkins/deploy/Genarrative`。
|
||||||
5. `INCLUDE_TABLES`:可选,逗号分隔的表名白名单。
|
5. `ROOT_DIR`:可选,透传给 `spacetime --root-dir`;为空时使用 `<DEPLOY_DIRECTORY>/.spacetimedb`。
|
||||||
6. `OUTPUT_DIRECTORY`:导出文件目录,默认 `database-exports`。
|
6. `INCLUDE_TABLES`:可选,逗号分隔的表名白名单。
|
||||||
7. `EXPORT_NAME`:导出文件名;留空时使用 `spacetime-migration-<BUILD_NUMBER>.json`。
|
7. `OUTPUT_DIRECTORY`:导出文件目录,默认 `database-exports`。
|
||||||
|
8. `EXPORT_NAME`:导出文件名;留空时使用 `spacetime-migration-<BUILD_NUMBER>.json`。
|
||||||
|
|
||||||
导出成功后,Jenkins 归档:
|
导出成功后,Jenkins 归档:
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ Genarrative-Database-Import
|
|||||||
关键参数:
|
关键参数:
|
||||||
|
|
||||||
1. `INPUT_FILE`:必填,迁移 JSON 文件路径。
|
1. `INPUT_FILE`:必填,迁移 JSON 文件路径。
|
||||||
2. `DATABASE`、`SERVER`、`SERVER_URL`、`ROOT_DIR`:与导出流水线一致。
|
2. `DATABASE`、`SERVER`、`SERVER_URL`、`DEPLOY_DIRECTORY`、`ROOT_DIR`:与导出流水线一致。
|
||||||
3. `INCLUDE_TABLES`:可选,只导入指定表。
|
3. `INCLUDE_TABLES`:可选,只导入指定表。
|
||||||
4. `DRY_RUN`:默认 `true`,只校验不写入。
|
4. `DRY_RUN`:默认 `true`,只校验不写入。
|
||||||
5. `INCREMENTAL`:默认 `true`,跳过已存在或冲突的行。
|
5. `INCREMENTAL`:默认 `true`,跳过已存在或冲突的行。
|
||||||
@@ -85,7 +86,27 @@ Genarrative-Database-Import
|
|||||||
3. Jenkinsfile 不打印 token;生产环境应通过 Jenkins 凭据或目标机器环境变量传入敏感值。
|
3. Jenkinsfile 不打印 token;生产环境应通过 Jenkins 凭据或目标机器环境变量传入敏感值。
|
||||||
4. 如果不传 `TOKEN`,导入脚本会创建临时 Web API identity,并调用迁移授权/撤销 procedure 收敛权限窗口。
|
4. 如果不传 `TOKEN`,导入脚本会创建临时 Web API identity,并调用迁移授权/撤销 procedure 收敛权限窗口。
|
||||||
|
|
||||||
## 5. 文件清单
|
## 5. 本地部署测试参数
|
||||||
|
|
||||||
|
`Genarrative-Build-And-Deploy` 增加以下本地发布包参数,便于在 Jenkins 中测试本地 SpacetimeDB,不依赖 Maincloud:
|
||||||
|
|
||||||
|
1. `DATABASE`:发布包默认数据库名,默认 `genarrative_pipeline_local_test`。
|
||||||
|
2. `API_PORT`:发布包内 api-server 端口,默认 `8082`。
|
||||||
|
3. `WEB_PORT`:发布包内静态网站端口,默认 `25001`。
|
||||||
|
4. `SPACETIME_PORT`:发布包内本地 SpacetimeDB 端口,默认 `3101`。
|
||||||
|
5. `DEPLOY_DIRECTORY`:固定部署目录,继续透传给 `Genarrative-Deploy`。
|
||||||
|
|
||||||
|
数据库导入导出流水线在本地测试时应显式填写:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DATABASE=genarrative_pipeline_local_test
|
||||||
|
SERVER_URL=http://127.0.0.1:3101
|
||||||
|
DEPLOY_DIRECTORY=/var/lib/jenkins/deploy/Genarrative
|
||||||
|
```
|
||||||
|
|
||||||
|
这样脚本会自动使用 `/var/lib/jenkins/deploy/Genarrative/.spacetimedb` 作为 `spacetime --root-dir`,避免回退到 Jenkins 用户全局 CLI 登录态,也避免误连 Maincloud。
|
||||||
|
|
||||||
|
## 6. 文件清单
|
||||||
|
|
||||||
```text
|
```text
|
||||||
jenkins/Jenkinsfile.database-export
|
jenkins/Jenkinsfile.database-export
|
||||||
|
|||||||
@@ -4,13 +4,7 @@
|
|||||||
|
|
||||||
## 文档列表
|
## 文档列表
|
||||||
|
|
||||||
- [RPG_HOME_CUSTOM_WORLD_LIBRARY_TIMEOUT_FIX_2026-04-29.md](./RPG_HOME_CUSTOM_WORLD_LIBRARY_TIMEOUT_FIX_2026-04-29.md):记录首页 `custom-world-library` 首屏列表 SpacetimeDB procedure 超时的根因,冻结列表读模型轻量化与 procedure 等待窗口配置化的修复口径。
|
- [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md):冻结 SpacetimeDB 表结构变更约束、自动迁移可接受范围、冲突后的系统行为,以及保留旧数据的增量迁移流程;凡涉及 `spacetime publish`、表字段调整或 `migration.rs` 对齐时优先参考。
|
||||||
- [PRODUCT_NAMING_TAONI_RENAME_2026-04-29.md](./PRODUCT_NAMING_TAONI_RENAME_2026-04-29.md):记录本轮产品中文名调整为“陶泥”,以及陶泥币、陶泥号、陶泥主三类对外称谓替换的落地边界。
|
|
||||||
- [PUZZLE_FORM_CREATION_FLOW_2026-04-29.md](./PUZZLE_FORM_CREATION_FLOW_2026-04-29.md):记录拼图创作入口从 Agent 对话改为标题与画面描述填表、参考图直达首图生成,以及结果页合并为单列表的落地边界。
|
|
||||||
- [PUZZLE_IMAGE_AND_RUNTIME_9_16_ALIGNMENT_2026-04-29.md](./PUZZLE_IMAGE_AND_RUNTIME_9_16_ALIGNMENT_2026-04-29.md):记录拼图生成图片、结果页预览、历史素材缩略和运行时棋盘统一为 9:16 竖屏的落地边界。
|
|
||||||
- [RPG_NPC_BATTLE_ENTRY_QUEST_AND_TARGET_FIX_2026-04-29.md](./RPG_NPC_BATTLE_ENTRY_QUEST_AND_TARGET_FIX_2026-04-29.md):记录 NPC 进入战斗时不再自动补章节任务、pending 委托不被误接取,以及战斗目标缺少 encounter 时仍可渲染的修复边界。
|
|
||||||
- [RPG_RUNTIME_PANEL_CLOSE_BUTTON_FIX_2026-04-29.md](./RPG_RUNTIME_PANEL_CLOSE_BUTTON_FIX_2026-04-29.md):记录 RPG 运行态历史手写弹窗右上关闭按钮点击失效的统一修复边界,收口像素风关闭按钮的事件传播、层级和点击面积。
|
|
||||||
- [RPG_RUNTIME_PARTY_INVENTORY_PANEL_UI_SIMPLIFICATION_2026-04-29.md](./RPG_RUNTIME_PARTY_INVENTORY_PANEL_UI_SIMPLIFICATION_2026-04-29.md):记录 RPG 运行态队伍面板删除成员列表上方任务信息、背包面板删除顶部旅程回顾的展示边界,保持辅助面板首屏聚焦成员与物品。
|
|
||||||
- [RPG_PROMPT_FRONTEND_REMOVAL_AND_SERVER_RS_MIGRATION_2026-04-28.md](./RPG_PROMPT_FRONTEND_REMOVAL_AND_SERVER_RS_MIGRATION_2026-04-28.md):冻结 RPG 提示词禁止存在前端的边界,明确前端只保留 API client,角色私聊/NPC 对话/剧情续写等 prompt 统一收口到 `server-rs`。
|
- [RPG_PROMPT_FRONTEND_REMOVAL_AND_SERVER_RS_MIGRATION_2026-04-28.md](./RPG_PROMPT_FRONTEND_REMOVAL_AND_SERVER_RS_MIGRATION_2026-04-28.md):冻结 RPG 提示词禁止存在前端的边界,明确前端只保留 API client,角色私聊/NPC 对话/剧情续写等 prompt 统一收口到 `server-rs`。
|
||||||
- [RPG_CREATION_RESULT_VIEW_BACKEND_TRUTH_MIGRATION_2026-04-28.md](./RPG_CREATION_RESULT_VIEW_BACKEND_TRUTH_MIGRATION_2026-04-28.md):冻结 RPG 创作结果页保存、Agent session/result preview 真相优先级和结果页入口裁决迁移到后端 result-view 的落地边界。
|
- [RPG_CREATION_RESULT_VIEW_BACKEND_TRUTH_MIGRATION_2026-04-28.md](./RPG_CREATION_RESULT_VIEW_BACKEND_TRUTH_MIGRATION_2026-04-28.md):冻结 RPG 创作结果页保存、Agent session/result preview 真相优先级和结果页入口裁决迁移到后端 result-view 的落地边界。
|
||||||
- [RPG_CREATION_PROFILE_GENERATION_BACKEND_MIGRATION_2026-04-28.md](./RPG_CREATION_PROFILE_GENERATION_BACKEND_MIGRATION_2026-04-28.md):记录 RPG 创作 profile 生成移除非浏览器 legacy AI 回退,统一通过 `server-rs` 的 `/api/runtime/custom-world/profile` 生成世界底稿。
|
- [RPG_CREATION_PROFILE_GENERATION_BACKEND_MIGRATION_2026-04-28.md](./RPG_CREATION_PROFILE_GENERATION_BACKEND_MIGRATION_2026-04-28.md):记录 RPG 创作 profile 生成移除非浏览器 legacy AI 回退,统一通过 `server-rs` 的 `/api/runtime/custom-world/profile` 生成世界底稿。
|
||||||
@@ -196,5 +190,5 @@
|
|||||||
## 使用建议
|
## 使用建议
|
||||||
|
|
||||||
- 做实现选型时,优先看这一组。
|
- 做实现选型时,优先看这一组。
|
||||||
- 做后端实现前,先看 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`,再进入具体 Rust / SpacetimeDB 方案。
|
- 做后端实现前,先看 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`;涉及 SpacetimeDB 表结构、发布或迁移时,再看 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`,最后进入具体 Rust / SpacetimeDB 方案。
|
||||||
- 做阶段排期时,把这一组和 `docs/planning/`、`docs/prd/` 一起看,更容易判断先后顺序。
|
- 做阶段排期时,把这一组和 `docs/planning/`、`docs/prd/` 一起看,更容易判断先后顺序。
|
||||||
|
|||||||
235
docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md
Normal file
235
docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
# SpacetimeDB 表结构变更约束
|
||||||
|
|
||||||
|
本文档总结 SpacetimeDB 开发过程中修改表结构时需要遵守的约束,以及发生迁移冲突后如何在保留旧数据的前提下手动迁移。
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
当对已有数据库重新执行 `spacetime publish` 时,SpacetimeDB 会尝试根据旧 module schema 和新 module schema 生成自动迁移计划。当前实现的重点是自动 schema migration,不是通用的脚本式数据迁移框架。
|
||||||
|
|
||||||
|
相关实现入口:
|
||||||
|
|
||||||
|
- 迁移计划生成:`crates/schema/src/auto_migrate.rs`
|
||||||
|
- 迁移执行:`crates/core/src/db/update.rs`
|
||||||
|
- 发布前预检查:`crates/client-api/src/routes/database.rs`
|
||||||
|
- CLI 发布确认逻辑:`crates/cli/src/subcommands/publish.rs`
|
||||||
|
|
||||||
|
## 与 PostgreSQL 迁移模型的差别
|
||||||
|
|
||||||
|
PostgreSQL 的常见迁移模型是脚本驱动的 DDL migration。开发者显式写出每一步迁移动作,例如:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE users DROP COLUMN old_name;
|
||||||
|
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
|
||||||
|
DROP TABLE old_table;
|
||||||
|
```
|
||||||
|
|
||||||
|
也就是说,PG 里能做什么主要取决于 PostgreSQL DDL 能力、锁风险、现有数据是否满足约束,以及 migration 工具如何组织执行。
|
||||||
|
|
||||||
|
SpacetimeDB 的迁移模型不同。schema 来自模块代码里的 `#[table]`、reducers 和类型定义。开发者修改模块代码后执行:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
spacetime publish <DATABASE_NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
host 会比较新模块声明的 schema 和旧数据库 schema,然后尝试自动迁移。自动迁移能力是有限的:SpacetimeDB 更像是“模块代码声明世界,publish 时数据库尝试跟上,但只走安全路径”。
|
||||||
|
|
||||||
|
对常见变更,可以按下面方式理解:
|
||||||
|
|
||||||
|
| 变更 | PostgreSQL | SpacetimeDB |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 加表 | `CREATE TABLE` migration | 自动允许 |
|
||||||
|
| 删表 | `DROP TABLE`,生产需谨慎 | 高风险。当前代码只有限支持删除空表;非空表会失败 |
|
||||||
|
| 加字段 | `ALTER TABLE ADD COLUMN`,可带默认值 | 只允许加在表定义末尾,并且必须有 default,例如 Rust `#[default(...)]` |
|
||||||
|
| 删字段 | `ALTER TABLE DROP COLUMN` | 自动迁移禁止 |
|
||||||
|
| 改字段类型、重命名、调顺序 | 可用 DDL 或拆成多步 migration | 自动迁移通常禁止 |
|
||||||
|
| 加普通索引 | `CREATE INDEX` / `CREATE INDEX CONCURRENTLY` | 自动允许 |
|
||||||
|
| 删索引 | `DROP INDEX` | 允许,但可能破坏某些 subscription query |
|
||||||
|
| 加唯一约束或主键 | 可先清理数据再加 | 对已有表自动迁移禁止 |
|
||||||
|
| 删除唯一约束 | 可做 | 自动允许移除 `#[unique]` |
|
||||||
|
| 删除主键注解 | 可做 | 允许,但可能破坏旧客户端缓存行为 |
|
||||||
|
|
||||||
|
一句话概括:PG 迁移更自由,但风险由开发者和 migration 流程管理;SpacetimeDB 迁移更保守,系统只自动接受它能证明相对安全、客户端不容易坏的 schema 演进。
|
||||||
|
|
||||||
|
## 总体原则
|
||||||
|
|
||||||
|
1. 对已有表做变更时,应优先选择向后兼容的增量式变更。
|
||||||
|
2. 不要直接删除、改名、重排或重定义已有列。
|
||||||
|
3. 对复杂结构变更,应新增目标表,通过 reducer 或后台批处理把旧数据搬到新表。
|
||||||
|
4. 等数据迁移完成、旧客户端完成升级、旧表数据清空后,再移除旧表。
|
||||||
|
5. 开发环境可以使用 `--delete-data` 重建数据库,生产环境不要用它作为数据迁移方案。
|
||||||
|
|
||||||
|
## 通常安全的变更
|
||||||
|
|
||||||
|
这些变更一般可以自动迁移,并且通常不会破坏现有客户端:
|
||||||
|
|
||||||
|
- 新增表。
|
||||||
|
- 新增索引。
|
||||||
|
- 添加或移除 auto-increment/sequence 类注解,但新增 sequence 会有额外数据范围预检查。
|
||||||
|
- 将表从 private 改为 public。
|
||||||
|
- 新增 reducer。
|
||||||
|
- 移除 unique constraint。
|
||||||
|
- 新增 view,或在兼容范围内更新 view。
|
||||||
|
|
||||||
|
## 可能破坏客户端但可确认后发布的变更
|
||||||
|
|
||||||
|
这些变更可以生成自动迁移计划,但可能使旧客户端不兼容。CLI 会要求用户确认,并通过 `BreakClients` policy 携带 token 继续发布。
|
||||||
|
|
||||||
|
- 给已有表末尾新增带 default 的列,例如 Rust `#[default(...)]`。
|
||||||
|
- 删除空表。
|
||||||
|
- 删除或改变 view 的返回列、参数、上下文等不兼容部分。
|
||||||
|
- 将 public 表改为 private。
|
||||||
|
- 移除 primary key 注解。
|
||||||
|
- 移除索引,尤其是旧客户端订阅查询依赖该索引时。
|
||||||
|
- RLS 规则增加、删除或变化。
|
||||||
|
- 删除或修改 reducer,使旧客户端继续调用旧 reducer 时失败。
|
||||||
|
|
||||||
|
## 会触发冲突或拒绝自动迁移的变更
|
||||||
|
|
||||||
|
以下变更通常会在自动迁移规划阶段失败,服务端返回 `AutoMigrateError`,CLI 表现为需要 manual migration:
|
||||||
|
|
||||||
|
- 删除已有列。
|
||||||
|
- 重命名已有列。
|
||||||
|
- 重排已有列。
|
||||||
|
- 在已有表中间新增列。
|
||||||
|
- 给已有表新增没有 default value 的列。
|
||||||
|
- 修改已有列类型,除非是布局兼容的受限变更。
|
||||||
|
- 修改 product/sum 类型时减少字段或 variant。
|
||||||
|
- 修改 product/sum 类型时重命名字段或 variant。
|
||||||
|
- 修改类型导致 layout size 或 alignment 不兼容。
|
||||||
|
- 给已有表新增 unique constraint。
|
||||||
|
- 修改已有 unique constraint。
|
||||||
|
- 修改 table type。
|
||||||
|
- 修改 event flag。
|
||||||
|
- 修改 index accessor name。
|
||||||
|
|
||||||
|
## 规划通过但执行时仍会失败的情况
|
||||||
|
|
||||||
|
有些变更可以生成迁移计划,但执行阶段仍可能失败:
|
||||||
|
|
||||||
|
- 删除非空表:当前代码会生成 `RemoveTable`,但执行时会检查 row count。只有空表可以删除,非空表会失败并回滚。
|
||||||
|
- schedule 相关变更:规划层可能生成 `AddSchedule` 或 `RemoveSchedule`,但执行层目前仍返回 not implemented。
|
||||||
|
- 新增或修改 sequence 时,如果已有数据落在新 sequence 的分配范围内,预检查会失败。
|
||||||
|
- 新增索引、RLS、view 重算等操作如果底层校验或执行失败,也会导致本次更新回滚。
|
||||||
|
|
||||||
|
## 冲突后的系统行为
|
||||||
|
|
||||||
|
发生冲突时,SpacetimeDB 默认不会自动修改旧数据,也不会执行用户自定义迁移脚本。
|
||||||
|
|
||||||
|
常见结果:
|
||||||
|
|
||||||
|
- 发布前预检查发现冲突:CLI 打印原因并中止发布。
|
||||||
|
- 服务端迁移规划失败:API 返回 `400 Database update rejected: ...`。
|
||||||
|
- 迁移执行中失败:当前事务 rollback,旧数据库和旧 module 继续运行。
|
||||||
|
- 只有显式使用 `--delete-data` 或 `--delete-data=on-conflict` 时,才会清空旧数据并用新 module 重建数据库。
|
||||||
|
|
||||||
|
## 保留旧数据的手动迁移方式
|
||||||
|
|
||||||
|
生产环境推荐使用增量迁移,而不是直接修改旧表结构。
|
||||||
|
|
||||||
|
建议步骤:
|
||||||
|
|
||||||
|
1. 保留旧表。
|
||||||
|
|
||||||
|
不要直接删除或修改旧表的关键字段。例如保留 `character`。
|
||||||
|
|
||||||
|
2. 新增目标表。
|
||||||
|
|
||||||
|
新建 `character_v2`,结构定义为目标 schema。新增表属于自动迁移支持范围。
|
||||||
|
|
||||||
|
3. 发布兼容中间版本。
|
||||||
|
|
||||||
|
中间版本同时包含旧表和新表,并添加迁移 helper/reducer:
|
||||||
|
|
||||||
|
- 读数据时,优先读新表。
|
||||||
|
- 新表没有对应行时,从旧表读取,转换后写入新表。
|
||||||
|
- 写数据时,优先写新表。
|
||||||
|
- 如果仍需兼容旧客户端,同步写旧表。
|
||||||
|
- 大量历史数据通过 `migrate_batch(limit)` 之类的 reducer 分批迁移。
|
||||||
|
|
||||||
|
4. 验证迁移完成。
|
||||||
|
|
||||||
|
检查新表行数、主键覆盖、关键字段一致性和客户端升级情况。
|
||||||
|
|
||||||
|
5. 清空旧表数据。
|
||||||
|
|
||||||
|
通过 reducer 分批删除旧表数据。因为当前实现只允许删除空表。
|
||||||
|
|
||||||
|
6. 删除旧表定义。
|
||||||
|
|
||||||
|
旧表为空后,再从 schema 中移除旧表并发布最终版本。
|
||||||
|
|
||||||
|
## 增量迁移示例
|
||||||
|
|
||||||
|
下面示例展示从旧表 `character` 迁移到新表 `character_v2` 的基本模式:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn get_character(ctx: &ReducerContext, player: Identity) -> CharacterV2 {
|
||||||
|
if let Some(row) = ctx.db.character_v2().player_id().find(player) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
let old = ctx
|
||||||
|
.db
|
||||||
|
.character()
|
||||||
|
.player_id()
|
||||||
|
.find(player)
|
||||||
|
.expect("character not found");
|
||||||
|
|
||||||
|
let migrated = CharacterV2 {
|
||||||
|
player_id: old.player_id,
|
||||||
|
nickname: old.nickname,
|
||||||
|
level: old.level,
|
||||||
|
class: old.class,
|
||||||
|
alliance: Alliance::Neutral,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.db.character_v2().insert(migrated.clone());
|
||||||
|
migrated
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
批量迁移时,应限制每次处理的行数,避免单个事务过大:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[spacetimedb::reducer]
|
||||||
|
fn migrate_character_batch(ctx: &ReducerContext, limit: u32) {
|
||||||
|
let mut migrated = 0;
|
||||||
|
|
||||||
|
for old in ctx.db.character().iter() {
|
||||||
|
if migrated >= limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.db.character_v2().player_id().find(old.player_id).is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.db.character_v2().insert(CharacterV2 {
|
||||||
|
player_id: old.player_id,
|
||||||
|
nickname: old.nickname,
|
||||||
|
level: old.level,
|
||||||
|
class: old.class,
|
||||||
|
alliance: Alliance::Neutral,
|
||||||
|
});
|
||||||
|
|
||||||
|
migrated += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 发布前检查清单
|
||||||
|
|
||||||
|
发布涉及表结构变更的 module 前,至少检查:
|
||||||
|
|
||||||
|
- 是否删除、改名、重排或修改了已有列。
|
||||||
|
- 新增列是否位于表定义末尾,并且是否有 default value。
|
||||||
|
- 是否给已有表新增了 unique 或 primary key 约束。
|
||||||
|
- 是否删除了非空表。
|
||||||
|
- 是否修改了 event table、schedule table、RLS 或 view。
|
||||||
|
- 是否会破坏旧客户端订阅、reducer 调用或本地缓存假设。
|
||||||
|
- 是否需要先发布中间版本做增量迁移。
|
||||||
|
- 是否已在 staging 数据库用真实规模样本测试过发布。
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
SpacetimeDB 的表结构变更策略应以“新增、兼容、分阶段”为核心。实际开发时应尽量只做 additive change:加表、加索引、在末尾加带默认值的字段。遇到自动迁移冲突时,不要直接清库或强行修改旧表;应保留旧数据,新增目标 schema,通过 reducer 分批迁移数据,逐步切换客户端,最后在旧数据清空后移除旧表。
|
||||||
@@ -10,7 +10,10 @@ pipeline {
|
|||||||
string(name: 'AGENT_LABEL', defaultValue: 'built-in', description: '构建节点标签')
|
string(name: 'AGENT_LABEL', defaultValue: 'built-in', description: '构建节点标签')
|
||||||
string(name: 'GENARRATIVE_WORKSPACE_ROOT', defaultValue: '', description: '源码根目录,留空则使用当前 Jenkins 工作区')
|
string(name: 'GENARRATIVE_WORKSPACE_ROOT', defaultValue: '', description: '源码根目录,留空则使用当前 Jenkins 工作区')
|
||||||
string(name: 'BUILD_VERSION', defaultValue: '', description: '发布版本号,留空则使用 Jenkins BUILD_NUMBER')
|
string(name: 'BUILD_VERSION', defaultValue: '', description: '发布版本号,留空则使用 Jenkins BUILD_NUMBER')
|
||||||
|
string(name: 'DATABASE', defaultValue: 'genarrative_pipeline_local_test', description: '发布包默认 SpacetimeDB database')
|
||||||
|
string(name: 'API_PORT', defaultValue: '8082', description: '发布包内 api-server 端口')
|
||||||
string(name: 'WEB_PORT', defaultValue: '25001', description: '发布包内静态网站端口,默认 25001')
|
string(name: 'WEB_PORT', defaultValue: '25001', description: '发布包内静态网站端口,默认 25001')
|
||||||
|
string(name: 'SPACETIME_PORT', defaultValue: '3101', description: '发布包内本地 SpacetimeDB 端口')
|
||||||
booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '部署时是否清空 SpacetimeDB 数据后再发布 wasm')
|
booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '部署时是否清空 SpacetimeDB 数据后再发布 wasm')
|
||||||
booleanParam(name: 'RUN_NPM_CI', defaultValue: false, description: '构建前是否执行 npm ci')
|
booleanParam(name: 'RUN_NPM_CI', defaultValue: false, description: '构建前是否执行 npm ci')
|
||||||
string(name: 'DEPLOY_JOB_NAME', defaultValue: 'Genarrative-Deploy', description: '部署流水线作业名')
|
string(name: 'DEPLOY_JOB_NAME', defaultValue: 'Genarrative-Deploy', description: '部署流水线作业名')
|
||||||
@@ -30,6 +33,29 @@ pipeline {
|
|||||||
env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER
|
env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER
|
||||||
// 允许 Jenkins Job 直接指定固定源码目录,未指定时回退到当前工作区。
|
// 允许 Jenkins Job 直接指定固定源码目录,未指定时回退到当前工作区。
|
||||||
env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd()
|
env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd()
|
||||||
|
def database = params.DATABASE?.trim()
|
||||||
|
if (!database) {
|
||||||
|
error('DATABASE 不能为空。')
|
||||||
|
}
|
||||||
|
if (!(database ==~ /^[0-9A-Za-z._-]+$/)) {
|
||||||
|
error("DATABASE 只能包含数字、字母、点、下划线和短横线,当前值: ${database}")
|
||||||
|
}
|
||||||
|
env.EFFECTIVE_DATABASE = database
|
||||||
|
def apiPort = params.API_PORT?.trim()
|
||||||
|
if (!apiPort) {
|
||||||
|
error('API_PORT 不能为空。')
|
||||||
|
}
|
||||||
|
if (!(apiPort ==~ /^[0-9]+$/)) {
|
||||||
|
error("API_PORT 必须是数字端口,当前值: ${apiPort}")
|
||||||
|
}
|
||||||
|
if (apiPort.length() > 5) {
|
||||||
|
error("API_PORT 必须在 1-65535 之间,当前值: ${apiPort}")
|
||||||
|
}
|
||||||
|
def parsedApiPort = apiPort.toInteger()
|
||||||
|
if (parsedApiPort < 1 || parsedApiPort > 65535) {
|
||||||
|
error("API_PORT 必须在 1-65535 之间,当前值: ${apiPort}")
|
||||||
|
}
|
||||||
|
env.EFFECTIVE_API_PORT = apiPort
|
||||||
def webPort = params.WEB_PORT?.trim()
|
def webPort = params.WEB_PORT?.trim()
|
||||||
if (!webPort) {
|
if (!webPort) {
|
||||||
error('WEB_PORT 不能为空。')
|
error('WEB_PORT 不能为空。')
|
||||||
@@ -46,6 +72,21 @@ pipeline {
|
|||||||
}
|
}
|
||||||
// 后续构建与下游部署都使用校验后的同一端口值,避免参数空格导致上下游不一致。
|
// 后续构建与下游部署都使用校验后的同一端口值,避免参数空格导致上下游不一致。
|
||||||
env.EFFECTIVE_WEB_PORT = webPort
|
env.EFFECTIVE_WEB_PORT = webPort
|
||||||
|
def spacetimePort = params.SPACETIME_PORT?.trim()
|
||||||
|
if (!spacetimePort) {
|
||||||
|
error('SPACETIME_PORT 不能为空。')
|
||||||
|
}
|
||||||
|
if (!(spacetimePort ==~ /^[0-9]+$/)) {
|
||||||
|
error("SPACETIME_PORT 必须是数字端口,当前值: ${spacetimePort}")
|
||||||
|
}
|
||||||
|
if (spacetimePort.length() > 5) {
|
||||||
|
error("SPACETIME_PORT 必须在 1-65535 之间,当前值: ${spacetimePort}")
|
||||||
|
}
|
||||||
|
def parsedSpacetimePort = spacetimePort.toInteger()
|
||||||
|
if (parsedSpacetimePort < 1 || parsedSpacetimePort > 65535) {
|
||||||
|
error("SPACETIME_PORT 必须在 1-65535 之间,当前值: ${spacetimePort}")
|
||||||
|
}
|
||||||
|
env.EFFECTIVE_SPACETIME_PORT = spacetimePort
|
||||||
// 记录当前构建节点名,部署阶段必须回到同一节点读取本地 build 目录。
|
// 记录当前构建节点名,部署阶段必须回到同一节点读取本地 build 目录。
|
||||||
env.SOURCE_NODE_NAME = env.NODE_NAME
|
env.SOURCE_NODE_NAME = env.NODE_NAME
|
||||||
}
|
}
|
||||||
@@ -60,6 +101,7 @@ pipeline {
|
|||||||
# 这里不使用 -x,避免删除 node_modules 等忽略目录后与 RUN_NPM_CI=false 的配置冲突。
|
# 这里不使用 -x,避免删除 node_modules 等忽略目录后与 RUN_NPM_CI=false 的配置冲突。
|
||||||
git reset --hard HEAD
|
git reset --hard HEAD
|
||||||
git clean -fd
|
git clean -fd
|
||||||
|
rm -rf "build/${EFFECTIVE_BUILD_VERSION}"
|
||||||
'
|
'
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -73,8 +115,13 @@ pipeline {
|
|||||||
sh """
|
sh """
|
||||||
bash -lc '
|
bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# 构建并部署流水线显式透传 Web 端口,确保部署包默认监听 25001,同时允许 Jenkins 参数覆盖。
|
# 构建并部署流水线显式透传本地测试参数,避免发布包回退到默认库名或端口。
|
||||||
npm run deploy:rust:remote -- --skip-upload --name "${env.EFFECTIVE_BUILD_VERSION}" --web-port "${env.EFFECTIVE_WEB_PORT}"
|
npm run deploy:rust:remote -- --skip-upload \
|
||||||
|
--name "${env.EFFECTIVE_BUILD_VERSION}" \
|
||||||
|
--database "${env.EFFECTIVE_DATABASE}" \
|
||||||
|
--api-port "${env.EFFECTIVE_API_PORT}" \
|
||||||
|
--web-port "${env.EFFECTIVE_WEB_PORT}" \
|
||||||
|
--spacetime-port "${env.EFFECTIVE_SPACETIME_PORT}"
|
||||||
test -d "build/${env.EFFECTIVE_BUILD_VERSION}"
|
test -d "build/${env.EFFECTIVE_BUILD_VERSION}"
|
||||||
'
|
'
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ pipeline {
|
|||||||
string(name: 'DATABASE', defaultValue: '', description: 'SpacetimeDB 数据库名,留空则读取环境变量')
|
string(name: 'DATABASE', defaultValue: '', description: 'SpacetimeDB 数据库名,留空则读取环境变量')
|
||||||
string(name: 'SERVER', defaultValue: 'maincloud', description: 'SpacetimeDB server 别名,例如 maincloud/local/dev')
|
string(name: 'SERVER', defaultValue: 'maincloud', description: 'SpacetimeDB server 别名,例如 maincloud/local/dev')
|
||||||
string(name: 'SERVER_URL', defaultValue: '', description: 'SpacetimeDB server URL,填写后优先于 SERVER')
|
string(name: 'SERVER_URL', defaultValue: '', description: 'SpacetimeDB server URL,填写后优先于 SERVER')
|
||||||
string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选')
|
string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录,ROOT_DIR 为空时使用其 .spacetimedb')
|
||||||
|
string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选,优先于 DEPLOY_DIRECTORY')
|
||||||
string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单')
|
string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单')
|
||||||
|
string(name: 'BOOTSTRAP_SECRET', defaultValue: '', description: '可选,授权临时导出 identity 的迁移引导密钥')
|
||||||
string(name: 'OUTPUT_DIRECTORY', defaultValue: 'database-exports', description: '导出文件目录,相对源码根目录或绝对路径')
|
string(name: 'OUTPUT_DIRECTORY', defaultValue: 'database-exports', description: '导出文件目录,相对源码根目录或绝对路径')
|
||||||
string(name: 'EXPORT_NAME', defaultValue: '', description: '导出文件名,留空则自动使用构建号')
|
string(name: 'EXPORT_NAME', defaultValue: '', description: '导出文件名,留空则自动使用构建号')
|
||||||
}
|
}
|
||||||
@@ -28,6 +30,11 @@ pipeline {
|
|||||||
script {
|
script {
|
||||||
// 允许 Jenkins Job 指定固定源码目录;未指定时使用当前工作区,方便临时手工执行。
|
// 允许 Jenkins Job 指定固定源码目录;未指定时使用当前工作区,方便临时手工执行。
|
||||||
env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd()
|
env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd()
|
||||||
|
def deployDirectory = params.DEPLOY_DIRECTORY?.trim()
|
||||||
|
if (!deployDirectory) {
|
||||||
|
error('DEPLOY_DIRECTORY 不能为空。')
|
||||||
|
}
|
||||||
|
env.EFFECTIVE_ROOT_DIR = params.ROOT_DIR?.trim() ? params.ROOT_DIR.trim() : "${deployDirectory}/.spacetimedb"
|
||||||
def exportName = params.EXPORT_NAME?.trim()
|
def exportName = params.EXPORT_NAME?.trim()
|
||||||
if (!exportName) {
|
if (!exportName) {
|
||||||
exportName = "spacetime-migration-${env.BUILD_NUMBER}.json"
|
exportName = "spacetime-migration-${env.BUILD_NUMBER}.json"
|
||||||
@@ -60,12 +67,15 @@ pipeline {
|
|||||||
if [[ -n "${params.SERVER_URL}" ]]; then
|
if [[ -n "${params.SERVER_URL}" ]]; then
|
||||||
args+=(--server-url "${params.SERVER_URL}")
|
args+=(--server-url "${params.SERVER_URL}")
|
||||||
fi
|
fi
|
||||||
if [[ -n "${params.ROOT_DIR}" ]]; then
|
if [[ -n "${env.EFFECTIVE_ROOT_DIR}" ]]; then
|
||||||
args+=(--root-dir "${params.ROOT_DIR}")
|
args+=(--root-dir "${env.EFFECTIVE_ROOT_DIR}")
|
||||||
fi
|
fi
|
||||||
if [[ -n "${params.INCLUDE_TABLES}" ]]; then
|
if [[ -n "${params.INCLUDE_TABLES}" ]]; then
|
||||||
args+=(--include "${params.INCLUDE_TABLES}")
|
args+=(--include "${params.INCLUDE_TABLES}")
|
||||||
fi
|
fi
|
||||||
|
if [[ -n "${params.BOOTSTRAP_SECRET}" ]]; then
|
||||||
|
args+=(--bootstrap-secret "${params.BOOTSTRAP_SECRET}")
|
||||||
|
fi
|
||||||
# 复用后端迁移 procedure 导出 JSON,避免 Jenkins 直接拼接表结构和 SQL。
|
# 复用后端迁移 procedure 导出 JSON,避免 Jenkins 直接拼接表结构和 SQL。
|
||||||
node "\${args[@]}"
|
node "\${args[@]}"
|
||||||
test -s "\${output_path}"
|
test -s "\${output_path}"
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ pipeline {
|
|||||||
string(name: 'DATABASE', defaultValue: '', description: 'SpacetimeDB 数据库名,留空则读取环境变量')
|
string(name: 'DATABASE', defaultValue: '', description: 'SpacetimeDB 数据库名,留空则读取环境变量')
|
||||||
string(name: 'SERVER', defaultValue: 'maincloud', description: 'SpacetimeDB server 别名,例如 maincloud/local/dev')
|
string(name: 'SERVER', defaultValue: 'maincloud', description: 'SpacetimeDB server 别名,例如 maincloud/local/dev')
|
||||||
string(name: 'SERVER_URL', defaultValue: '', description: 'SpacetimeDB server URL,填写后优先于 SERVER')
|
string(name: 'SERVER_URL', defaultValue: '', description: 'SpacetimeDB server URL,填写后优先于 SERVER')
|
||||||
string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选')
|
string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录,ROOT_DIR 为空时使用其 .spacetimedb')
|
||||||
|
string(name: 'ROOT_DIR', defaultValue: '', description: 'spacetime CLI root-dir,可选,优先于 DEPLOY_DIRECTORY')
|
||||||
string(name: 'INPUT_FILE', defaultValue: '', description: '必填,迁移 JSON 文件路径,相对源码根目录或绝对路径')
|
string(name: 'INPUT_FILE', defaultValue: '', description: '必填,迁移 JSON 文件路径,相对源码根目录或绝对路径')
|
||||||
string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单')
|
string(name: 'INCLUDE_TABLES', defaultValue: '', description: '可选,逗号分隔的表名白名单')
|
||||||
booleanParam(name: 'DRY_RUN', defaultValue: true, description: '仅校验导入,不写入数据')
|
booleanParam(name: 'DRY_RUN', defaultValue: true, description: '仅校验导入,不写入数据')
|
||||||
@@ -37,6 +38,11 @@ pipeline {
|
|||||||
if (params.INCREMENTAL && params.REPLACE_EXISTING) {
|
if (params.INCREMENTAL && params.REPLACE_EXISTING) {
|
||||||
error('INCREMENTAL 不能和 REPLACE_EXISTING 同时启用。')
|
error('INCREMENTAL 不能和 REPLACE_EXISTING 同时启用。')
|
||||||
}
|
}
|
||||||
|
def deployDirectory = params.DEPLOY_DIRECTORY?.trim()
|
||||||
|
if (!deployDirectory) {
|
||||||
|
error('DEPLOY_DIRECTORY 不能为空。')
|
||||||
|
}
|
||||||
|
env.EFFECTIVE_ROOT_DIR = params.ROOT_DIR?.trim() ? params.ROOT_DIR.trim() : "${deployDirectory}/.spacetimedb"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,8 +74,8 @@ pipeline {
|
|||||||
if [[ -n "${params.SERVER_URL}" ]]; then
|
if [[ -n "${params.SERVER_URL}" ]]; then
|
||||||
args+=(--server-url "${params.SERVER_URL}")
|
args+=(--server-url "${params.SERVER_URL}")
|
||||||
fi
|
fi
|
||||||
if [[ -n "${params.ROOT_DIR}" ]]; then
|
if [[ -n "${env.EFFECTIVE_ROOT_DIR}" ]]; then
|
||||||
args+=(--root-dir "${params.ROOT_DIR}")
|
args+=(--root-dir "${env.EFFECTIVE_ROOT_DIR}")
|
||||||
fi
|
fi
|
||||||
if [[ -n "${params.INCLUDE_TABLES}" ]]; then
|
if [[ -n "${params.INCLUDE_TABLES}" ]]; then
|
||||||
args+=(--include "${params.INCLUDE_TABLES}")
|
args+=(--include "${params.INCLUDE_TABLES}")
|
||||||
|
|||||||
@@ -483,6 +483,15 @@ server.listen(webPort, webHost, () => {
|
|||||||
});
|
});
|
||||||
WEB_SERVER
|
WEB_SERVER
|
||||||
|
|
||||||
|
touch "${TARGET_DIR}/.env"
|
||||||
|
for env_file in "${TARGET_DIR}/.env" "${TARGET_DIR}/.env.local"; do
|
||||||
|
if [[ -f "${env_file}" ]]; then
|
||||||
|
grep -v '^GENARRATIVE_SPACETIME_ROOT_DIR=' "${env_file}" >"${env_file}.tmp" || true
|
||||||
|
mv "${env_file}.tmp" "${env_file}"
|
||||||
|
printf '\nGENARRATIVE_SPACETIME_ROOT_DIR=__GENARRATIVE_RUNTIME_SPACETIME_ROOT_DIR__\n' >>"${env_file}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
cat >"${TARGET_DIR}/start.sh" <<'START_SCRIPT'
|
cat >"${TARGET_DIR}/start.sh" <<'START_SCRIPT'
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
@@ -575,6 +584,9 @@ load_env_file "${SCRIPT_DIR}/.env"
|
|||||||
load_env_file "${SCRIPT_DIR}/.env.local"
|
load_env_file "${SCRIPT_DIR}/.env.local"
|
||||||
|
|
||||||
SPACETIME_ROOT_DIR="${GENARRATIVE_SPACETIME_ROOT_DIR:-${SCRIPT_DIR}/.spacetimedb}"
|
SPACETIME_ROOT_DIR="${GENARRATIVE_SPACETIME_ROOT_DIR:-${SCRIPT_DIR}/.spacetimedb}"
|
||||||
|
if [[ "${SPACETIME_ROOT_DIR}" == "__GENARRATIVE_RUNTIME_SPACETIME_ROOT_DIR__" ]]; then
|
||||||
|
SPACETIME_ROOT_DIR="${SCRIPT_DIR}/.spacetimedb"
|
||||||
|
fi
|
||||||
SPACETIME_HOST="${GENARRATIVE_SPACETIME_HOST:-__GENARRATIVE_DEFAULT_SPACETIME_HOST__}"
|
SPACETIME_HOST="${GENARRATIVE_SPACETIME_HOST:-__GENARRATIVE_DEFAULT_SPACETIME_HOST__}"
|
||||||
SPACETIME_PORT="${GENARRATIVE_SPACETIME_PORT:-__GENARRATIVE_DEFAULT_SPACETIME_PORT__}"
|
SPACETIME_PORT="${GENARRATIVE_SPACETIME_PORT:-__GENARRATIVE_DEFAULT_SPACETIME_PORT__}"
|
||||||
SPACETIME_SERVER_URL="${GENARRATIVE_SPACETIME_SERVER_URL:-http://${SPACETIME_HOST}:${SPACETIME_PORT}}"
|
SPACETIME_SERVER_URL="${GENARRATIVE_SPACETIME_SERVER_URL:-http://${SPACETIME_HOST}:${SPACETIME_PORT}}"
|
||||||
@@ -787,7 +799,7 @@ start_process() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[start] 启动 ${name}"
|
echo "[start] 启动 ${name}"
|
||||||
nohup "$@" >"${log_file}" 2>&1 &
|
JENKINS_NODE_COOKIE=dontKillMe BUILD_ID=dontKillMe nohup "$@" >"${log_file}" 2>&1 &
|
||||||
echo "$!" >"${pid_file}"
|
echo "$!" >"${pid_file}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,10 +931,10 @@ STOP_SCRIPT
|
|||||||
|
|
||||||
chmod +x "${TARGET_DIR}/start.sh" "${TARGET_DIR}/stop.sh"
|
chmod +x "${TARGET_DIR}/start.sh" "${TARGET_DIR}/stop.sh"
|
||||||
|
|
||||||
cat >"${TARGET_DIR}/README.md" <<EOF
|
cat >"${TARGET_DIR}/README.md" <<'EOF'
|
||||||
# Genarrative Ubuntu Release
|
# Genarrative Ubuntu Release
|
||||||
|
|
||||||
构建时间:\`${BUILD_NAME}\`
|
构建时间:`__GENARRATIVE_BUILD_NAME__`
|
||||||
|
|
||||||
## 内容
|
## 内容
|
||||||
|
|
||||||
@@ -961,6 +973,7 @@ cat >"${TARGET_DIR}/README.md" <<EOF
|
|||||||
- OSS、LLM、短信、微信等业务密钥仍通过目标服务器环境变量或同目录 \`.env.local\` 管理。
|
- OSS、LLM、短信、微信等业务密钥仍通过目标服务器环境变量或同目录 \`.env.local\` 管理。
|
||||||
- 迁移引导密钥由构建发布包时随机生成,构建日志和服务器 \`start.sh\` 发布日志都会显示同一份密钥。
|
- 迁移引导密钥由构建发布包时随机生成,构建日志和服务器 \`start.sh\` 发布日志都会显示同一份密钥。
|
||||||
EOF
|
EOF
|
||||||
|
replace_placeholder_in_file "${TARGET_DIR}/README.md" "__GENARRATIVE_BUILD_NAME__" "${BUILD_NAME}"
|
||||||
|
|
||||||
BUILD_COMPLETED=1
|
BUILD_COMPLETED=1
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ DEPLOY_ITEMS=(
|
|||||||
".env.local"
|
".env.local"
|
||||||
"README.md"
|
"README.md"
|
||||||
"api-server"
|
"api-server"
|
||||||
|
"migration-bootstrap-secret.txt"
|
||||||
"spacetime_module.wasm"
|
"spacetime_module.wasm"
|
||||||
"start.sh"
|
"start.sh"
|
||||||
"stop.sh"
|
"stop.sh"
|
||||||
|
|||||||
@@ -93,8 +93,13 @@ export function buildSpacetimeCallArgs(options, procedureName, input) {
|
|||||||
args.push('call');
|
args.push('call');
|
||||||
if (options.server) {
|
if (options.server) {
|
||||||
args.push('-s', options.server);
|
args.push('-s', options.server);
|
||||||
|
} else if (options.serverUrl) {
|
||||||
|
args.push('-s', options.serverUrl);
|
||||||
}
|
}
|
||||||
args.push(...options.passthrough);
|
args.push(...options.passthrough);
|
||||||
|
if (!options.passthrough.includes('--no-config')) {
|
||||||
|
args.push('--no-config');
|
||||||
|
}
|
||||||
args.push(options.database, procedureName, JSON.stringify(input), '-y');
|
args.push(options.database, procedureName, JSON.stringify(input), '-y');
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user