Deploy script: wrap in async IIFE (top-level await invalid in CommonJS)
Some checks failed
Deploy to WebDAV (/BLB) / deploy (push) Failing after 43s

This commit is contained in:
hermes-explorigin 2026-09-05 03:05:14 +00:00
parent 096c527629
commit 1bbdf05a76

View File

@ -31,89 +31,91 @@ jobs:
const { execSync } = require('child_process'); const { execSync } = require('child_process');
const fs = require('fs'); const fs = require('fs');
const USER = process.env.WEBDAV_USER; (async () => {
const PASS = process.env.WEBDAV_PASS; const USER = process.env.WEBDAV_USER;
const BASE = process.env.WEBDAV_BASE; const PASS = process.env.WEBDAV_PASS;
const BEFORE = process.env.GITHUB_BEFORE || ''; const BASE = process.env.WEBDAV_BASE;
const SHA = process.env.GITHUB_SHA || ''; const BEFORE = process.env.GITHUB_BEFORE || '';
const vacant = '0'.repeat(40); const SHA = process.env.GITHUB_SHA || '';
const vacant = '0'.repeat(40);
// Simple content-type map (node:20 image has no `mime` package). // Simple content-type map (node:20 image has no `mime` package).
const MIME = { const MIME = {
html: 'text/html', htm: 'text/html', js: 'application/javascript', html: 'text/html', htm: 'text/html', js: 'application/javascript', mjs: 'application/javascript',
css: 'text/css', json: 'application/json', txt: 'text/plain', css: 'text/css', json: 'application/json', txt: 'text/plain',
md: 'text/markdown', xml: 'application/xml', gif: 'image/gif', md: 'text/markdown', xml: 'application/xml', gif: 'image/gif',
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
ico: 'image/x-icon', svg: 'image/svg+xml', pdf: 'application/pdf', ico: 'image/x-icon', svg: 'image/svg+xml', pdf: 'application/pdf',
mid: 'audio/midi', mp3: 'audio/mpeg', wav: 'audio/x-wav', rm: 'application/vnd.rn-realmedia', mid: 'audio/midi', mp3: 'audio/mpeg', wav: 'audio/x-wav', rm: 'application/vnd.rn-realmedia',
swf: 'application/x-shockwave-flash', jsx: 'text/javascript', mjs: 'application/javascript', swf: 'application/x-shockwave-flash', jsx: 'text/javascript',
}; };
const typeFor = (f) => { const typeFor = (f) => {
const e = f.split('.').pop().toLowerCase(); const e = f.split('.').pop().toLowerCase();
return MIME[e] || 'application/octet-stream'; return MIME[e] || 'application/octet-stream';
}; };
// Which files to deploy? On a normal push, only those changed in the // Which files to deploy? On a normal push, only those changed in the
// pushed range. On the very first deploy (empty before), every file. // pushed range. On the very first deploy (empty before), every file.
let files; let files;
if (BEFORE && BEFORE !== vacant) { if (BEFORE && BEFORE !== vacant) {
files = execSync(`git diff-tree -r --name-only -z "${BEFORE}" "${SHA}"`, { encoding: 'utf8' }) files = execSync(`git diff-tree -r --name-only -z "${BEFORE}" "${SHA}"`, { encoding: 'utf8' })
.split('\0').filter(Boolean); .split('\0').filter(Boolean);
} else { } else {
files = execSync('git ls-files -z', { encoding: 'utf8' }) files = execSync('git ls-files -z', { encoding: 'utf8' })
.split('\0').filter(Boolean); .split('\0').filter(Boolean);
} }
// Never treat CI/config files as site content. // Never treat CI/config files as site content.
files = files.filter(f => !f.startsWith('.gitea') && !f.startsWith('.github')); files = files.filter(f => !f.startsWith('.gitea') && !f.startsWith('.github'));
if (files.length === 0) { if (files.length === 0) {
console.log('No site files changed; nothing to deploy.'); console.log('No site files changed; nothing to deploy.');
process.exit(0); return;
} }
console.log(`Deploying ${files.length} changed file(s):`); console.log(`Deploying ${files.length} changed file(s):`);
for (const f of files.slice(0, 20)) console.log(' ' + f); for (const f of files.slice(0, 20)) console.log(' ' + f);
if (files.length > 20) console.log(` ... and ${files.length - 20} more`); if (files.length > 20) console.log(` ... and ${files.length - 20} more`);
const auth = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64'); const auth = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64');
const enc = (p) => p.split('/').map(encodeURIComponent).join('/'); const enc = (p) => p.split('/').map(encodeURIComponent).join('/');
const urlFor = (f) => BASE + '/' + enc(f); const urlFor = (f) => BASE + '/' + enc(f);
async function delThenPut(f) { async function delThenPut(f) {
const url = urlFor(f); const url = urlFor(f);
const body = fs.readFileSync(f); const body = fs.readFileSync(f);
// DELETE first: serving layer caches PUTs, so overwrite without // DELETE first: serving layer caches PUTs, so overwrite without
// delete silently keeps serving the OLD copy. // delete silently keeps serving the OLD copy.
try {
await fetch(url, { method: 'DELETE', headers: { Authorization: auth } });
} catch (e) { /* file may not exist yet -- fine */ }
const r = await fetch(url, {
method: 'PUT',
headers: { Authorization: auth, 'Content-Type': typeFor(f) },
body,
});
if (!r.ok) throw new Error(`PUT ${url} -> ${r.status} ${r.statusText}`);
}
// Small worker pool so large first deploys don't crush the server.
const N = 8;
let i = 0;
const failures = [];
const worker = async () => {
while (i < files.length) {
const f = files[i++];
try { try {
await delThenPut(f); await fetch(url, { method: 'DELETE', headers: { Authorization: auth } });
} catch (e) { } catch (e) { /* file may not exist yet -- fine */ }
failures.push(`${f}: ${e.message}`); const r = await fetch(url, {
console.log(`::warning::failed ${f} -- ${e.message}`); method: 'PUT',
headers: { Authorization: auth, 'Content-Type': typeFor(f) },
body,
});
if (!r.ok) throw new Error(`PUT ${url} -> ${r.status} ${r.statusText}`);
}
// Small worker pool so large first deploys don't crush the server.
const N = 8;
let i = 0;
const failures = [];
async function worker() {
while (i < files.length) {
const f = files[i++];
try {
await delThenPut(f);
} catch (e) {
failures.push(`${f}: ${e.message}`);
console.log(`::warning::failed ${f} -- ${e.message}`);
}
} }
} }
}; await Promise.all(Array.from({ length: N }, worker));
await Promise.all(Array.from({ length: N }, worker)); if (failures.length) {
if (failures.length) { console.log(`::error::${failures.length}/${files.length} files failed to deploy`);
console.log(`::error::${failures.length}/${files.length} files failed to deploy`); for (const f of failures.slice(0, 20)) console.log(' ' + f);
for (const f of failures.slice(0, 20)) console.log(' ' + f); process.exit(1);
process.exit(1); }
} console.log(`Deployed ${files.length} file(s) successfully.`);
console.log(`Deployed ${files.length} file(s) successfully.`); })().catch((e) => { console.error(e); process.exit(1); });
NODE NODE