Skip to content

Commit f5df75c

Browse files
Copilotasperpharma
andcommitted
feat: add health-check and sync-check scripts for all commits
- Add npm scripts: sync:check, health, test:brain to package.json - Add scripts/sync-check.js: verifies Supabase REST API, Edge Functions, and Shopify Storefront API connectivity; CI-safe (exits 0 when creds absent) - Add .github/workflows/ci-all-commits.yml: runs lint+typecheck+sync-check on every push to any branch; runs health-check on main after other jobs pass Co-authored-by: asperpharma <252395498+asperpharma@users.noreply.github.com>
1 parent 4a2fe04 commit f5df75c

3 files changed

Lines changed: 326 additions & 1 deletion

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# CI checks for every commit pushed to any branch
2+
# Repo: asperpharma/understand-project
3+
#
4+
# Runs lint, type-check, sync-check, and (on main only) a live health-check
5+
# against the production Lovable deployment.
6+
#
7+
# No secrets are required — sync-check and health-check skip gracefully when
8+
# optional credentials are absent.
9+
#
10+
name: CI — All Commits
11+
12+
on:
13+
push:
14+
branches:
15+
- '**'
16+
pull_request:
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
# ─────────────────────────────────────────────────────────────────────────────
23+
# 1. Code quality: lint + typecheck (runs on every push / PR)
24+
# ─────────────────────────────────────────────────────────────────────────────
25+
lint-and-typecheck:
26+
name: Lint & Typecheck
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: Set up Node.js
32+
uses: actions/setup-node@v4
33+
with:
34+
node-version: '20.x'
35+
cache: 'npm'
36+
37+
- name: Install dependencies
38+
run: npm ci
39+
40+
- name: Lint
41+
run: npm run lint
42+
43+
- name: Typecheck
44+
run: npm run typecheck
45+
46+
# ─────────────────────────────────────────────────────────────────────────────
47+
# 2. Sync check: verify Supabase & Shopify integrations are reachable
48+
# Uses public endpoints only — no secrets required, exits 0 if creds absent
49+
# ─────────────────────────────────────────────────────────────────────────────
50+
sync-check:
51+
name: Sync Check
52+
runs-on: ubuntu-latest
53+
steps:
54+
- uses: actions/checkout@v4
55+
56+
- name: Set up Node.js
57+
uses: actions/setup-node@v4
58+
with:
59+
node-version: '20.x'
60+
cache: 'npm'
61+
62+
- name: Install dependencies
63+
run: npm ci
64+
65+
- name: Run sync check
66+
run: npm run sync:check
67+
env:
68+
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL || 'https://qqceibvalkoytafynwoc.supabase.co' }}
69+
VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_PUBLISHABLE_KEY }}
70+
VITE_SHOPIFY_STORE_DOMAIN: ${{ secrets.VITE_SHOPIFY_STORE_DOMAIN || 'lovable-project-milns.myshopify.com' }}
71+
VITE_SHOPIFY_STOREFRONT_TOKEN: ${{ secrets.VITE_SHOPIFY_STOREFRONT_TOKEN }}
72+
VITE_SHOPIFY_API_VERSION: ${{ secrets.VITE_SHOPIFY_API_VERSION || '2025-07' }}
73+
74+
# ─────────────────────────────────────────────────────────────────────────────
75+
# 3. Live health check: ping the production site (main branch only)
76+
# Runs immediately — does NOT wait for a new Lovable deploy.
77+
# ─────────────────────────────────────────────────────────────────────────────
78+
health-check:
79+
name: Health Check (main)
80+
runs-on: ubuntu-latest
81+
if: github.ref == 'refs/heads/main'
82+
needs: [lint-and-typecheck, sync-check]
83+
steps:
84+
- uses: actions/checkout@v4
85+
86+
- name: Set up Node.js
87+
uses: actions/setup-node@v4
88+
with:
89+
node-version: '20.x'
90+
cache: 'npm'
91+
92+
- name: Install dependencies
93+
run: npm ci
94+
95+
- name: Run health check
96+
run: npm run health
97+
env:
98+
SITE_URL: ${{ secrets.SITE_URL || 'https://asperbeautyshop-com.lovable.app' }}

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
"test:watch": "vitest",
1919
"sync": "tsx scripts/sync-shopify-catalog.ts",
2020
"sync:dry": "tsx scripts/sync-shopify-catalog.ts --dry-run",
21-
"sync:publish": "tsx scripts/sync-shopify-catalog.ts --publish"
21+
"sync:publish": "tsx scripts/sync-shopify-catalog.ts --publish",
22+
"sync:check": "node scripts/sync-check.js",
23+
"health": "node scripts/health-check.js",
24+
"test:brain": "node scripts/test-brain.js"
2225
},
2326
"dependencies": {
2427
"@hcaptcha/react-hcaptcha": "^2.0.2",

scripts/sync-check.js

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Sync check script for Asper Beauty Shop
4+
* Verifies Supabase and Shopify integration connectivity for catalog sync
5+
*
6+
* Usage:
7+
* node scripts/sync-check.js
8+
* npm run sync:check
9+
*
10+
* Exits 0 when all reachable integrations respond correctly, or when
11+
* credentials are not configured (CI-safe — skips gracefully).
12+
* Exits 1 only when a configured integration is unreachable.
13+
*/
14+
15+
import https from 'https';
16+
import http from 'http';
17+
18+
const SUPABASE_URL = process.env.VITE_SUPABASE_URL || 'https://qqceibvalkoytafynwoc.supabase.co';
19+
const SUPABASE_KEY = process.env.VITE_SUPABASE_PUBLISHABLE_KEY || '';
20+
const SHOPIFY_DOMAIN = process.env.VITE_SHOPIFY_STORE_DOMAIN || 'lovable-project-milns.myshopify.com';
21+
const SHOPIFY_TOKEN = process.env.VITE_SHOPIFY_STOREFRONT_TOKEN || '';
22+
const SHOPIFY_API_VERSION = process.env.VITE_SHOPIFY_API_VERSION || '2025-07';
23+
const TIMEOUT_MS = 10000;
24+
25+
/**
26+
* Makes an HTTP GET request and returns response data
27+
*/
28+
function fetchUrl(url, headers = {}) {
29+
return new Promise((resolve, reject) => {
30+
const parsedUrl = new URL(url);
31+
const client = parsedUrl.protocol === 'https:' ? https : http;
32+
33+
const options = {
34+
hostname: parsedUrl.hostname,
35+
port: parsedUrl.port || undefined,
36+
path: parsedUrl.pathname + parsedUrl.search,
37+
method: 'GET',
38+
timeout: TIMEOUT_MS,
39+
headers: {
40+
'User-Agent': 'Asper-Sync-Check/1.0',
41+
...headers,
42+
},
43+
};
44+
45+
const req = client.request(options, (res) => {
46+
let data = '';
47+
res.on('data', (chunk) => { data += chunk; });
48+
res.on('end', () => {
49+
resolve({ statusCode: res.statusCode, body: data });
50+
});
51+
});
52+
53+
req.on('error', reject);
54+
req.on('timeout', () => {
55+
req.destroy();
56+
reject(new Error('Request timeout'));
57+
});
58+
59+
req.end();
60+
});
61+
}
62+
63+
/**
64+
* Check Supabase REST API is reachable
65+
*/
66+
async function checkSupabase() {
67+
console.log('\n🗄️ Checking Supabase Connectivity...');
68+
69+
try {
70+
const url = `${SUPABASE_URL}/rest/v1/`;
71+
const headers = SUPABASE_KEY ? { apikey: SUPABASE_KEY } : {};
72+
const response = await fetchUrl(url, headers);
73+
74+
// 200 = OK with key, 401 = endpoint exists but no key — both mean server is up
75+
const reachable = response.statusCode === 200 || response.statusCode === 401;
76+
console.log(`[${reachable ? '✓' : '✗'}] Supabase REST API`);
77+
console.log(` URL: ${SUPABASE_URL}`);
78+
console.log(` Status: ${response.statusCode}${reachable ? ' (reachable)' : ' (unreachable)'}`);
79+
80+
if (!SUPABASE_KEY) {
81+
console.log(' ℹ️ VITE_SUPABASE_PUBLISHABLE_KEY not set — skipping auth check');
82+
}
83+
84+
return { name: 'Supabase REST API', success: reachable };
85+
} catch (error) {
86+
console.log(`[✗] Supabase REST API`);
87+
console.log(` Error: ${error.message}`);
88+
return { name: 'Supabase REST API', success: false, error: error.message };
89+
}
90+
}
91+
92+
/**
93+
* Check Supabase Edge Functions are reachable (beauty-assistant health endpoint)
94+
*/
95+
async function checkSupabaseEdgeFunctions() {
96+
console.log('\n⚡ Checking Supabase Edge Functions...');
97+
98+
try {
99+
const url = `${SUPABASE_URL}/functions/v1/beauty-assistant`;
100+
const headers = SUPABASE_KEY ? { apikey: SUPABASE_KEY } : {};
101+
const response = await fetchUrl(url, headers);
102+
103+
// 404 means function is not deployed; 400/401/403/500 mean it exists
104+
const deployed = response.statusCode !== 404;
105+
console.log(`[${deployed ? '✓' : '✗'}] Beauty Assistant Edge Function`);
106+
console.log(` URL: ${url}`);
107+
console.log(` Status: ${response.statusCode} ${deployed ? '(deployed)' : '(not found)'}`);
108+
109+
return { name: 'Edge Functions', success: deployed };
110+
} catch (error) {
111+
console.log(`[✗] Edge Functions`);
112+
console.log(` Error: ${error.message}`);
113+
return { name: 'Edge Functions', success: false, error: error.message };
114+
}
115+
}
116+
117+
/**
118+
* Check Shopify Storefront API is reachable
119+
*/
120+
async function checkShopify() {
121+
console.log('\n🛒 Checking Shopify Storefront API...');
122+
123+
if (!SHOPIFY_TOKEN) {
124+
console.log(' ℹ️ VITE_SHOPIFY_STOREFRONT_TOKEN not set — skipping Shopify check');
125+
return { name: 'Shopify Storefront API', success: true, skipped: true };
126+
}
127+
128+
try {
129+
const url = `https://${SHOPIFY_DOMAIN}/api/${SHOPIFY_API_VERSION}/graphql.json`;
130+
// Minimal introspection query to verify API connectivity
131+
const body = JSON.stringify({ query: '{ shop { name } }' });
132+
const response = await new Promise((resolve, reject) => {
133+
const parsedUrl = new URL(url);
134+
const options = {
135+
hostname: parsedUrl.hostname,
136+
path: parsedUrl.pathname,
137+
method: 'POST',
138+
timeout: TIMEOUT_MS,
139+
headers: {
140+
'Content-Type': 'application/json',
141+
'X-Shopify-Storefront-Access-Token': SHOPIFY_TOKEN,
142+
'User-Agent': 'Asper-Sync-Check/1.0',
143+
'Content-Length': Buffer.byteLength(body),
144+
},
145+
};
146+
147+
const req = https.request(options, (res) => {
148+
let data = '';
149+
res.on('data', (chunk) => { data += chunk; });
150+
res.on('end', () => resolve({ statusCode: res.statusCode, body: data }));
151+
});
152+
153+
req.on('error', reject);
154+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
155+
req.write(body);
156+
req.end();
157+
});
158+
159+
const success = response.statusCode === 200;
160+
console.log(`[${success ? '✓' : '✗'}] Shopify Storefront API`);
161+
console.log(` Domain: ${SHOPIFY_DOMAIN}`);
162+
console.log(` Status: ${response.statusCode}`);
163+
164+
if (success) {
165+
try {
166+
const json = JSON.parse(response.body);
167+
if (json.data?.shop?.name) {
168+
console.log(` Shop: ${json.data.shop.name}`);
169+
}
170+
} catch (_) { /* ignore parse errors */ }
171+
}
172+
173+
return { name: 'Shopify Storefront API', success };
174+
} catch (error) {
175+
console.log(`[✗] Shopify Storefront API`);
176+
console.log(` Error: ${error.message}`);
177+
return { name: 'Shopify Storefront API', success: false, error: error.message };
178+
}
179+
}
180+
181+
/**
182+
* Main sync check routine
183+
*/
184+
async function main() {
185+
console.log('🔄 Asper Beauty Shop — Sync Check');
186+
console.log(`🗄️ Supabase: ${SUPABASE_URL}`);
187+
console.log(`🛒 Shopify: ${SHOPIFY_DOMAIN}`);
188+
console.log('='.repeat(60));
189+
190+
const checks = [];
191+
192+
checks.push(await checkSupabase());
193+
checks.push(await checkSupabaseEdgeFunctions());
194+
checks.push(await checkShopify());
195+
196+
// Summary
197+
console.log('\n' + '='.repeat(60));
198+
const skipped = checks.filter((c) => c.skipped).length;
199+
const active = checks.filter((c) => !c.skipped);
200+
const passed = active.filter((c) => c.success).length;
201+
const total = active.length;
202+
const allPassed = passed === total;
203+
204+
console.log(`\n${allPassed ? '✅' : '❌'} Sync Check ${allPassed ? 'PASSED' : 'FAILED'}`);
205+
console.log(` ${passed}/${total} checks passed${skipped > 0 ? `, ${skipped} skipped` : ''}`);
206+
207+
if (!allPassed) {
208+
console.log('\n⚠️ Failed checks:');
209+
active
210+
.filter((c) => !c.success)
211+
.forEach((c) => {
212+
console.log(` - ${c.name}: ${c.error || `HTTP ${c.statusCode}`}`);
213+
});
214+
}
215+
216+
process.exit(allPassed ? 0 : 1);
217+
}
218+
219+
main().catch((error) => {
220+
console.error('Fatal error:', error);
221+
process.exit(1);
222+
});
223+
224+
export { checkSupabase, checkSupabaseEdgeFunctions, checkShopify };

0 commit comments

Comments
 (0)