Compare commits
7 Commits
fa71e00f69
...
v2.3.22-rc
| Author | SHA1 | Date | |
|---|---|---|---|
| 5809291741 | |||
| e263e7f25c | |||
| 1fee592ee7 | |||
| ff73aa7c4e | |||
| 26f6ddc098 | |||
| db48e03cb9 | |||
| 53941cdf01 |
@@ -394,42 +394,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
pnpm store prune
|
pnpm store prune
|
||||||
pnpm install --no-frozen-lockfile
|
pnpm install --no-frozen-lockfile
|
||||||
- name: 📦 Cache APT Packages
|
- name: Setup Chrome
|
||||||
uses: actions/cache@v4
|
id: setup-chrome
|
||||||
|
uses: browser-actions/setup-chrome@v1
|
||||||
with:
|
with:
|
||||||
path: /var/cache/apt/archives
|
install-dependencies: true
|
||||||
key: apt-cache-${{ runner.os }}-${{ runner.arch }}-chromium
|
|
||||||
|
|
||||||
- name: 💾 Cache Chromium
|
|
||||||
id: cache-chromium
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /usr/bin/chromium
|
|
||||||
key: ${{ runner.os }}-chromium-native-${{ hashFiles('package.json') }}
|
|
||||||
|
|
||||||
- name: 🔍 Install Chromium (Native & ARM64)
|
|
||||||
if: steps.cache-chromium.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
rm -f /etc/apt/apt.conf.d/docker-clean
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y gnupg wget ca-certificates
|
|
||||||
OS_ID=$(. /etc/os-release && echo $ID)
|
|
||||||
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
|
||||||
if [ "$OS_ID" = "debian" ]; then
|
|
||||||
apt-get install -y chromium
|
|
||||||
else
|
|
||||||
mkdir -p /etc/apt/keyrings
|
|
||||||
KEY_ID="82BB6851C64F6880"
|
|
||||||
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_ID" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg
|
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list
|
|
||||||
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --allow-downgrades chromium
|
|
||||||
fi
|
|
||||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
|
||||||
[ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ── Minimalist Smoke Test ──────────────────────────────────────────
|
# ── Minimalist Smoke Test ──────────────────────────────────────────
|
||||||
- name: 🌐 Smoke Test (Essential Flow)
|
- name: 🌐 Smoke Test (Essential Flow)
|
||||||
@@ -437,7 +406,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||||
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD || 'klz2026' }}
|
||||||
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
|
PUPPETEER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }}
|
||||||
run: npx tsx scripts/smoke.ts
|
run: npx tsx scripts/smoke.ts
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -45,6 +45,28 @@ export async function sendContactFormAction(formData: FormData) {
|
|||||||
email,
|
email,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 1.5. Simple Fail-Safe Backup to Disk
|
||||||
|
// To ensure leads are never lost if email fails or Gotify is down, we append them to a local JSON Lines file.
|
||||||
|
try {
|
||||||
|
const fs = await import('fs');
|
||||||
|
const path = await import('path');
|
||||||
|
const backupDir = path.join(process.cwd(), '.data');
|
||||||
|
if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
|
||||||
|
|
||||||
|
const backupFile = path.join(backupDir, 'leads-backup.jsonl');
|
||||||
|
const leadData = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
productName,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
fs.appendFileSync(backupFile, JSON.stringify(leadData) + '\\n');
|
||||||
|
logger.info('Successfully saved lead to local backup file', { backupFile });
|
||||||
|
} catch (backupError) {
|
||||||
|
logger.error('Failed to write to local leads backup', { error: String(backupError) });
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Send Emails
|
// 2. Send Emails
|
||||||
logger.info('Sending branded emails', { email, productName });
|
logger.info('Sending branded emails', { email, productName });
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"preinstall": "npx only-allow pnpm"
|
"preinstall": "npx only-allow pnpm"
|
||||||
},
|
},
|
||||||
"version": "2.3.22-rc.7",
|
"version": "2.3.22-rc.11",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@parcel/watcher",
|
"@parcel/watcher",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ async function run() {
|
|||||||
|
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
|
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
|
||||||
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
@@ -33,16 +34,30 @@ async function run() {
|
|||||||
const res = await page.goto(`${targetUrl}${p}`, { waitUntil: 'domcontentloaded' });
|
const res = await page.goto(`${targetUrl}${p}`, { waitUntil: 'domcontentloaded' });
|
||||||
if (res?.status() !== 200) throw new Error(`Page ${p} returned ${res?.status()}`);
|
if (res?.status() !== 200) throw new Error(`Page ${p} returned ${res?.status()}`);
|
||||||
|
|
||||||
// Check for broken images on this page
|
// Check for broken images (using in-page fetch to preserve session and avoid heavy navigation)
|
||||||
const images = await page.$$eval('img', (imgs) => imgs.map((img) => img.src));
|
const images = await page.$$eval('img', (imgs) => imgs.map((img) => img.src).filter(Boolean));
|
||||||
console.log(` Checking ${images.length} images...`);
|
console.log(` Checking ${images.length} images...`);
|
||||||
for (const src of images.slice(0, 5)) {
|
|
||||||
// Check first 5 images per page
|
const brokenImages = await page.evaluate(async (urls) => {
|
||||||
const imgRes = await page.goto(src, { waitUntil: 'domcontentloaded' }).catch(() => null);
|
const broken: string[] = [];
|
||||||
if (imgRes && imgRes.status() >= 400)
|
await Promise.all(
|
||||||
console.warn(` ⚠️ Broken image: ${src} (${imgRes.status()})`);
|
urls.map(async (url) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, { method: 'HEAD' });
|
||||||
|
if (res.status >= 400) broken.push(`${url} (Status: ${res.status})`);
|
||||||
|
} catch (e) {
|
||||||
|
broken.push(`${url} (Fetch failed)`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return broken;
|
||||||
|
}, images);
|
||||||
|
|
||||||
|
if (brokenImages.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Found ${brokenImages.length} broken images on ${p}:\n${brokenImages.join('\n')}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await page.goto(`${targetUrl}${p}`, { waitUntil: 'domcontentloaded' }); // Go back
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Test Contact Form
|
// 3. Test Contact Form
|
||||||
|
|||||||
Reference in New Issue
Block a user