Document local SpacetimeDB dev skip and clear workflows
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-09 20:37:17 +08:00
parent 7e608d4230
commit dada5a4797
4 changed files with 60 additions and 92 deletions

View File

@@ -83,6 +83,22 @@
- 验证:`spacetime --root-dir server-rs/.spacetimedb/local list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限的全局 identity。
- 关联:`scripts/dev-rust-stack.sh``docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`
## 本地 SpacetimeDB 联调可按阶段跳过宿主或发布
- 现象:本地 `npm run dev``3101` 已占用、重复发布 SpacetimeDB wasm 编译太慢,或只想检查 `spacetime-module` 语法而被完整联调链路拖慢。
- 原因:`npm run dev` 默认同时启动 SpacetimeDB standalone、发布 `server-rs/crates/spacetime-module`、启动 Rust `api-server`、主站 Vite 与后台 Vite并非每个阶段都需要完整重启和重新发布。
- 处理:`3101` 已被可复用 standalone 占用时使用 `npm run dev -- --skip-spacetime`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101``--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``scripts/dev-rust-stack.sh`
## 本地 SpacetimeDB publish 401 可清本地库重发
- 现象:本地 `spacetime publish` 显示 `401` 无权限,或重新发布仍像是在更新旧库。
- 原因:本地开发 root-dir 中保留的数据库、控制库身份或发布身份与当前目标不一致。
- 处理:确认本地开发数据可以丢弃后,执行 `spacetime --root-dir=server-rs/.spacetimedb/local server clear`,再重新运行 `npm run dev` 或本地 publish。
- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查 root-dir、库名和 CLI 身份。
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`
## Vite SPA fallback 吞掉 API 请求
- 现象:本地请求 `/api/profile/*` 等接口时返回 HTML被前端当 JSON 解析报错。

View File

@@ -58,14 +58,23 @@ Vite 代理覆盖范围:
4. 如只想启动进程不发布模块,可传 `--skip-publish`
5. 后续进入正式版本前,涉及表结构变化时必须在开发阶段补齐迁移表与迁移函数,不能依赖清库发布作为正式升级策略。
本地联调跳过策略:
1. 如果 `3101` 已被当前可复用的 SpacetimeDB standalone 占用,可使用 `npm run dev -- --skip-spacetime` 跳过 SpacetimeDB 宿主启动,只复用现有监听实例并继续后续发布、`api-server` 与前端启动。若占用方不是本仓库本地 SpacetimeDB先停止占用进程或改用 `--spacetime-port`
2. 如果当前没有修改 `server-rs/crates/spacetime-module`,可使用 `npm run dev -- --skip-publish` 跳过数据库发布,降低本地启动时的 SpacetimeDB wasm 编译耗时。
3. 如果当前阶段只需要检查 `spacetime-module` 语法,不需要重新发布本地数据库,可执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`。该命令只做 Rust 编译检查,不生成新数据库,也不刷新 bindings。
常用示例:
```bash
npm run dev:rust
npm run dev -- --skip-spacetime
npm run dev -- --skip-publish
./scripts/dev-rust-stack.sh
./scripts/dev-rust-stack.sh --api-port 8090 --spacetime-port 3110 --database genarrative-dev
./scripts/dev-rust-stack.sh --skip-spacetime --skip-publish
./scripts/dev-rust-stack.sh --preserve-database
cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml
```
bindings 生成:
@@ -107,6 +116,7 @@ npm run dev:rust:logs -- --follow
3. 发布库名与 `GENARRATIVE_SPACETIME_DATABASE` 不一致时,`/api/runtime/custom-world-gallery` 会从 Rust `api-server` 返回 `502`,前端首页只能展示空态或错误提示,无法自行修复。
4. 如果 Vite 输出 `/api/auth/refresh``/api/auth/login-options``/api/runtime/custom-world-gallery``ECONNREFUSED`,先确认当前脚本是否已经打印 `等待 api-server 就绪` 并通过;正常情况下 Vite 只会在 `/healthz` 可访问后启动,不应再因为 Rust 监听未完成而代理失败。
5. 如果 `spacetime server ping` 打印 `Server could not be reached (502 Bad Gateway)`,即使命令退出码为 `0` 也不能直接视为已就绪;本地脚本会继续探测 `/v1/ping`。若 `/v1/ping` 返回 `200`,说明 standalone 已经可用,可以继续发布模块;若 `/v1/ping` 也失败,脚本会继续等待新启动实例,或在 root-dir 已被其他实例占用时输出占用进程。
6. 如果本地 `spacetime publish` 显示 `401` 无权限,且确认本地开发数据可以丢弃,可执行 `spacetime --root-dir=server-rs/.spacetimedb/local server clear` 清除本地 SpacetimeDB 数据库后重新发布。重新发布时日志应表现为创建新的数据库,而不是更新旧数据库;如果仍显示更新旧库或继续无权限,说明 root-dir、库名或 CLI 身份仍未对齐。
编译警告治理:

View File

@@ -195,7 +195,7 @@ is_spacetime_ready() {
local server="$1"
local output
if output="$(spacetime --root-dir="${SPACETIME_ROOT_DIR}" server ping "${server}" 2>&1)" &&
if output="$(spacetime server ping "${server}" 2>&1)" &&
[[ "${output}" == *"Server is online:"* ]]; then
return 0
fi
@@ -345,48 +345,6 @@ prepare_migration_bootstrap_secret() {
echo "[dev:rust] 迁移引导密钥: ${MIGRATION_BOOTSTRAP_SECRET}"
}
is_sccache_wrapper_failure_log() {
local log_file="$1"
grep -Eiq \
'sccache: error|could not execute process.*sccache|Failed to send data to or receive data from server|Mismatch of client/server versions|Failed to read response header|failed to fill whole buffer' \
"${log_file}"
}
run_spacetime_publish_with_sccache_fallback() {
local root_dir="$1"
shift
local publish_log
publish_log="$(mktemp -t genarrative-spacetime-publish.XXXXXX.log)"
set +e
spacetime --root-dir="${root_dir}" "$@" 2> >(tee "${publish_log}" >&2)
local publish_status="$?"
set -e
if [[ "${publish_status}" -eq 0 ]]; then
rm -f "${publish_log}"
return 0
fi
if ! is_sccache_wrapper_failure_log "${publish_log}"; then
rm -f "${publish_log}"
return "${publish_status}"
fi
echo "[dev:rust] 检测到 sccache wrapper 通信异常,改用 rustc 直连重试 SpacetimeDB 发布。"
echo "[dev:rust] 这只影响本次 publish项目级 sccache 配置不会被修改。"
set +e
RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= spacetime --root-dir="${root_dir}" "$@"
publish_status="$?"
set -e
rm -f "${publish_log}"
return "${publish_status}"
}
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
SERVER_RS_DIR="${REPO_ROOT}/server-rs"
@@ -606,7 +564,6 @@ if [[ "${SKIP_SPACETIME}" -ne 1 ]]; then
# 当目标端口被占用时SpacetimeDB 会询问是否使用最近的可用端口;
# 这里直接发送回车接受默认建议,再从启动日志解析实际监听端口。
printf '\n' | spacetime \
--root-dir="${SPACETIME_ROOT_DIR}" \
start \
--data-dir "${SPACETIME_DATA_DIR}" \
--listen-addr "${SPACETIME_HOST}:${SPACETIME_PORT}" \
@@ -644,9 +601,7 @@ if [[ "${SKIP_PUBLISH}" -ne 1 ]]; then
cd "${SERVER_RS_DIR}"
# spacetime publish 会在内部调用 Cargo从 server-rs 目录执行,确保读取
# server-rs/.cargo/config.toml 中的 sccache/linker 配置,并复用同一套 target 缓存。
# Windows 本地 sccache server 偶发通信异常时,保留 root-dir/target 缓存路径,
# 仅对重试子进程清空 Cargo wrapper避免可选缓存工具阻断真实构建。
run_spacetime_publish_with_sccache_fallback "${SPACETIME_ROOT_DIR}" "${PUBLISH_ARGS[@]}"
spacetime "${PUBLISH_ARGS[@]}"
)
fi

View File

@@ -37,30 +37,14 @@ loadEnvFile(resolve(repoRoot, '.env'), fileEnv);
loadEnvFile(resolve(repoRoot, '.env.local'), fileEnv);
loadEnvFile(resolve(repoRoot, '.env.secrets.local'), fileEnv);
function resolveConfiguredTarget() {
if (fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET) {
return fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET;
}
if (fileEnv.RUST_SERVER_TARGET) {
return fileEnv.RUST_SERVER_TARGET;
}
if (fileEnv.GENARRATIVE_API_TARGET) {
return fileEnv.GENARRATIVE_API_TARGET;
}
if (fileEnv.GENARRATIVE_API_PORT) {
return `http://127.0.0.1:${fileEnv.GENARRATIVE_API_PORT}`;
}
return '';
}
function buildFallbackCandidates() {
function buildTargetCandidates() {
const candidates = [
'http://127.0.0.1:3100',
fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET,
fileEnv.RUST_SERVER_TARGET,
fileEnv.GENARRATIVE_API_TARGET,
`http://127.0.0.1:${fileEnv.GENARRATIVE_API_PORT || '3100'}`,
'http://127.0.0.1:8082',
'http://127.0.0.1:3100',
].filter(Boolean);
return Array.from(new Set(candidates));
@@ -86,30 +70,39 @@ async function isTargetReachable(target) {
}
async function resolveRuntimeTarget() {
const configuredTarget = resolveConfiguredTarget();
const candidates = buildTargetCandidates();
const reachableTargets = [];
if (configuredTarget) {
return {
target: configuredTarget,
fallbackUsed: false,
targetUnavailable: !(await isTargetReachable(configuredTarget)),
};
}
for (const target of buildFallbackCandidates()) {
for (const target of candidates) {
if (await isTargetReachable(target)) {
return {
target,
fallbackUsed: true,
targetUnavailable: false,
};
reachableTargets.push(target);
if (
target === fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET ||
target === fileEnv.RUST_SERVER_TARGET ||
target === fileEnv.GENARRATIVE_API_TARGET
) {
return {
target,
fallbackUsed: false,
};
}
}
}
if (reachableTargets.length > 0) {
return {
target: reachableTargets[0],
fallbackUsed: true,
};
}
return {
target: 'http://127.0.0.1:3100',
target:
fileEnv.GENARRATIVE_RUNTIME_SERVER_TARGET ||
fileEnv.RUST_SERVER_TARGET ||
fileEnv.GENARRATIVE_API_TARGET ||
`http://127.0.0.1:${fileEnv.GENARRATIVE_API_PORT || '3100'}`,
fallbackUsed: false,
targetUnavailable: true,
};
}
@@ -120,12 +113,6 @@ if (runtimeTarget.fallbackUsed) {
);
}
if (runtimeTarget.targetUnavailable) {
console.warn(
`[dev:web] Rust target 当前不可用: ${runtimeTarget.target},请先启动 api-server。`,
);
}
const mergedEnv = {
...fileEnv,
RUST_SERVER_TARGET: runtimeTarget.target,