Skip to content

Clone and Push

Clone and Push #13

name: Clone and Push
on:
workflow_dispatch:
inputs:
job_id:
description: "Import job id (for explorer status)"
required: false
git_url:
description: "Git repository URL to clone"
required: true
org:
description: "PowerSync org id"
required: true
repo:
description: "PowerSync repo id"
required: true
jobs:
clone-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install pnpm
run: corepack enable && corepack prepare [email protected] --activate
- name: Cache pnpm store
id: pnpm-cache
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-${{ runner.os }}-
- name: Install dependencies (workspace)
run: pnpm install --no-frozen-lockfile
- name: Build shared core
run: pnpm --filter @powersync-community/powergit-core build
- name: Build remote-helper
run: pnpm --filter @powersync-community/powergit-remote-helper build
- name: Add remote-helper to PATH
run: |
echo "$PWD/node_modules/.bin" >> "$GITHUB_PATH"
echo "$PWD/packages/remote-helper/node_modules/.bin" >> "$GITHUB_PATH"
ln -sf "$PWD/packages/remote-helper/dist/remote-helper/src/bin.js" "$PWD/packages/remote-helper/dist/remote-helper/src/git-remote-powergit"
chmod +x "$PWD/packages/remote-helper/dist/remote-helper/src/git-remote-powergit"
echo "$PWD/packages/remote-helper/dist/remote-helper/src" >> "$GITHUB_PATH"
- name: Verify remote-helper is available
run: |
if ! command -v git-remote-powergit >/dev/null 2>&1; then
echo "git-remote-powergit is missing from PATH. Contents of node_modules/.bin:"
ls -l "$PWD/node_modules/.bin" || true
ls -l "$PWD/packages/remote-helper/node_modules/.bin" || true
ls -l "$PWD/packages/remote-helper/dist/remote-helper/src" || true
exit 1
fi
git remote -h | head -n 5
- name: Start Powergit daemon
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL || secrets.POWERSYNC_SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY || secrets.POWERSYNC_SUPABASE_ANON_KEY }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY || secrets.POWERSYNC_SUPABASE_SERVICE_ROLE_KEY }}
SUPABASE_JWT_SECRET: ${{ secrets.SUPABASE_JWT_SECRET || secrets.POWERSYNC_SUPABASE_JWT_SECRET }}
POWERGIT_EMAIL: ${{ secrets.POWERGIT_EMAIL }}
POWERGIT_PASSWORD: ${{ secrets.POWERGIT_PASSWORD }}
POWERSYNC_DAEMON_PORT: 5030
POWERSYNC_SUPABASE_ONLY: "true"
run: |
# Fail fast on missing required secrets
if [ -z "$SUPABASE_URL" ]; then echo "Missing SUPABASE_URL secret" && exit 1; fi
if [ -z "$SUPABASE_ANON_KEY" ]; then echo "Missing SUPABASE_ANON_KEY secret" && exit 1; fi
if [ -z "$POWERGIT_EMAIL" ]; then echo "Missing POWERGIT_EMAIL secret" && exit 1; fi
if [ -z "$POWERGIT_PASSWORD" ]; then echo "Missing POWERGIT_PASSWORD secret" && exit 1; fi
nohup pnpm --filter @powersync-community/powergit-daemon start -- --port ${POWERSYNC_DAEMON_PORT:-5030} > daemon.log 2>&1 &
echo $! > daemon.pid
for i in $(seq 1 30); do
if curl -sf "http://127.0.0.1:${POWERSYNC_DAEMON_PORT:-5030}/health" >/dev/null; then
echo "Daemon is healthy"
break
fi
if [ "$i" -eq 30 ]; then
echo "Daemon did not become healthy. Logs:" && cat daemon.log && exit 1
fi
sleep 1
done
# Verify auth status
for i in $(seq 1 20); do
STATUS_JSON=$(curl -sf "http://127.0.0.1:${POWERSYNC_DAEMON_PORT:-5030}/auth/status" || true)
STATUS=$(echo "$STATUS_JSON" | jq -r '.status // empty')
echo "Auth status attempt $i: ${STATUS_JSON:-<empty>}"
if [ "$STATUS" = "ready" ]; then
break
fi
if [ "$i" -eq 20 ]; then
echo "Daemon did not authenticate. Full log:" && cat daemon.log && exit 1
fi
sleep 1
done
- name: Record import job running
if: ${{ github.event.inputs.job_id != '' }}
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL || secrets.POWERSYNC_SUPABASE_URL }}
SUPABASE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY || secrets.POWERSYNC_SUPABASE_SERVICE_ROLE_KEY || secrets.SUPABASE_ANON_KEY || secrets.POWERSYNC_SUPABASE_ANON_KEY }}
JOB_ID: ${{ github.event.inputs.job_id }}
ORG_ID: ${{ github.event.inputs.org }}
REPO_ID: ${{ github.event.inputs.repo }}
REPO_URL: ${{ github.event.inputs.git_url }}
WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
node - <<'EOF'
const { createClient } = require('@supabase/supabase-js')
const required = ['SUPABASE_URL','SUPABASE_KEY','JOB_ID','ORG_ID','REPO_ID','REPO_URL']
for (const k of required) {
if (!process.env[k] || !process.env[k].trim()) throw new Error(`Missing env ${k}`)
}
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
const now = new Date().toISOString()
const payload = {
id: process.env.JOB_ID,
org_id: process.env.ORG_ID,
repo_id: process.env.REPO_ID,
repo_url: process.env.REPO_URL,
status: 'running',
created_at: now,
updated_at: now,
source: 'actions',
workflow_url: process.env.WORKFLOW_URL ?? null,
}
;(async () => {
const { error } = await supabase.from('import_jobs').upsert(payload, { onConflict: 'id' })
if (error) throw error
})().catch((err) => {
console.error('Failed to record import job running', err)
process.exit(1)
})
EOF
- name: Clone target repo
run: |
git clone "${{ github.event.inputs.git_url }}" workdir
cd workdir
git rev-parse HEAD
- name: Push
env:
POWERSYNC_ORG: ${{ github.event.inputs.org }}
POWERSYNC_REPO: ${{ github.event.inputs.repo }}
POWERSYNC_REPO_URL: ${{ github.event.inputs.git_url }}
POWERSYNC_DAEMON_URL: http://127.0.0.1:5030
POWERSYNC_DAEMON_AUTOSTART: "false"
run: |
cd workdir
git config user.email "[email protected]"
git config user.name "GitHub Actions"
git remote add powersync "powergit::/$POWERSYNC_ORG/$POWERSYNC_REPO"
git push powersync --all
git push powersync --tags || true
- name: Show daemon repo summary
run: |
echo "Daemon /auth/status:" && curl -sf http://127.0.0.1:5030/auth/status || true
echo ""
echo "Daemon repo summary:" && curl -sf "http://127.0.0.1:5030/orgs/${{ github.event.inputs.org }}/repos/${{ github.event.inputs.repo }}/summary" || true
echo ""
echo "Daemon log (tail):"
tail -n 200 daemon.log || true
- name: Verify Supabase replication (fail if missing)
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
SUPABASE_EMAIL: ${{ secrets.POWERGIT_EMAIL }}
SUPABASE_PASSWORD: ${{ secrets.POWERGIT_PASSWORD }}
ORG_ID: ${{ github.event.inputs.org }}
REPO_ID: ${{ github.event.inputs.repo }}
run: |
node - <<'EOF'
const { createClient } = require('@supabase/supabase-js');
const required = ['SUPABASE_URL','SUPABASE_ANON_KEY','SUPABASE_EMAIL','SUPABASE_PASSWORD','ORG_ID','REPO_ID'];
for (const key of required) {
if (!process.env[key] || !process.env[key].trim()) {
throw new Error(`Missing env ${key} for Supabase verification`);
}
}
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);
const deadline = Date.now() + 120_000; // wait up to 2 minutes for writer to catch up
const pollDelayMs = 5_000;
async function main() {
const { error: loginError } = await supabase.auth.signInWithPassword({
email: process.env.SUPABASE_EMAIL,
password: process.env.SUPABASE_PASSWORD,
});
if (loginError) throw new Error(`Supabase login failed: ${loginError.message}`);
const tables = ['refs','commits','file_changes','objects'];
let lastCounts = {};
while (Date.now() < deadline) {
const counts = {};
for (const table of tables) {
const { count, error } = await supabase
.from(table)
.select('id', { count: 'exact', head: true })
.eq('org_id', process.env.ORG_ID)
.eq('repo_id', process.env.REPO_ID);
if (error) throw new Error(`Count query failed for ${table}: ${error.message}`);
counts[table] = count ?? 0;
}
lastCounts = counts;
console.log('Supabase counts:', counts);
if ((counts.refs ?? 0) > 0 && (counts.commits ?? 0) > 0) {
return;
}
await new Promise((r) => setTimeout(r, pollDelayMs));
}
throw new Error(
`Supabase replication incomplete after waiting: refs=${lastCounts.refs ?? 0}, commits=${lastCounts.commits ?? 0}`,
);
}
main().catch((err) => {
console.error(String(err?.message || err));
process.exit(1);
});
EOF
- name: Record import job result
if: ${{ always() && github.event.inputs.job_id != '' }}
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL || secrets.POWERSYNC_SUPABASE_URL }}
SUPABASE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY || secrets.POWERSYNC_SUPABASE_SERVICE_ROLE_KEY || secrets.SUPABASE_ANON_KEY || secrets.POWERSYNC_SUPABASE_ANON_KEY }}
JOB_ID: ${{ github.event.inputs.job_id }}
WORKFLOW_STATUS: ${{ job.status }}
run: |
node - <<'EOF'
const { createClient } = require('@supabase/supabase-js')
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
const now = new Date().toISOString()
const status = process.env.WORKFLOW_STATUS === 'success' ? 'success' : 'error'
const update = {
status,
updated_at: now,
error: status === 'error' ? 'GitHub Actions import failed' : null,
}
;(async () => {
const { error } = await supabase.from('import_jobs').update(update).eq('id', process.env.JOB_ID)
if (error) throw error
})().catch((err) => {
console.error('Failed to record import job result', err)
process.exit(1)
})
EOF