Files
Portfolio/scripts/generate-social-preview.mjs

118 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
import { randomUUID } from 'node:crypto';
import { constants } from 'node:fs';
import { mkdir, open, realpath, rename, rm } from 'node:fs/promises';
import { dirname, resolve, sep } from 'node:path';
import { fileURLToPath } from 'node:url';
import sharp from 'sharp';
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const permittedPrivateRoot = resolve(repositoryRoot, 'content/private');
const fail = () => {
throw new Error('SOCIAL-PREVIEW-PATH-001');
};
const canonicalizeForWrite = async (path) => {
const absolute = resolve(path);
let cursor = dirname(absolute);
const suffix = [];
while (true) {
try {
return resolve(await realpath(cursor), ...suffix, absolute.slice(dirname(absolute).length + 1));
} catch (error) {
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) fail();
const parent = dirname(cursor);
if (parent === cursor) fail();
suffix.unshift(cursor.slice(parent.length + 1));
cursor = parent;
}
}
};
const isInside = (path, root) => path === root || path.startsWith(`${root}${sep}`);
const writeRegularFileWithoutFollowingLinks = async (output, bytes) => {
if (typeof constants.O_NOFOLLOW !== 'number') fail();
const destinationFlags = constants.O_WRONLY | constants.O_CREAT | constants.O_NOFOLLOW
| (constants.O_NONBLOCK ?? 0);
let destination;
try {
destination = await open(output, destinationFlags, 0o600);
const stats = await destination.stat();
if (!stats.isFile() || stats.nlink !== 1) fail();
} catch {
fail();
} finally {
await destination?.close();
}
const temporaryPath = `${output}.${randomUUID()}.tmp`;
let temporary;
let installed = false;
try {
temporary = await open(
temporaryPath,
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
0o600,
);
const stats = await temporary.stat();
if (!stats.isFile() || stats.nlink !== 1) fail();
await temporary.writeFile(bytes);
await temporary.sync();
await temporary.close();
temporary = undefined;
await rename(temporaryPath, output);
installed = true;
} catch {
fail();
} finally {
await temporary?.close();
if (!installed) await rm(temporaryPath, { force: true });
}
};
export const generateSocialPreview = async (outputPath) => {
if (typeof outputPath !== 'string' || outputPath.trim() === '') fail();
const output = await canonicalizeForWrite(outputPath);
const privateRoot = await canonicalizeForWrite(permittedPrivateRoot);
if (!isInside(output, privateRoot) && !isInside(output, '/tmp')) fail();
const svg = Buffer.from(`
<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
<rect width="1200" height="630" fill="#070910"/>
<rect y="356" width="1200" height="274" fill="#0b1019"/>
<path d="M0 356H1200" stroke="#1b3246" stroke-width="2"/>
<circle cx="880" cy="174" r="78" fill="#152638"/>
<path d="M474 630L586 356H642L780 630Z" fill="#101b28"/>
<path d="M544 630L606 356" stroke="#35c8d0" stroke-width="7" opacity="0.9"/>
<path d="M701 630L623 356" stroke="#e3a44a" stroke-width="7" opacity="0.88"/>
<path d="M602 566L613 522M614 490L619 458M620 430L623 405" stroke="#dfe9ef" stroke-width="5" stroke-linecap="round" opacity="0.72"/>
<text x="92" y="183" font-family="DejaVu Sans, sans-serif" font-size="66" font-weight="700" fill="#f1f5f7">Luca Pîslaru</text>
<text x="96" y="245" font-family="DejaVu Sans, sans-serif" font-size="29" font-weight="500" letter-spacing="0.6" fill="#9fc8d1">System Administrator &amp; Full-Stack Developer</text>
<path d="M96 286H342" stroke="#35c8d0" stroke-width="4"/>
<path d="M350 286H430" stroke="#e3a44a" stroke-width="4"/>
</svg>
`);
const bytes = await sharp(svg, { density: 72 })
.flatten({ background: '#070910' })
.png({ compressionLevel: 9, adaptiveFiltering: false, palette: false })
.toBuffer();
await mkdir(dirname(output), { recursive: true });
await writeRegularFileWithoutFollowingLinks(output, bytes);
return output;
};
const parseOutput = (argv) => {
if (argv.length !== 2 || argv[0] !== '--output') fail();
return argv[1];
};
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
generateSocialPreview(parseOutput(process.argv.slice(2))).catch((error) => {
process.stderr.write(`${error instanceof Error ? error.message : 'SOCIAL-PREVIEW-001'}\n`);
process.exitCode = 1;
});
}