2023-09-29 21:57:34 +03:00
|
|
|
|
/**
|
2024-06-26 13:35:50 +03:00
|
|
|
|
* @import {Stats} from 'node:fs'
|
|
|
|
|
* @import {DataMapMatter, DataMapMeta} from 'vfile'
|
|
|
|
|
* @import {Entry} from 'xast-util-feed'
|
2023-10-14 17:07:54 +03:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef Info
|
2023-10-18 15:38:06 +03:00
|
|
|
|
* Info.
|
2024-06-26 13:35:50 +03:00
|
|
|
|
* @property {Readonly<DataMapMeta>} meta
|
2023-10-18 15:38:06 +03:00
|
|
|
|
* Meta.
|
2024-06-26 13:35:50 +03:00
|
|
|
|
* @property {Readonly<DataMapMatter>} matter
|
2023-10-18 15:38:06 +03:00
|
|
|
|
* Matter.
|
|
|
|
|
* @property {boolean | undefined} [navExclude=false]
|
|
|
|
|
* Whether to exclude from the navigation (default: `false`).
|
|
|
|
|
* @property {number | undefined} [navSortSelf=0]
|
|
|
|
|
* Self sort order (default: `0`).
|
2023-09-29 21:57:34 +03:00
|
|
|
|
*/
|
|
|
|
|
|
2023-10-16 13:04:24 +03:00
|
|
|
|
import assert from 'node:assert'
|
2023-10-18 15:38:06 +03:00
|
|
|
|
import fs from 'node:fs/promises'
|
2023-10-16 18:43:31 +03:00
|
|
|
|
import process from 'node:process'
|
2023-10-16 13:04:24 +03:00
|
|
|
|
import {fileURLToPath} from 'node:url'
|
2023-10-18 15:38:06 +03:00
|
|
|
|
import chromium from '@sparticuz/chromium'
|
2021-09-13 11:53:41 +03:00
|
|
|
|
import {globby} from 'globby'
|
2023-10-18 17:32:48 +03:00
|
|
|
|
import {fromHtml} from 'hast-util-from-html'
|
|
|
|
|
import {sanitize, defaultSchema} from 'hast-util-sanitize'
|
2021-09-13 11:53:41 +03:00
|
|
|
|
import {select} from 'hast-util-select'
|
2023-10-18 17:32:48 +03:00
|
|
|
|
import {toHtml} from 'hast-util-to-html'
|
2021-09-13 11:53:41 +03:00
|
|
|
|
import {h, s} from 'hastscript'
|
2023-10-18 15:38:06 +03:00
|
|
|
|
import pAll from 'p-all'
|
|
|
|
|
import puppeteer from 'puppeteer'
|
|
|
|
|
import {rss} from 'xast-util-feed'
|
|
|
|
|
import {toXml} from 'xast-util-to-xml'
|
2021-09-26 19:02:09 +03:00
|
|
|
|
import {config} from '../docs/_config.js'
|
2021-10-06 21:00:21 +03:00
|
|
|
|
import {schema} from './schema-description.js'
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2021-10-09 16:30:28 +03:00
|
|
|
|
const dateTimeFormat = new Intl.DateTimeFormat('en', {dateStyle: 'long'})
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
fs.copyFile(
|
|
|
|
|
new URL('404/index.html', config.output),
|
|
|
|
|
new URL('404.html', config.output)
|
|
|
|
|
)
|
|
|
|
|
console.log('✔ `/404/index.html` -> `/404.html`')
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
const css = await fs.readFile(
|
|
|
|
|
new URL('../docs/_asset/index.css', import.meta.url),
|
|
|
|
|
'utf8'
|
|
|
|
|
)
|
2021-09-14 12:14:10 +03:00
|
|
|
|
|
2023-10-16 13:04:24 +03:00
|
|
|
|
const filePaths = await globby('**/index.json', {
|
|
|
|
|
cwd: fileURLToPath(config.output)
|
|
|
|
|
})
|
2023-10-18 15:38:06 +03:00
|
|
|
|
const files = filePaths.map(function (d) {
|
2023-10-14 17:07:54 +03:00
|
|
|
|
return new URL(d, config.output)
|
|
|
|
|
})
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
const allInfo = await Promise.all(
|
2023-10-18 15:38:06 +03:00
|
|
|
|
files.map(async function (url) {
|
2023-10-14 17:07:54 +03:00
|
|
|
|
/** @type {Info} */
|
|
|
|
|
const info = JSON.parse(String(await fs.readFile(url)))
|
2023-10-18 15:38:06 +03:00
|
|
|
|
return {info, url}
|
2023-09-29 21:57:34 +03:00
|
|
|
|
})
|
2023-10-14 17:07:54 +03:00
|
|
|
|
)
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
// RSS feed.
|
|
|
|
|
const now = new Date()
|
2021-10-21 12:23:33 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
const entries = await pAll(
|
|
|
|
|
[...allInfo]
|
|
|
|
|
// All blog entries that are published in the past.
|
2023-10-18 15:38:06 +03:00
|
|
|
|
.filter(function (d) {
|
|
|
|
|
return (
|
2023-10-14 17:07:54 +03:00
|
|
|
|
d.info.meta.pathname &&
|
|
|
|
|
d.info.meta.pathname.startsWith('/blog/') &&
|
|
|
|
|
d.info.meta.pathname !== '/blog/' &&
|
|
|
|
|
d.info.meta.published !== null &&
|
|
|
|
|
d.info.meta.published !== undefined &&
|
|
|
|
|
new Date(d.info.meta.published) < now
|
2023-10-18 15:38:06 +03:00
|
|
|
|
)
|
|
|
|
|
})
|
2023-10-14 17:07:54 +03:00
|
|
|
|
// Sort.
|
2023-10-18 15:38:06 +03:00
|
|
|
|
.sort(function (a, b) {
|
2023-10-14 17:07:54 +03:00
|
|
|
|
assert(a.info.meta.published)
|
|
|
|
|
assert(b.info.meta.published)
|
|
|
|
|
return (
|
|
|
|
|
new Date(b.info.meta.published).valueOf() -
|
|
|
|
|
new Date(a.info.meta.published).valueOf()
|
2021-10-21 12:23:33 +03:00
|
|
|
|
)
|
2023-10-14 17:07:54 +03:00
|
|
|
|
})
|
|
|
|
|
// Ten most recently published articles.
|
|
|
|
|
.slice(0, 10)
|
2023-10-18 15:38:06 +03:00
|
|
|
|
.map(function ({info, url}) {
|
2023-10-14 17:07:54 +03:00
|
|
|
|
/**
|
|
|
|
|
* @returns {Promise<Entry>}
|
2023-10-18 15:38:06 +03:00
|
|
|
|
* Entry.
|
2023-10-14 17:07:54 +03:00
|
|
|
|
*/
|
2023-10-18 15:38:06 +03:00
|
|
|
|
return async function () {
|
2023-10-14 17:07:54 +03:00
|
|
|
|
const buf = await fs.readFile(new URL('index.html', url))
|
2023-10-18 17:32:48 +03:00
|
|
|
|
const tree = fromHtml(buf)
|
|
|
|
|
const body = select('.body', tree)
|
|
|
|
|
assert(body)
|
|
|
|
|
const clean = sanitize(body, {
|
|
|
|
|
...defaultSchema,
|
|
|
|
|
attributes: {
|
|
|
|
|
...defaultSchema.attributes,
|
|
|
|
|
code: [
|
|
|
|
|
[
|
|
|
|
|
'className',
|
|
|
|
|
'language-diff',
|
|
|
|
|
'language-html',
|
|
|
|
|
'language-js',
|
|
|
|
|
'language-jsx',
|
|
|
|
|
'language-md',
|
|
|
|
|
'language-mdx',
|
|
|
|
|
'language-sh',
|
|
|
|
|
'language-ts'
|
2023-10-14 17:07:54 +03:00
|
|
|
|
]
|
2023-10-18 17:32:48 +03:00
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
clobber: []
|
|
|
|
|
})
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
return {
|
2023-10-18 15:38:06 +03:00
|
|
|
|
author: info.meta.author,
|
2023-10-14 17:07:54 +03:00
|
|
|
|
description: info.meta.description,
|
2023-10-18 17:32:48 +03:00
|
|
|
|
descriptionHtml: toHtml(clean),
|
2023-10-18 15:38:06 +03:00
|
|
|
|
modified: info.meta.modified,
|
|
|
|
|
published: info.meta.published,
|
|
|
|
|
title: info.meta.title,
|
2023-10-14 17:07:54 +03:00
|
|
|
|
url: new URL(
|
|
|
|
|
url.href.slice(config.output.href.length - 1) + '/../',
|
|
|
|
|
config.site
|
2023-10-18 15:38:06 +03:00
|
|
|
|
).href
|
2021-09-13 11:53:41 +03:00
|
|
|
|
}
|
2023-10-14 17:07:54 +03:00
|
|
|
|
}
|
2023-10-18 15:38:06 +03:00
|
|
|
|
}),
|
2023-10-14 17:07:54 +03:00
|
|
|
|
{concurrency: 6}
|
|
|
|
|
)
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
await fs.writeFile(
|
|
|
|
|
new URL('rss.xml', config.output),
|
|
|
|
|
toXml(
|
|
|
|
|
rss(
|
|
|
|
|
{
|
|
|
|
|
author: config.author,
|
2023-10-18 15:38:06 +03:00
|
|
|
|
description: 'MDX updates',
|
|
|
|
|
feedUrl: new URL('rss.xml', config.site).href,
|
2023-10-14 17:07:54 +03:00
|
|
|
|
lang: 'en',
|
2023-10-18 15:38:06 +03:00
|
|
|
|
tags: config.tags,
|
|
|
|
|
title: config.title,
|
|
|
|
|
url: config.site.href
|
2023-10-14 17:07:54 +03:00
|
|
|
|
},
|
|
|
|
|
entries
|
|
|
|
|
)
|
|
|
|
|
) + '\n'
|
|
|
|
|
)
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
console.log('✔ `/rss.xml`')
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-16 18:43:31 +03:00
|
|
|
|
const browser = await puppeteer.launch(
|
|
|
|
|
process.env.AWS_EXECUTION_ENV
|
|
|
|
|
? {
|
|
|
|
|
// See: <https://github.com/Sparticuz/chromium/issues/85#issuecomment-1527692751>
|
|
|
|
|
args: [...chromium.args, '--disable-gpu'],
|
|
|
|
|
defaultViewport: chromium.defaultViewport,
|
|
|
|
|
executablePath: await chromium.executablePath(),
|
2024-02-10 13:53:22 +03:00
|
|
|
|
headless: true
|
2023-10-16 18:43:31 +03:00
|
|
|
|
}
|
2024-02-10 13:53:22 +03:00
|
|
|
|
: {headless: true}
|
2023-10-16 18:43:31 +03:00
|
|
|
|
)
|
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
await pAll(
|
2023-10-18 15:38:06 +03:00
|
|
|
|
allInfo.map(function (data) {
|
|
|
|
|
return async function () {
|
|
|
|
|
const {url, info} = data
|
|
|
|
|
const output = new URL('index.png', url)
|
|
|
|
|
/** @type {Stats | undefined} */
|
|
|
|
|
let stats
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
stats = await fs.stat(output)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const cause = /** @type {NodeJS.ErrnoException} */ (error)
|
|
|
|
|
if (cause.code !== 'ENOENT') throw cause
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-18 15:38:06 +03:00
|
|
|
|
// Don’t regenerate to improve performance.
|
|
|
|
|
if (stats) return
|
|
|
|
|
|
2023-10-18 17:32:48 +03:00
|
|
|
|
const value = toHtml({
|
|
|
|
|
type: 'root',
|
|
|
|
|
children: [
|
|
|
|
|
{type: 'doctype'},
|
2023-10-18 15:38:06 +03:00
|
|
|
|
h('html', {lang: 'en'}, [
|
|
|
|
|
h('head', [
|
|
|
|
|
h('meta', {charSet: 'utf8'}),
|
|
|
|
|
h('title', 'Generated image'),
|
|
|
|
|
h('style', css),
|
|
|
|
|
h(
|
|
|
|
|
'style',
|
|
|
|
|
`
|
2023-10-14 17:07:54 +03:00
|
|
|
|
html {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
2021-10-18 12:45:07 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
body {
|
|
|
|
|
/* yellow */
|
|
|
|
|
background-image: radial-gradient(
|
|
|
|
|
ellipse at 0% 0%,
|
|
|
|
|
rgb(252 180 45 / 15%) 20%,
|
|
|
|
|
rgb(252 180 45 / 0%) 80%
|
|
|
|
|
),
|
|
|
|
|
/* purple */
|
|
|
|
|
radial-gradient(
|
|
|
|
|
ellipse at 0% 100%,
|
|
|
|
|
rgb(130 80 223 / 15%) 20%,
|
|
|
|
|
rgb(130 80 223 / 0%) 80%
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-root {
|
|
|
|
|
/* Twitter seems to cut 1em off the size in the height,
|
|
|
|
|
* compared to facebook. So this’ll look a bit big on FB
|
|
|
|
|
* but the assumption is that most folks will share on
|
|
|
|
|
* twitter */
|
|
|
|
|
height: calc(100vh - calc(2 * (1em + 1ex)));
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-flow: column;
|
|
|
|
|
margin-block: calc(1 * (1em + 1ex));
|
|
|
|
|
padding-block: calc(1 * (1em + 1ex));
|
|
|
|
|
padding-inline: calc(1 * (1em + 1ex));
|
|
|
|
|
background-color: var(--bg);
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-head {
|
|
|
|
|
margin-block-end: calc(2 * (1em + 1ex));
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-icon {
|
|
|
|
|
display: block;
|
|
|
|
|
height: calc(1em + 1ex);
|
|
|
|
|
width: auto;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-title {
|
|
|
|
|
font-size: 3rem;
|
|
|
|
|
line-height: calc(1em + (1 / 3 * 1ex));
|
|
|
|
|
margin-block: 0;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-description {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-description-inside {
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
2021-10-18 12:45:07 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-meta {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
margin-top: calc(1em + 1ex);
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
.og-right {
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
`
|
2023-10-18 15:38:06 +03:00
|
|
|
|
)
|
|
|
|
|
]),
|
|
|
|
|
h('body', [
|
|
|
|
|
h('.og-root', [
|
|
|
|
|
h('.og-head', [
|
|
|
|
|
s(
|
|
|
|
|
'svg.og-icon.og-icon-mdx',
|
|
|
|
|
{height: 28.5, width: 69, viewBox: '0 0 138 57'},
|
|
|
|
|
[
|
|
|
|
|
s('rect', {
|
|
|
|
|
fill: 'var(--fg)',
|
|
|
|
|
height: 55.5,
|
|
|
|
|
rx: 4.5,
|
|
|
|
|
width: 136.5,
|
|
|
|
|
x: 0.75,
|
|
|
|
|
y: 0.75
|
|
|
|
|
}),
|
|
|
|
|
s(
|
|
|
|
|
'g',
|
|
|
|
|
{fill: 'none', stroke: 'var(--bg)', strokeWidth: 6},
|
|
|
|
|
[
|
|
|
|
|
s('path', {
|
|
|
|
|
d: 'M16.5 44V19L30.25 32.75l14-14v25'
|
|
|
|
|
}),
|
|
|
|
|
s('path', {d: 'M70.5 40V10.75'}),
|
|
|
|
|
s('path', {d: 'M57 27.25L70.5 40.75l13.5-13.5'}),
|
|
|
|
|
s('path', {
|
|
|
|
|
d: 'M122.5 41.24L93.25 12M93.5 41.25L122.75 12'
|
|
|
|
|
})
|
|
|
|
|
]
|
2023-10-14 17:07:54 +03:00
|
|
|
|
)
|
2023-10-18 15:38:06 +03:00
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]),
|
|
|
|
|
h('h2.og-title', info.meta.title),
|
|
|
|
|
h('.og-description', [
|
|
|
|
|
h('.og-description-inside', [
|
|
|
|
|
info.meta.descriptionHast
|
2023-10-18 17:32:48 +03:00
|
|
|
|
? sanitize(info.meta.descriptionHast, schema)
|
2023-10-18 15:38:06 +03:00
|
|
|
|
: info.meta.description || info.matter.description
|
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
h('.og-meta', [
|
|
|
|
|
h('.og-left', [
|
|
|
|
|
h('small', 'By'),
|
|
|
|
|
h('br'),
|
|
|
|
|
h('b', info.meta.author || 'MDX contributors')
|
|
|
|
|
]),
|
|
|
|
|
info.meta.modified
|
|
|
|
|
? h('.og-right', [
|
|
|
|
|
h('small', 'Last modified on'),
|
|
|
|
|
h('br'),
|
|
|
|
|
h(
|
|
|
|
|
'b',
|
|
|
|
|
dateTimeFormat.format(new Date(info.meta.modified))
|
|
|
|
|
)
|
|
|
|
|
])
|
|
|
|
|
: undefined
|
|
|
|
|
])
|
2021-09-13 11:53:41 +03:00
|
|
|
|
])
|
|
|
|
|
])
|
|
|
|
|
])
|
2023-10-18 17:32:48 +03:00
|
|
|
|
]
|
|
|
|
|
})
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-18 15:38:06 +03:00
|
|
|
|
try {
|
|
|
|
|
await fs.unlink(output)
|
|
|
|
|
} catch {}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-18 15:38:06 +03:00
|
|
|
|
const page = await browser.newPage()
|
|
|
|
|
// This is doubled in the actual file dimensions.
|
|
|
|
|
await page.setViewport({deviceScaleFactor: 2, height: 628, width: 1200})
|
|
|
|
|
await page.emulateMediaFeatures([
|
|
|
|
|
{name: 'prefers-color-scheme', value: 'light'}
|
|
|
|
|
])
|
2023-10-18 17:32:48 +03:00
|
|
|
|
await page.setContent(value)
|
2023-10-18 15:38:06 +03:00
|
|
|
|
const screenshot = await page.screenshot()
|
|
|
|
|
await page.close()
|
2023-10-16 18:43:31 +03:00
|
|
|
|
|
2023-10-18 15:38:06 +03:00
|
|
|
|
await fs.writeFile(output, screenshot)
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-18 15:38:06 +03:00
|
|
|
|
console.log('OG image `%s`', info.meta.title)
|
|
|
|
|
}
|
2023-10-16 18:43:31 +03:00
|
|
|
|
})
|
2023-10-14 17:07:54 +03:00
|
|
|
|
)
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
2023-10-16 18:43:31 +03:00
|
|
|
|
await browser.close()
|
|
|
|
|
|
2023-10-14 17:07:54 +03:00
|
|
|
|
console.log('✔ OG images')
|