From dada5a4797481e8c32f284bf9249143e8a8465bf Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 9 May 2026 20:37:17 +0800 Subject: [PATCH] Document local SpacetimeDB dev skip and clear workflows --- .hermes/shared-memory/pitfalls.md | 16 ++++ ...ND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md | 10 +++ scripts/dev-rust-stack.sh | 49 +----------- scripts/dev-web-rust.mjs | 77 ++++++++----------- 4 files changed, 60 insertions(+), 92 deletions(-) diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 9e5dc4e1..b6f17e65 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -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 解析报错。 diff --git a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md index 09167c94..3b619321 100644 --- a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md +++ b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md @@ -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 身份仍未对齐。 编译警告治理: diff --git a/scripts/dev-rust-stack.sh b/scripts/dev-rust-stack.sh index 7077aac7..e693e613 100644 --- a/scripts/dev-rust-stack.sh +++ b/scripts/dev-rust-stack.sh @@ -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 diff --git a/scripts/dev-web-rust.mjs b/scripts/dev-web-rust.mjs index 681f61c8..98d1ed10 100644 --- a/scripts/dev-web-rust.mjs +++ b/scripts/dev-web-rust.mjs @@ -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,