johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
// The screenshots without a PDS: what knotless-serve answered for each page,
// recorded once in fixtures.json, and played back by a stand-in for it in
// front of the pages. The pages are drawn as they are by the real app, from
// web/build; only their data is fixed.
//
// node harness/shots/fixtures.mjs record them again, from the world
// `just shots-up` keeps up, or a new
// one: `just shots-record`
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { createServer, request } from 'node:http'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import config from './playwright.config.mjs'
import {
base,
create,
now,
pages,
port,
root,
saved,
serve,
until,
viewers,
zoekt,
} from './world.mjs'
export const fixtures = join(root, 'harness/shots/fixtures.json')
/// What knotless-serve answers itself, rather than handing to the pages.
const answered = (path) => /^\/xrpc\//.test(path) || /^\/[^/]+\/[^/]+\/raw\//.test(path)
/// The headers of an answer worth keeping.
const KEPT = ['content-type', 'cache-control', 'location', 'content-security-policy']
/// knotless-serve's place on `port`, in front of the pages (from `build`,
/// else this checkout's): answering from `recorded`, or, given `from`,
/// asking knotless-serve there and recording what it says. Returns the
/// recording, and how to stop.
export async function front({ recorded = {}, from, build } = {}) {
const drawn = pages(port + 1, base, build)
const answer = async (path) => {
if (!from) return recorded[path]
const res = await fetch(from + path, { redirect: 'manual' })
const body = Buffer.from(await res.arrayBuffer())
const text = body.toString('utf8')
const binary = !Buffer.from(text, 'utf8').equals(body)
const headers = Object.fromEntries(
KEPT.filter((h) => res.headers.has(h)).map((h) => [h, res.headers.get(h)]),
)
return (recorded[path] = {
status: res.status,
headers,
...(binary ? { base64: body.toString('base64') } : { body: text }),
})
}
// Who a page asked as `signedIn(handle)` is: that account, as its
// recorded profile has it, logged in, with nothing unread.
const viewing = async (url, handle) => {
const json = (body) => ({
status: 200,
headers: { 'content-type': 'application/json', 'cache-control': 'no-cache' },
body: JSON.stringify(body),
})
if (url === '/xrpc/dev.knotless.getUnreadCount') return json({ count: 0 })
// What `just look --set` answers instead, when it says.
if (url.startsWith('/xrpc/dev.knotless.listNotifications'))
return (await answer(url).catch(() => null)) ?? json(await heard(handle))
if (url.startsWith('/xrpc/dev.knotless.listSecrets')) {
const recorded = await answer(url).catch(() => null)
return recorded?.status === 200 ? recorded : json(await kept(url))
}
// A repository and a pull or an issue, with what the account has to do
// with it, which the server answers anyone logged in: nothing chosen,
// so it hears of what it takes part in. Of a pull or an issue, its
// author may close it, and the repository's owner close and hide it.
if (
url.startsWith('/xrpc/dev.knotless.getRepo?') ||
url.startsWith('/xrpc/dev.knotless.getThread?')
) {
const recorded = await answer(url).catch(() => null)
if (recorded?.status !== 200) return null
const body = JSON.parse(recorded.body)
if (url.startsWith('/xrpc/dev.knotless.getRepo?'))
return { ...recorded, body: JSON.stringify({ viewer: {}, ...body }) }
const profile = await answer(
`/xrpc/dev.knotless.getProfile?actor=${encodeURIComponent(handle)}`,
)
const { did } = JSON.parse(profile.body)
const owner = new URLSearchParams(url.split('?')[1])
.get('repo')
?.startsWith(`at://${handle}/`)
const author = body.item?.author?.did === did
const viewer = author
? { setState: true, notifiedFor: 'author' }
: owner
? { setState: true, hide: true }
: {}
return { ...recorded, body: JSON.stringify({ viewer, ...body }) }
}
if (url !== '/xrpc/dev.knotless.getViewer') return null
const profile = await answer(
`/xrpc/dev.knotless.getProfile?actor=${encodeURIComponent(handle)}`,
)
if (!profile) return null
const { did, displayName, avatar } = JSON.parse(profile.body)
// Over what was recorded for no one, so what `just look --set` changes holds.
const recorded = await answer(url).catch(() => null)
const base = recorded?.status === 200 ? JSON.parse(recorded.body) : {}
return json({ ...base, actor: { did, handle, displayName, avatar }, login: true })
}
// The secrets an owner keeps for the tally, made from the recording, as
// they are asked for logged in, which the recording never is: two sealed
// to its runner's key, one for main's pushes alone.
const kept = async (url) => {
const runners = JSON.parse(
(
await answer(
`/xrpc/dev.knotless.listRunners?repo=${encodeURIComponent('at://alice.test/dev.knotless.repo/tally')}`,
)
).body,
)
const [{ runner, key }] = runners.keys
const secret = (name, refs, updatedAt) => ({
name,
runner,
...(refs ? { refs: refs.map((pattern) => ({ pattern })) } : {}),
key,
updatedAt,
})
return {
secrets: [
secret('DEPLOY_TOKEN', ['refs/heads/main'], '2026-10-25T17:52:00Z'),
secret('NPM_TOKEN', null, '2026-10-26T09:10:00Z'),
],
}
}
// What `handle` heard of, made from the recording, as no index there
// keeps any for a recorded login: bob.test's doings on alice.test's
// tally, each reason, its checks failing and ejected, and an invitation to bob.test as an organization,
// the newest three not yet seen.
const heard = async (handle) => {
const read = async (path) => JSON.parse((await answer(path)).body)
const owner = await read(`/xrpc/dev.knotless.getProfile?actor=${encodeURIComponent(handle)}`)
const bob = await read('/xrpc/dev.knotless.getProfile?actor=bob.test')
const items = (kind) =>
read(
`/xrpc/dev.knotless.listItems?repo=${encodeURIComponent(`at://${handle}/dev.knotless.repo/tally`)}&kind=${kind}`,
)
const [issue] = (await items('issue')).items
const [pull] = (await items('pull')).items
const author = {
did: bob.did,
handle: bob.handle,
...(bob.avatar ? { avatar: bob.avatar } : {}),
}
const repo = { did: owner.did, handle, name: 'tally' }
const at = (hours) => new Date(Date.parse(now) - hours * 3600_000).toISOString()
const comment = `at://${bob.did}/dev.knotless.comment/3myvwhm3ntion`
const notifications = [
{
uri: comment,
reason: 'mention',
reasonSubject: issue.uri,
title: issue.title,
text: '@alice.test can you look at this one? It fails on **every** file with a BOM.',
isRead: false,
createdAt: at(1),
},
{
uri: `at://${bob.did}/dev.knotless.org/self`,
reason: 'invite',
role: 'member',
isRead: false,
createdAt: at(2),
},
{ uri: pull.uri, reason: 'pull', title: pull.title, isRead: false, createdAt: at(3) },
{
uri: `at://${bob.did}/dev.knotless.comment/3myvwhc0mment`,
reason: 'comment',
reasonSubject: issue.uri,
title: issue.title,
text: 'Same here on 0.4.2.',
isRead: true,
createdAt: at(26),
},
{
uri: `at://${bob.did}/dev.knotless.check/3myvwhch3cks`,
reason: 'checks-failed',
reasonSubject: pull.uri,
title: pull.title,
checks: [
{ workflow: 'test', status: 'failure' },
{ workflow: 'web', status: 'error' },
],
isRead: true,
createdAt: at(30),
},
{
uri: `at://${bob.did}/dev.knotless.queue/3myvwhqu3ue`,
reason: 'ejected',
reasonSubject: pull.uri,
title: pull.title,
checks: [{ workflow: 'shots', status: 'failure' }],
isRead: true,
createdAt: at(40),
},
{ uri: issue.uri, reason: 'mention', title: issue.title, isRead: true, createdAt: at(50) },
{
uri: `at://${bob.did}/dev.knotless.star/3myvwhst4rred`,
reason: 'star',
isRead: true,
createdAt: at(74),
},
{
uri: `at://${bob.did}/dev.knotless.follow/3myvwhf0llow`,
reason: 'follow',
isRead: true,
createdAt: at(98),
},
].map((n) => ({
...n,
author,
...(n.reason === 'follow' || n.reason === 'invite' ? {} : { repo }),
}))
return { notifications, seenAt: at(4) }
}
const server = createServer(async (req, res) => {
if (answered(req.url)) {
const as = req.headers.cookie?.match(/(?:^|;\s*)shots_as=([^;]+)/)?.[1]
// A choice of what to hear of, as the server keeps it, for what one
// signed in clicks to look at.
if (as && req.url === '/xrpc/dev.knotless.putSubscription') {
let input = ''
for await (const chunk of req) input += chunk
res.writeHead(200, { 'content-type': 'application/json' })
res.end(JSON.stringify({ ...JSON.parse(input), createdAt: now }))
return
}
const a =
(as && (await viewing(req.url, decodeURIComponent(as)))) ||
(await answer(req.url).catch(() => null))
if (!a) {
res.writeHead(404, { 'content-type': 'application/json' })
res.end(JSON.stringify({ message: `nothing recorded for ${req.url}` }))
return
}
res.writeHead(a.status, a.headers)
res.end(a.base64 === undefined ? a.body : Buffer.from(a.base64, 'base64'))
return
}
const headers = {
...req.headers,
'x-forwarded-host': req.headers.host,
'x-forwarded-proto': 'http',
}
const on = request(
{ host: '127.0.0.1', port: port + 1, path: req.url, method: req.method, headers },
(up) => {
res.writeHead(up.statusCode, up.headers)
up.pipe(res)
},
)
on.on('error', (e) => {
res.writeHead(502)
res.end(`The pages are not being drawn: ${e.message}`)
})
req.pipe(on)
})
await new Promise((r) => server.listen(port, '127.0.0.1', r))
const kill = () => {
server.close()
server.closeAllConnections()
drawn.kill()
// Gone once both have let go of their ports, for the next to take them.
const exited = drawn.exitCode !== null || drawn.signalCode !== null
return Promise.all([
new Promise((r) => server.once('close', r)),
exited ? null : new Promise((r) => drawn.once('exit', r)),
])
}
try {
await until('the pages', async () => (await fetch(`${base}/start`)).ok)
} catch (e) {
kill()
throw e
}
return { recorded, kill }
}
/// The cookies a page is asked with: the zone the browser has said
/// (views.spec.mjs), and for a view in `viewers`, who it is signed in as,
/// which front() answers getViewer with.
export const cookies = (name) =>
[
`tz=${encodeURIComponent(config.use.timezoneId)}`,
...(viewers[name] ? [signedIn(viewers[name])] : []),
].join('; ')
/// The cookie that signs a page in as `handle`, to front() alone.
export const signedIn = (handle) => `shots_as=${encodeURIComponent(handle)}`
/// The recording in `file`, this checkout's unless given, if there is one.
export const recording = (file = fixtures) =>
existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : null
/// Records every page's answers from `world`, and writes them with the pages.
async function record(world) {
const at = port + 2
const server = await serve(world, { at, alone: true })
const stand = await front({ from: `http://127.0.0.1:${at}` })
try {
// Asked as the browser asks (views.spec.mjs), having said its zone and
// signed in where the view is, so what the pages ask then is what is kept.
for (const [name, path] of Object.entries(world.views)) {
const html = await (await fetch(base + path, { headers: { cookie: cookies(name) } })).text()
// What the browser asks for itself: images, from the raw files and
// the pictures checks made.
for (const [, src] of html.matchAll(/src="(\/[^"]+\/(?:raw|image)\/[^"]+)"/g))
await fetch(base + src)
}
} finally {
stand.kill()
server.kill()
}
const sorted = Object.fromEntries(
Object.entries(stand.recorded).sort(([a], [b]) => (a < b ? -1 : 1)),
)
writeFileSync(fixtures, JSON.stringify({ views: world.views, answers: sorted }, null, 1) + '\n')
return Object.keys(sorted).length
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
if (!zoekt) {
console.error(
'zoekt-webserver and zoekt-git-index are not on PATH: the code views record what they find (see the zoekt step in .knotless/workflows/test.yml)',
)
process.exit(1)
}
const kept = existsSync(saved) ? JSON.parse(readFileSync(saved, 'utf8')) : null
const up =
kept &&
(await fetch(`${kept.net.pds}/xrpc/com.atproto.server.describeServer`).then(
(r) => r.ok,
() => false,
))
const world = up ? kept : await create()
// A new world's server is stopped first: the recording starts its own.
if (!up) world.server.kill()
const n = await record(world)
if (!up) world.stop()
console.log(`recorded ${n} answers for ${Object.keys(world.views).length} pages in ${fixtures}`)
process.exit(0)
}