diff --git a/.github/workflows/shared-supabase-policy.yml b/.github/workflows/shared-supabase-policy.yml index 0b00afe..f890f71 100644 --- a/.github/workflows/shared-supabase-policy.yml +++ b/.github/workflows/shared-supabase-policy.yml @@ -25,7 +25,8 @@ jobs: - name: Check migration policy shell: bash env: - MIGRATION_PATH: supabase/migrations + # Shadow Shift 的 Supabase 迁移位于 app 私有子模块;子模块自身也有同名门禁。 + MIGRATION_PATH: app/supabase/migrations BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} run: | @@ -33,16 +34,12 @@ jobs: changed_migrations="$(git diff --name-status "$BASE_SHA" "$HEAD_SHA" -- "$MIGRATION_PATH" || true)" if [[ -n "$changed_migrations" ]]; then - while IFS=$'\t' read -r status file _; do - [[ -z "$file" ]] && continue - case "$status" in - A) ;; - *) echo "Existing migration history is immutable: $file"; exit 1 ;; - esac - done <<< "$changed_migrations" + echo "Product repositories may not add or modify executable files under ${MIGRATION_PATH}." + echo "$changed_migrations" + exit 1 fi - if git diff --unified=0 "$BASE_SHA" "$HEAD_SHA" -- . ':(exclude).github/workflows/shared-supabase-policy.yml' | grep -nE 'supabase db push|supabase migration repair|supabase db reset --linked|SUPABASE_DB_PASSWORD|SUPABASE_ACCESS_TOKEN'; then + if git diff --unified=0 "$BASE_SHA" "$HEAD_SHA" -- . ':(exclude).github/workflows/shared-supabase-policy.yml' | grep -nE 'supabase db push|supabase db query --linked|supabase migration repair|supabase db reset --linked|SUPABASE_DB_PASSWORD|SUPABASE_ACCESS_TOKEN'; then echo "Direct production migration or migration-repair commands are forbidden in product repositories." exit 1 fi diff --git a/README.md b/README.md index c703b4c..d16ec1b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ > **影迁 Shadow Shift** —— 一键将 WizNote 笔记迁移到 Obsidian,保留笔记、图片、附件与 WikiLinks 内部链接。 > -> 📖 [快速参考指南](QUICK_REFERENCE.md) · 📚 [文档索引](DOCUMENTATION_INDEX.md) · 📝 [更新日志](CHANGELOG.md) +> 📖 [快速参考指南](QUICK_REFERENCE.md) · 📚 [文档索引](DOCUMENTATION_INDEX.md) · 📝 [更新日志](CHANGELOG.md) · 🌐 [Shadow Lab 官网](https://shadow.wang/zh/products/shadow-shift) --- diff --git a/local-dev/profile.json b/local-dev/profile.json new file mode 100644 index 0000000..10696a5 --- /dev/null +++ b/local-dev/profile.json @@ -0,0 +1,15 @@ +{ + "profile_schema_version": 1, + "project_id": "shadow-shift", + "display_name": "Shadow Shift", + "requires": [], + "schema_overlays": [], + "edge_functions": [], + "processes": [], + "optional_features": [ + "shadow-shift-cli", + "shadow-shift-app" + ], + "ingress_claims": [], + "health_checks": [] +} diff --git a/scripts/start-local-dev.sh b/scripts/start-local-dev.sh new file mode 100755 index 0000000..cb5ac1c --- /dev/null +++ b/scripts/start-local-dev.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Shadow Shift 的中央本地开发薄入口。 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +RUNTIME_ROOT="${SHADOW_LOCAL_DEV_RUNTIME_DIR:-$WORKSPACE_ROOT/shadow-size/merchant-admin}" + +if [[ ! -f "$RUNTIME_ROOT/scripts/local-dev/cli.mjs" ]]; then + printf '[SHIFT-local] 找不到中央本地开发 CLI:%s\n' "$RUNTIME_ROOT/scripts/local-dev/cli.mjs" >&2 + exit 2 +fi + +cd "$RUNTIME_ROOT" +LOCAL_DEV_ENTRY_PROJECT=shadow-shift exec node scripts/local-dev/cli.mjs "$@" diff --git a/tests/test_local_dev_profile.py b/tests/test_local_dev_profile.py new file mode 100644 index 0000000..d8d81bd --- /dev/null +++ b/tests/test_local_dev_profile.py @@ -0,0 +1,29 @@ +import json +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +PROFILE = json.loads((ROOT / "local-dev" / "profile.json").read_text()) + + +def test_shadow_shift_profile_is_canonical_and_offline(): + assert PROFILE["profile_schema_version"] == 1 + assert PROFILE["project_id"] == "shadow-shift" + assert PROFILE["requires"] == [] + assert PROFILE["processes"] == [] + assert PROFILE["ingress_claims"] == [] + assert PROFILE["health_checks"] == [] + + +def test_shadow_shift_profile_does_not_claim_shared_schema_or_edge_runtime(): + assert PROFILE["schema_overlays"] == [] + assert PROFILE["edge_functions"] == [] + assert PROFILE["optional_features"] == ["shadow-shift-cli", "shadow-shift-app"] + + +def test_shadow_shift_entrypoint_delegates_to_central_runtime(): + script = (ROOT / "scripts" / "start-local-dev.sh").read_text() + assert "scripts/local-dev/cli.mjs" in script + assert "supabase start" not in script + assert "functions serve" not in script + assert "cloudflared" not in script