diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 87194217..ebc230b2 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -31,89 +31,91 @@ jobs: const { execSync } = require('child_process'); const fs = require('fs'); - const USER = process.env.WEBDAV_USER; - const PASS = process.env.WEBDAV_PASS; - const BASE = process.env.WEBDAV_BASE; - const BEFORE = process.env.GITHUB_BEFORE || ''; - const SHA = process.env.GITHUB_SHA || ''; - const vacant = '0'.repeat(40); + (async () => { + const USER = process.env.WEBDAV_USER; + const PASS = process.env.WEBDAV_PASS; + const BASE = process.env.WEBDAV_BASE; + const BEFORE = process.env.GITHUB_BEFORE || ''; + const SHA = process.env.GITHUB_SHA || ''; + const vacant = '0'.repeat(40); - // Simple content-type map (node:20 image has no `mime` package). - const MIME = { - html: 'text/html', htm: 'text/html', js: 'application/javascript', - css: 'text/css', json: 'application/json', txt: 'text/plain', - md: 'text/markdown', xml: 'application/xml', gif: 'image/gif', - jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', - 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', - swf: 'application/x-shockwave-flash', jsx: 'text/javascript', mjs: 'application/javascript', - }; - const typeFor = (f) => { - const e = f.split('.').pop().toLowerCase(); - return MIME[e] || 'application/octet-stream'; - }; + // Simple content-type map (node:20 image has no `mime` package). + const MIME = { + html: 'text/html', htm: 'text/html', js: 'application/javascript', mjs: 'application/javascript', + css: 'text/css', json: 'application/json', txt: 'text/plain', + md: 'text/markdown', xml: 'application/xml', gif: 'image/gif', + jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', + 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', + swf: 'application/x-shockwave-flash', jsx: 'text/javascript', + }; + const typeFor = (f) => { + const e = f.split('.').pop().toLowerCase(); + return MIME[e] || 'application/octet-stream'; + }; - // Which files to deploy? On a normal push, only those changed in the - // pushed range. On the very first deploy (empty before), every file. - let files; - if (BEFORE && BEFORE !== vacant) { - files = execSync(`git diff-tree -r --name-only -z "${BEFORE}" "${SHA}"`, { encoding: 'utf8' }) - .split('\0').filter(Boolean); - } else { - files = execSync('git ls-files -z', { encoding: 'utf8' }) - .split('\0').filter(Boolean); - } - // Never treat CI/config files as site content. - files = files.filter(f => !f.startsWith('.gitea') && !f.startsWith('.github')); + // Which files to deploy? On a normal push, only those changed in the + // pushed range. On the very first deploy (empty before), every file. + let files; + if (BEFORE && BEFORE !== vacant) { + files = execSync(`git diff-tree -r --name-only -z "${BEFORE}" "${SHA}"`, { encoding: 'utf8' }) + .split('\0').filter(Boolean); + } else { + files = execSync('git ls-files -z', { encoding: 'utf8' }) + .split('\0').filter(Boolean); + } + // Never treat CI/config files as site content. + files = files.filter(f => !f.startsWith('.gitea') && !f.startsWith('.github')); - if (files.length === 0) { - console.log('No site files changed; nothing to deploy.'); - process.exit(0); - } - console.log(`Deploying ${files.length} changed file(s):`); - 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 === 0) { + console.log('No site files changed; nothing to deploy.'); + return; + } + console.log(`Deploying ${files.length} changed file(s):`); + for (const f of files.slice(0, 20)) console.log(' ' + f); + if (files.length > 20) console.log(` ... and ${files.length - 20} more`); - const auth = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64'); - const enc = (p) => p.split('/').map(encodeURIComponent).join('/'); - const urlFor = (f) => BASE + '/' + enc(f); + const auth = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64'); + const enc = (p) => p.split('/').map(encodeURIComponent).join('/'); + const urlFor = (f) => BASE + '/' + enc(f); - async function delThenPut(f) { - const url = urlFor(f); - const body = fs.readFileSync(f); - // DELETE first: serving layer caches PUTs, so overwrite without - // 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++]; + async function delThenPut(f) { + const url = urlFor(f); + const body = fs.readFileSync(f); + // DELETE first: serving layer caches PUTs, so overwrite without + // delete silently keeps serving the OLD copy. try { - await delThenPut(f); - } catch (e) { - failures.push(`${f}: ${e.message}`); - console.log(`::warning::failed ${f} -- ${e.message}`); + 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 = []; + 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)); - if (failures.length) { - console.log(`::error::${failures.length}/${files.length} files failed to deploy`); - for (const f of failures.slice(0, 20)) console.log(' ' + f); - process.exit(1); - } - console.log(`Deployed ${files.length} file(s) successfully.`); + await Promise.all(Array.from({ length: N }, worker)); + if (failures.length) { + console.log(`::error::${failures.length}/${files.length} files failed to deploy`); + for (const f of failures.slice(0, 20)) console.log(' ' + f); + process.exit(1); + } + console.log(`Deployed ${files.length} file(s) successfully.`); + })().catch((e) => { console.error(e); process.exit(1); }); NODE \ No newline at end of file