Skip to content

fix: migration 010 β€” clean up group_members RLS policies to minimal set #26

fix: migration 010 β€” clean up group_members RLS policies to minimal set

fix: migration 010 β€” clean up group_members RLS policies to minimal set #26

Workflow file for this run

name: πŸš€ Release
on:
push:
branches:
- main
tags:
- 'v*.*.*'
workflow_dispatch:
permissions:
contents: write
jobs:
release:
name: Create or Update GitHub Release
runs-on: ubuntu-latest
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 🏷️ Get tag info
id: tag
run: |
set -euo pipefail
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
else
VERSION="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' package.json | head -1)"
TAG="v${VERSION}"
fi
if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9.-]+)?$'; then
echo "❌ Invalid package version: $VERSION"
exit 1
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: 🟒 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: βœ… Release syntax checks
run: |
set -euo pipefail
node --check server.mjs
node --check server/backup-manager.mjs
node --check public/sync/backup-normalizer.js
node --check public/sync/local-data-adapter.js
node --check scripts/validate-backup-files.mjs
node --check scripts/repair-user-backup.mjs
node --check scripts/prove-new-browser-restore.mjs
node --check scripts/validate-storage-cleanup.mjs
node --check scripts/verify-supabase-security.mjs
node --check scripts/compare-remote-assets.mjs
npm run docs:validate
- name: πŸ“‹ Extract changelog section
id: changelog
run: |
VERSION="${{ steps.tag.outputs.version }}"
# Extract section for this version from CHANGELOG.md
NOTES=$(awk "/^## \[$VERSION\]/{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md)
if [ -z "$NOTES" ]; then
NOTES="See [CHANGELOG.md](./CHANGELOG.md) for details."
fi
# Write to a file to avoid multi-line/special-char issues in GITHUB_OUTPUT
echo "$NOTES" > /tmp/changelog_notes.txt
{
echo "notes<<CHANGELOG_EOF"
cat /tmp/changelog_notes.txt
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
- name: πŸ€– Enhance release notes with AI
id: ai_notes
continue-on-error: true
env:
YEPAPI_KEY: ${{ secrets.YEPAPI_KEY }}
run: |
if [ -z "$YEPAPI_KEY" ]; then
echo "No YEPAPI_KEY β€” using raw changelog notes"
{
echo "notes<<CHANGELOG_EOF"
cat /tmp/changelog_notes.txt
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
exit 0
fi
VERSION="${{ steps.tag.outputs.version }}"
# Write excerpt to a file β€” avoids shell injection via changelog content
head -50 /tmp/changelog_notes.txt > /tmp/changelog_excerpt.txt
# Build JSON payload using node to handle escaping safely
node -e "
const fs = require('fs');
const excerpt = fs.readFileSync('/tmp/changelog_excerpt.txt', 'utf8').trim();
const payload = {
model: 'gpt-4o-mini',
messages: [{
role: 'user',
content: 'Polish these GitHub release notes for IsotopeAI v${VERSION} into a clean, friendly, emoji-enhanced format for students. Keep it concise (max 300 words). Original:\n' + excerpt
}]
};
fs.writeFileSync('/tmp/ai_payload.json', JSON.stringify(payload));
"
RESPONSE=$(curl -s -X POST "https://api.yepapi.com/v1/ai/chat" \
-H "x-api-key: $YEPAPI_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/ai_payload.json)
AI_NOTES=$(echo "$RESPONSE" | node -e "
let d='';
process.stdin.on('data', c => d += c);
process.stdin.on('end', () => {
try {
const p = JSON.parse(d);
console.log(p.data?.message?.content || '');
} catch {
console.log('');
}
});
")
if [ -n "$AI_NOTES" ]; then
echo "$AI_NOTES" > /tmp/ai_notes.txt
{
echo "notes<<CHANGELOG_EOF"
cat /tmp/ai_notes.txt
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
else
echo "AI returned empty β€” falling back to raw changelog"
{
echo "notes<<CHANGELOG_EOF"
cat /tmp/changelog_notes.txt
echo "CHANGELOG_EOF"
} >> "$GITHUB_OUTPUT"
fi
- name: πŸ“¦ Package release archive
run: |
VERSION="${{ steps.tag.outputs.version }}"
zip -qr "isotope-v${VERSION}.zip" . \
--exclude ".git/*" \
--exclude "node_modules/*" \
--exclude ".env" \
--exclude ".isotope/*" \
--exclude "artifacts/*" \
--exclude "*.log" \
--exclude ".github/*" \
--exclude "screenshots/*"
echo "Archive: $(wc -c < isotope-v${VERSION}.zip) bytes"
- name: πŸ”Ž Verify release archive
run: |
set -euo pipefail
VERSION="${{ steps.tag.outputs.version }}"
TMPDIR="$(mktemp -d)"
unzip -q "isotope-v${VERSION}.zip" -d "$TMPDIR"
required=(
server.mjs
package.json
public/sync/backup-normalizer.js
public/sync/local-data-adapter.js
server/backup-manager.mjs
scripts/prove-new-browser-restore.mjs
scripts/repair-user-backup.mjs
scripts/validate-backup-files.mjs
scripts/validate-storage-cleanup.mjs
scripts/verify-supabase-security.mjs
scripts/compare-remote-assets.mjs
sql/backup_manifests.sql
sql/006_security_policy_cleanup.sql
docs/sync-system.md
docs/storage-backup-system.md
docs/supabase-connection-map.md
docs/index.html
docs/install.html
docs/sync.html
docs/admin.html
docs/gallery.html
docs/motion.html
docs/assets/site.css
docs/assets/site.js
docs/logo.svg
README.md
ADMIN.md
.env.example
)
for f in "${required[@]}"; do
test -f "$TMPDIR/$f" || { echo "❌ Missing from release: $f"; exit 1; }
echo "βœ… $f"
done
if [ -f "$TMPDIR/.env" ]; then
echo "❌ .env included in release"
exit 1
fi
if find "$TMPDIR" -maxdepth 1 -name '.env.*' ! -name '.env.example' | grep -q .; then
echo "❌ private .env.* included in release"
exit 1
fi
PORT=3099 \
SUPABASE_URL=https://placeholder.supabase.co \
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYW5vbiJ9.signature \
SESSION_SECRET=release_smoke_test_secret_not_real \
node "$TMPDIR/server.mjs" &
PID=$!
trap 'kill "$PID" 2>/dev/null || true; rm -rf "$TMPDIR"' EXIT
for i in $(seq 1 30); do
STATUS="$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3099/__isotope/ping 2>/dev/null || echo 000)"
if [ "$STATUS" = "200" ]; then
echo "Release smoke HTTP $STATUS"
exit 0
fi
sleep 0.5
done
echo "❌ Release archive server ping did not return 200 (got: $STATUS)"
exit 1
- name: πŸ“ Build release notes
run: |
cat > /tmp/release_notes.md <<'EOF'
## IsotopeAI ${{ steps.tag.outputs.tag }}
${{ steps.ai_notes.outputs.notes || steps.changelog.outputs.notes }}
---
### πŸ“₯ Quick Install
```bash
git clone https://github.com/Suydev/isotope-code.git
cd isotope-code
bash setup.sh
```
### πŸ“‹ What's Changed
See [CHANGELOG.md](https://github.com/Suydev/isotope-code/blob/main/CHANGELOG.md) for the full history.
### ❀️ Support
UPI: `9699393886@fam` Β· [isotopeai.in](https://isotopeai.in)
EOF
- name: 🏷️ Ensure release tag points to this commit
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
TAG="${{ steps.tag.outputs.tag }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f "$TAG" "$GITHUB_SHA"
git push --force origin "refs/tags/$TAG"
- name: πŸŽ‰ Create or update GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
TAG="${{ steps.tag.outputs.tag }}"
VERSION="${{ steps.tag.outputs.version }}"
ARCHIVE="isotope-v${VERSION}.zip"
PRERELEASE_FLAG=()
if printf '%s' "$TAG" | grep -Eq -- '(-beta|-rc)'; then
PRERELEASE_FLAG=(--prerelease)
fi
if gh release view "$TAG" >/dev/null 2>&1; then
gh release edit "$TAG" \
--title "IsotopeAI ${TAG} πŸ§ͺ" \
--notes-file /tmp/release_notes.md \
"${PRERELEASE_FLAG[@]}"
else
gh release create "$TAG" \
--target "$GITHUB_SHA" \
--title "IsotopeAI ${TAG} πŸ§ͺ" \
--notes-file /tmp/release_notes.md \
"${PRERELEASE_FLAG[@]}"
fi
gh release upload "$TAG" "$ARCHIVE" --clobber
echo "βœ… Release ${TAG} published with ${ARCHIVE}"