Initial project commit for Coolify staging
CI / Quality gate (push) Has been cancelled

This commit is contained in:
Andres Resiri
2026-08-03 10:45:41 -04:00
commit 70daeb214a
1199 changed files with 243503 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
# ci.yml
#
# Fast quality gate on every push to main and every pull request.
# Two checks:
# 1. check-raw-siteconversion — no direct SiteConversion queries
# that bypass countedConversionWhere (guards against count drift)
# 2. tsc --noEmit — no TypeScript type errors
#
# No deploy steps. Median run time: ~60 seconds.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
quality:
name: Quality gate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Conversion query guard
run: node scripts/check-raw-siteconversion.mjs
- name: TypeScript typecheck
run: npx tsc --noEmit
+174
View File
@@ -0,0 +1,174 @@
# deploy-lag-monitor.yml
#
# Runs every hour. Compares the HEAD SHA of origin/main against the SHA
# of the current Vercel production deployment. If they differ AND the
# main HEAD commit is more than 1 hour old, opens a GitHub issue (or
# comments on an existing open one) to alert about a stalled deploy.
#
# Required repository secrets (Settings -> Secrets and variables -> Actions):
# VERCEL_TOKEN - Vercel personal access token (vercel.com -> Account Settings -> Tokens)
# VERCEL_PROJECT_ID - Vercel project ID (vercel.com -> Project Settings -> General, "Project ID" field)
#
# To test manually: Actions tab -> "Deploy Lag Monitor" -> "Run workflow"
# To silence a false alert: close the open "deploy-lag-alert" issue and
# the next run will not re-open until a new lag condition is detected.
name: Deploy Lag Monitor
on:
schedule:
- cron: "0 * * * *" # every hour at :00
workflow_dispatch: # manual trigger for testing
permissions:
issues: write
contents: read
jobs:
check-deploy-lag:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Check deploy lag
uses: actions/github-script@v7
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
with:
script: |
const ALERT_LABEL = "deploy-lag-alert";
const LAG_THRESHOLD_MS = 60 * 60 * 1000; // 1 hour
// ── 1. Get HEAD SHA and commit timestamp from the checkout ──────
const { execSync } = require("child_process");
const mainSha = execSync("git rev-parse HEAD").toString().trim();
const mainTimestampStr = execSync("git log -1 --format=%cI HEAD").toString().trim();
const mainCommitMsg = execSync("git log -1 --format=%s HEAD").toString().trim();
const mainTimestamp = new Date(mainTimestampStr);
const commitAgeMs = Date.now() - mainTimestamp.getTime();
core.info(`main HEAD: ${mainSha} (${mainTimestampStr})`);
core.info(`Commit age: ${Math.round(commitAgeMs / 60000)} minutes`);
// ── 2. Get current Vercel production deployment SHA ──────────────
let vercelSha = null;
let vercelDeployedAt = null;
let vercelDeployUrl = null;
if (!process.env.VERCEL_TOKEN || !process.env.VERCEL_PROJECT_ID) {
core.warning("VERCEL_TOKEN or VERCEL_PROJECT_ID secret not set. Skipping check.");
return;
}
try {
const res = await fetch(
`https://api.vercel.com/v6/deployments?projectId=${process.env.VERCEL_PROJECT_ID}&target=production&limit=1&state=READY`,
{ headers: { Authorization: `Bearer ${process.env.VERCEL_TOKEN}` } }
);
if (!res.ok) {
core.warning(`Vercel API returned ${res.status}. Skipping check.`);
return;
}
const data = await res.json();
const latest = data.deployments?.[0];
if (latest) {
vercelSha = latest.meta?.githubCommitSha ?? null;
vercelDeployedAt = latest.createdAt ? new Date(latest.createdAt).toISOString() : "unknown";
vercelDeployUrl = latest.url ? `https://${latest.url}` : null;
}
} catch (err) {
core.warning(`Failed to fetch Vercel deployment: ${err.message}. Skipping check.`);
return;
}
core.info(`Vercel production SHA: ${vercelSha ?? "unknown"}`);
// ── 3. Decide if there is a lag condition ───────────────────────
// Only alert when:
// a) SHAs differ (or Vercel SHA unknown), AND
// b) The main commit is older than LAG_THRESHOLD_MS
// (prevents noise on commits that just landed)
const shasMismatch = vercelSha !== mainSha;
const isOldEnough = commitAgeMs > LAG_THRESHOLD_MS;
if (!shasMismatch) {
core.info("Production is up to date. No action needed.");
return;
}
if (!isOldEnough) {
const minutesOld = Math.round(commitAgeMs / 60000);
core.info(`SHA mismatch but commit is only ${minutesOld}m old. Within grace period.`);
return;
}
core.warning(`Deploy lag detected! main=${mainSha} vercel=${vercelSha ?? "unknown"}`);
// ── 4. Check for an existing open alert issue ───────────────────
const { data: existingIssues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: ALERT_LABEL,
state: "open",
per_page: 1,
});
const hoursOld = Math.round(commitAgeMs / 3_600_000);
const body = [
`**Production deploy lag detected.**`,
``,
`| Field | Value |`,
`|---|---|`,
`| \`main\` HEAD SHA | \`${mainSha}\` |`,
`| Vercel production SHA | \`${vercelSha ?? "unknown"}\` |`,
`| Last commit | ${mainCommitMsg} |`,
`| Commit pushed | ${mainTimestampStr} (~${hoursOld}h ago) |`,
`| Vercel last deploy | ${vercelDeployedAt} |`,
`${vercelDeployUrl ? `| Deploy URL | ${vercelDeployUrl} |` : ""}`,
``,
`**Next steps:**`,
`1. Open the Vercel dashboard and check for a failed or queued build.`,
`2. Check GitHub Settings -> Webhooks -> Vercel webhook -> Recent Deliveries for failures.`,
`3. If the webhook is healthy, trigger a manual redeploy from the Vercel dashboard.`,
``,
`_Close this issue once the deploy is confirmed live._`,
].join("\n");
if (existingIssues.length > 0) {
// Comment on the existing issue instead of opening a duplicate.
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existingIssues[0].number,
body: `**Still lagging** (hourly check at ${new Date().toISOString()})\n\n${body}`,
});
core.info(`Commented on existing alert issue #${existingIssues[0].number}`);
} else {
// Ensure the label exists before creating the issue.
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: ALERT_LABEL,
});
} catch {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: ALERT_LABEL,
color: "e11d48",
description: "Auto-opened by deploy-lag-monitor workflow",
});
}
const { data: newIssue } = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "[ALERT] Production deploy lag detected",
body,
labels: [ALERT_LABEL],
});
core.warning(`Opened alert issue #${newIssue.number}: ${newIssue.html_url}`);
}