2021-09-13 11:53:41 +03:00
|
|
|
|
import {promises as fs} from 'fs'
|
|
|
|
|
import {fileURLToPath} from 'url'
|
2021-09-14 12:14:10 +03:00
|
|
|
|
import pAll from 'p-all'
|
2021-09-13 11:53:41 +03:00
|
|
|
|
import {globby} from 'globby'
|
|
|
|
|
import {u} from 'unist-builder'
|
|
|
|
|
import {select} from 'hast-util-select'
|
|
|
|
|
import {h, s} from 'hastscript'
|
|
|
|
|
import {rss} from 'xast-util-feed'
|
|
|
|
|
import {toXml} from 'xast-util-to-xml'
|
|
|
|
|
import {VFile} from 'vfile'
|
|
|
|
|
import {unified} from 'unified'
|
|
|
|
|
import rehypeParse from 'rehype-parse'
|
|
|
|
|
import rehypeSanitize, {defaultSchema} from 'rehype-sanitize'
|
|
|
|
|
import rehypeStringify from 'rehype-stringify'
|
|
|
|
|
import captureWebsite from 'capture-website'
|
|
|
|
|
import chromium from 'chrome-aws-lambda'
|
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
|
|
|
|
|
2021-09-26 19:02:09 +03:00
|
|
|
|
main().catch((error) => {
|
2021-09-13 11:53:41 +03:00
|
|
|
|
throw error
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function main() {
|
2021-09-24 13:58:28 +03:00
|
|
|
|
fs.copyFile(
|
|
|
|
|
new URL('404/index.html', config.output),
|
|
|
|
|
new URL('404.html', config.output)
|
|
|
|
|
)
|
2021-09-14 12:14:10 +03:00
|
|
|
|
console.log('✔ `/404/index.html` -> `/404.html`')
|
|
|
|
|
|
2021-09-13 11:53:41 +03:00
|
|
|
|
const css = await fs.readFile(
|
|
|
|
|
new URL('../docs/_asset/index.css', import.meta.url),
|
|
|
|
|
'utf8'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const files = (
|
|
|
|
|
await globby('**/index.nljson', {cwd: fileURLToPath(config.output)})
|
2021-09-26 19:02:09 +03:00
|
|
|
|
).map((d) => new URL(d + '/../index.json', config.output))
|
2021-09-13 11:53:41 +03:00
|
|
|
|
|
|
|
|
|
const allInfo = await Promise.all(
|
2021-09-26 19:02:09 +03:00
|
|
|
|
files.map(async (url) => ({url, info: JSON.parse(await fs.readFile(url))}))
|
2021-09-13 11:53:41 +03:00
|
|
|
|
)
|
|
|
|
|
|
2021-10-21 12:23:33 +03:00
|
|
|
|
const now = new Date()
|
|
|
|
|
|
2021-09-14 12:14:10 +03:00
|
|
|
|
const entries = await pAll(
|
2021-09-13 11:53:41 +03:00
|
|
|
|
[...allInfo]
|
2021-10-21 12:23:33 +03:00
|
|
|
|
// All blog entries that are published in the past.
|
|
|
|
|
.filter(
|
|
|
|
|
(d) =>
|
|
|
|
|
d.info.meta.pathname.startsWith('/blog/') &&
|
|
|
|
|
d.info.meta.pathname !== '/blog/' &&
|
|
|
|
|
d.info.meta.published !== undefined &&
|
|
|
|
|
new Date(d.info.meta.published) < now
|
|
|
|
|
)
|
|
|
|
|
// Sort.
|
2021-09-13 11:53:41 +03:00
|
|
|
|
.sort(
|
|
|
|
|
(a, b) =>
|
|
|
|
|
new Date(b.info.meta.published) - new Date(a.info.meta.published)
|
|
|
|
|
)
|
|
|
|
|
// Ten most recently published articles.
|
|
|
|
|
.slice(0, 10)
|
2021-09-14 12:14:10 +03:00
|
|
|
|
.map(({info, url}) => async () => {
|
2022-03-16 13:21:11 +03:00
|
|
|
|
const buf = await fs.readFile(new URL('index.html', url))
|
2021-09-13 11:53:41 +03:00
|
|
|
|
const file = await unified()
|
|
|
|
|
.use(rehypeParse)
|
2021-09-26 19:02:09 +03:00
|
|
|
|
.use(() => (tree) => ({
|
2021-09-13 11:53:41 +03:00
|
|
|
|
type: 'root',
|
|
|
|
|
children: select('.body', tree).children
|
|
|
|
|
}))
|
2021-10-21 12:23:33 +03:00
|
|
|
|
.use(rehypeSanitize, {
|
|
|
|
|
...defaultSchema,
|
|
|
|
|
attributes: {
|
|
|
|
|
...defaultSchema.attributes,
|
|
|
|
|
code: [
|
|
|
|
|
[
|
|
|
|
|
'className',
|
|
|
|
|
'language-diff',
|
|
|
|
|
'language-html',
|
|
|
|
|
'language-js',
|
|
|
|
|
'language-jsx',
|
|
|
|
|
'language-md',
|
|
|
|
|
'language-mdx',
|
|
|
|
|
'language-sh',
|
|
|
|
|
'language-ts'
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
clobber: []
|
|
|
|
|
})
|
2021-09-13 11:53:41 +03:00
|
|
|
|
.use(rehypeStringify)
|
|
|
|
|
.process(buf)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
title: info.meta.title,
|
|
|
|
|
description: info.meta.description,
|
|
|
|
|
descriptionHtml: file.value,
|
|
|
|
|
author: info.meta.author,
|
|
|
|
|
url: new URL(
|
|
|
|
|
url.href.slice(config.output.href.length - 1) + '/../',
|
|
|
|
|
config.site
|
|
|
|
|
).href,
|
|
|
|
|
modified: info.meta.modified,
|
|
|
|
|
published: info.meta.published
|
|
|
|
|
}
|
2021-09-14 12:14:10 +03:00
|
|
|
|
}),
|
|
|
|
|
{concurrency: 6}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await fs.writeFile(
|
|
|
|
|
new URL('rss.xml', config.output),
|
|
|
|
|
toXml(
|
|
|
|
|
rss(
|
|
|
|
|
{
|
|
|
|
|
title: config.title,
|
|
|
|
|
description: 'MDX updates',
|
|
|
|
|
tags: config.tags,
|
|
|
|
|
author: config.author,
|
|
|
|
|
url: config.site.href,
|
|
|
|
|
lang: 'en',
|
|
|
|
|
feedUrl: new URL('rss.xml', config.site).href
|
|
|
|
|
},
|
|
|
|
|
entries
|
|
|
|
|
)
|
|
|
|
|
) + '\n'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
console.log('✔ `/rss.xml`')
|
|
|
|
|
|
2021-09-14 12:14:10 +03:00
|
|
|
|
await pAll(
|
2021-09-26 19:02:09 +03:00
|
|
|
|
allInfo.map((data) => async () => {
|
2021-09-13 11:53:41 +03:00
|
|
|
|
const {url, info} = data
|
2022-03-16 13:21:11 +03:00
|
|
|
|
const output = new URL('index.png', url)
|
2021-09-13 11:53:41 +03:00
|
|
|
|
let stats
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
stats = await fs.stat(output)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error.code !== 'ENOENT') throw error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don’t regenerate to improve performance.
|
|
|
|
|
if (stats) return
|
|
|
|
|
|
|
|
|
|
const processor = unified().use(rehypeStringify)
|
|
|
|
|
const file = new VFile({path: url})
|
|
|
|
|
const tree = await processor.run(
|
|
|
|
|
u('root', [
|
|
|
|
|
u('doctype'),
|
|
|
|
|
h('html', {lang: 'en'}, [
|
|
|
|
|
h('head', [
|
|
|
|
|
h('meta', {charSet: 'utf8'}),
|
|
|
|
|
h('title', 'Generated image'),
|
|
|
|
|
h('style', css),
|
|
|
|
|
h(
|
|
|
|
|
'style',
|
|
|
|
|
`
|
2021-10-18 12:45:07 +03:00
|
|
|
|
html {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-13 11:53:41 +03:00
|
|
|
|
body {
|
2021-10-18 12:45:07 +03:00
|
|
|
|
/* 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
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 12:45:07 +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)));
|
2021-09-13 11:53:41 +03:00
|
|
|
|
display: flex;
|
2021-10-18 12:45:07 +03:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 12:45:07 +03:00
|
|
|
|
.og-head {
|
|
|
|
|
margin-block-end: calc(2 * (1em + 1ex));
|
2021-09-13 11:53:41 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.og-icon {
|
|
|
|
|
display: block;
|
|
|
|
|
height: calc(1em + 1ex);
|
|
|
|
|
width: auto;
|
2021-10-18 12:45:07 +03:00
|
|
|
|
vertical-align: middle;
|
2021-09-13 11:53:41 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.og-title {
|
2021-10-18 12:45:07 +03:00
|
|
|
|
font-size: 3rem;
|
|
|
|
|
line-height: calc(1em + (1 / 3 * 1ex));
|
|
|
|
|
margin-block: 0;
|
2021-09-13 11:53:41 +03:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.og-description {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 12:45:07 +03:00
|
|
|
|
.og-description-inside {
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-13 11:53:41 +03:00
|
|
|
|
.og-meta {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
margin-top: calc(1em + 1ex);
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.og-right {
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
)
|
|
|
|
|
]),
|
|
|
|
|
h('body', [
|
|
|
|
|
h('.og-root', [
|
2021-10-18 12:45:07 +03:00
|
|
|
|
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'
|
|
|
|
|
})
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]),
|
2021-09-13 11:53:41 +03:00
|
|
|
|
h('h2.og-title', info.meta.title),
|
|
|
|
|
h('.og-description', [
|
2021-10-18 12:45:07 +03:00
|
|
|
|
h('.og-description-inside', [
|
|
|
|
|
info.meta.descriptionHast
|
|
|
|
|
? unified()
|
|
|
|
|
.use(rehypeSanitize, schema)
|
|
|
|
|
.runSync(info.meta.descriptionHast)
|
|
|
|
|
: info.meta.description || info.matter.description
|
|
|
|
|
])
|
2021-09-13 11:53:41 +03:00
|
|
|
|
]),
|
|
|
|
|
h('.og-meta', [
|
|
|
|
|
h('.og-left', [
|
|
|
|
|
h('small', 'By'),
|
|
|
|
|
h('br'),
|
2021-10-11 15:48:12 +03:00
|
|
|
|
h('b', info.meta.author || 'MDX contributors')
|
2021-09-13 11:53:41 +03:00
|
|
|
|
]),
|
2021-10-09 16:30:28 +03:00
|
|
|
|
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
|
|
|
|
])
|
|
|
|
|
])
|
|
|
|
|
])
|
|
|
|
|
])
|
|
|
|
|
]),
|
|
|
|
|
file
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
file.value = processor.stringify(tree)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await fs.unlink(output)
|
|
|
|
|
} catch {}
|
|
|
|
|
|
2022-10-20 10:06:44 +03:00
|
|
|
|
await captureWebsite.file(file.value, fileURLToPath(output), {
|
2021-09-13 11:53:41 +03:00
|
|
|
|
launchOptions: {
|
|
|
|
|
args: chromium.args,
|
|
|
|
|
defaultViewport: chromium.defaultViewport,
|
|
|
|
|
executablePath: await chromium.executablePath
|
|
|
|
|
},
|
|
|
|
|
inputType: 'html',
|
|
|
|
|
// This is doubled in the actual file dimensions.
|
2021-10-18 12:45:07 +03:00
|
|
|
|
width: 1200,
|
|
|
|
|
height: 628
|
2021-09-13 11:53:41 +03:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
console.log('OG image `%s`', info.meta.title)
|
2021-09-14 12:14:10 +03:00
|
|
|
|
}),
|
|
|
|
|
{concurrency: 6}
|
2021-09-13 11:53:41 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
console.log('✔ OG images')
|
|
|
|
|
}
|