#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" SERVER_RS_DIR="${REPO_ROOT}/server-rs" MODULE_PATH="${SERVER_RS_DIR}/target/wasm32-unknown-unknown/release/spacetime_module.wasm" SPACETIME_SERVER_ALIAS="maincloud" CLEAR_DATABASE=0 MIGRATE_ON_CONFLICT=1 MIGRATION_DIR="" MIGRATION_BOOTSTRAP_SECRET="" MIGRATION_BOOTSTRAP_SECRET_MODE="auto" load_env_file() { local env_file="$1" local line key value if [[ ! -f "${env_file}" ]]; then return fi while IFS= read -r line || [[ -n "${line}" ]]; do line="${line%$'\r'}" line="${line#$'\xef\xbb\xbf'}" [[ -z "${line}" || "${line}" == \#* ]] && continue [[ "${line}" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] || continue key="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}" value="${value%\"}" value="${value#\"}" value="${value%\'}" value="${value#\'}" if [[ -z "${!key+x}" ]]; then export "${key}=${value}" fi done <"${env_file}" } usage() { cat <<'EOF' 用法: npm run spacetime:publish:maincloud npm run spacetime:publish:maincloud -- --database npm run spacetime:publish:maincloud -- --clear-database npm run spacetime:publish:maincloud -- --no-migrate-on-conflict npm run spacetime:publish:maincloud -- --no-migration-bootstrap-secret 说明: 发布 server-rs/crates/spacetime-module 到 SpacetimeDB Maincloud。 数据库名优先读取 --database,其次读取 GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE。 默认遇到 schema 冲突时会先导出迁移 JSON,再清库发布并导入回灌。 默认在构建 wasm 前随机生成迁移引导密钥,注入 GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET 并显示在控制台。 EOF } generate_migration_bootstrap_secret() { node -e 'const crypto = require("crypto"); process.stdout.write(crypto.randomBytes(32).toString("hex"));' } prepare_migration_bootstrap_secret() { case "${MIGRATION_BOOTSTRAP_SECRET_MODE}" in auto) MIGRATION_BOOTSTRAP_SECRET="$(generate_migration_bootstrap_secret)" ;; manual) if [[ "${#MIGRATION_BOOTSTRAP_SECRET}" -lt 16 ]]; then echo "[spacetime:maincloud] 迁移引导密钥至少需要 16 个字符。" >&2 exit 1 fi ;; disabled) unset GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET echo "[spacetime:maincloud] 未启用迁移引导密钥。" return ;; *) echo "[spacetime:maincloud] 未知迁移引导密钥模式: ${MIGRATION_BOOTSTRAP_SECRET_MODE}" >&2 exit 1 ;; esac export GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET="${MIGRATION_BOOTSTRAP_SECRET}" echo "[spacetime:maincloud] 迁移引导密钥: ${MIGRATION_BOOTSTRAP_SECRET}" } timestamp_slug() { node -e 'process.stdout.write(new Date().toISOString().replace(/[:.]/g, "-"));' } is_publish_conflict_output() { local output="$1" [[ "${output}" == *"conflict"* ]] || [[ "${output}" == *"schema"* && "${output}" == *"clear"* ]] } run_publish() { local output_file="$1" shift set +e spacetime "$@" >"${output_file}" 2>&1 local status=$? set -e cat "${output_file}" return "${status}" } run_conflict_migration_publish() { local migration_root migration_file publish_log if [[ "${MIGRATION_BOOTSTRAP_SECRET_MODE}" == "disabled" ]]; then echo "[spacetime:maincloud] schema 冲突需要迁移引导密钥;请去掉 --no-migration-bootstrap-secret 后重试。" >&2 exit 1 fi migration_root="${MIGRATION_DIR:-${REPO_ROOT}/tmp/spacetime-migrations/maincloud/${SPACETIME_DATABASE}}" mkdir -p "${migration_root}" migration_file="${migration_root}/$(timestamp_slug).json" publish_log="$(mktemp)" echo "[spacetime:maincloud] 检测到 schema 冲突,开始导出旧库迁移 JSON: ${migration_file}" node "${REPO_ROOT}/scripts/spacetime-export-migration-json.mjs" \ --server "${SPACETIME_SERVER_ALIAS}" \ --server-url "${SPACETIME_SERVER_URL}" \ --database "${SPACETIME_DATABASE}" \ --bootstrap-secret "${MIGRATION_BOOTSTRAP_SECRET}" \ --out "${migration_file}" \ --note "publish conflict export $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "[spacetime:maincloud] 清库发布新 SpacetimeDB wasm" if ! run_publish "${publish_log}" publish "${SPACETIME_DATABASE}" --server "${SPACETIME_SERVER_ALIAS}" --bin-path "${MODULE_PATH}" --clear-database --yes; then echo "[spacetime:maincloud] 清库发布失败,迁移 JSON 已保留: ${migration_file}" >&2 rm -f "${publish_log}" exit 1 fi rm -f "${publish_log}" echo "[spacetime:maincloud] 导入迁移 JSON 回灌数据" if ! node "${REPO_ROOT}/scripts/spacetime-import-migration-json.mjs" \ --server "${SPACETIME_SERVER_ALIAS}" \ --server-url "${SPACETIME_SERVER_URL}" \ --database "${SPACETIME_DATABASE}" \ --bootstrap-secret "${MIGRATION_BOOTSTRAP_SECRET}" \ --in "${migration_file}" \ --note "publish conflict import $(date -u +%Y-%m-%dT%H:%M:%SZ)"; then echo "[spacetime:maincloud] 导入失败,迁移 JSON 已保留: ${migration_file}" >&2 exit 1 fi echo "[spacetime:maincloud] schema 冲突迁移完成,迁移 JSON: ${migration_file}" } load_env_file "${REPO_ROOT}/.env" load_env_file "${REPO_ROOT}/.env.local" SPACETIME_DATABASE="${GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE:-}" SPACETIME_SERVER_URL="${GENARRATIVE_SPACETIME_MAINCLOUD_SERVER_URL:-https://maincloud.spacetimedb.com}" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage exit 0 ;; --database) SPACETIME_DATABASE="${2:?缺少 --database 的值}" shift 2 ;; --server-url) SPACETIME_SERVER_URL="${2:?缺少 --server-url 的值}" shift 2 ;; --clear-database) CLEAR_DATABASE=1 shift ;; --no-migrate-on-conflict) MIGRATE_ON_CONFLICT=0 shift ;; --migration-dir) MIGRATION_DIR="${2:?缺少 --migration-dir 的值}" shift 2 ;; --migration-bootstrap-secret) MIGRATION_BOOTSTRAP_SECRET="${2:?缺少 --migration-bootstrap-secret 的值}" MIGRATION_BOOTSTRAP_SECRET_MODE="manual" shift 2 ;; --no-migration-bootstrap-secret) MIGRATION_BOOTSTRAP_SECRET="" MIGRATION_BOOTSTRAP_SECRET_MODE="disabled" shift ;; *) echo "[spacetime:maincloud] 未知参数: $1" >&2 usage >&2 exit 1 ;; esac done if [[ -z "${SPACETIME_DATABASE}" ]]; then echo "[spacetime:maincloud] 缺少 GENARRATIVE_SPACETIME_MAINCLOUD_DATABASE。" >&2 echo "[spacetime:maincloud] 请在 .env.local 中配置,或通过 --database 传入。" >&2 exit 1 fi if ! command -v cargo >/dev/null 2>&1; then echo "[spacetime:maincloud] 缺少 cargo 命令。" >&2 exit 1 fi if ! command -v node >/dev/null 2>&1; then echo "[spacetime:maincloud] 缺少 node 命令,无法生成迁移引导密钥。" >&2 exit 1 fi if ! command -v spacetime >/dev/null 2>&1; then echo "[spacetime:maincloud] 缺少 spacetime CLI,请先安装并登录 Maincloud。" >&2 exit 1 fi prepare_migration_bootstrap_secret echo "[spacetime:maincloud] 构建 spacetime-module wasm" cargo build \ --manifest-path "${SERVER_RS_DIR}/Cargo.toml" \ -p spacetime-module \ --target wasm32-unknown-unknown \ --release PUBLISH_ARGS=( publish "${SPACETIME_DATABASE}" --server "${SPACETIME_SERVER_ALIAS}" --bin-path "${MODULE_PATH}" --yes ) if [[ "${CLEAR_DATABASE}" -eq 1 ]]; then # Maincloud 清库只在 schema 冲突时触发,避免无冲突升级误删线上数据。 PUBLISH_ARGS+=(-c=on-conflict) fi echo "[spacetime:maincloud] 发布 SpacetimeDB wasm: ${SPACETIME_DATABASE} -> ${SPACETIME_SERVER_ALIAS}" PUBLISH_LOG="$(mktemp)" if ! run_publish "${PUBLISH_LOG}" "${PUBLISH_ARGS[@]}"; then PUBLISH_OUTPUT="$(cat "${PUBLISH_LOG}")" rm -f "${PUBLISH_LOG}" if [[ "${CLEAR_DATABASE}" -eq 0 && "${MIGRATE_ON_CONFLICT}" -eq 1 ]] && is_publish_conflict_output "${PUBLISH_OUTPUT}"; then run_conflict_migration_publish else echo "[spacetime:maincloud] 发布失败。" >&2 exit 1 fi else rm -f "${PUBLISH_LOG}" fi cat <