- Bump /proc/bare_os/syscalls.json to schema 7; socketMsgSurface (sendmsg/recvmsg ENOTSUP) - POSIX profile 1.0.8; align syscalls.example.json, declared profile, compatibility matrix - Bridged SOCK_DGRAM recv/recvfrom with bounded queue + poll/select readiness env knobs - process_table.json schema 7; fdModel.processTableSchema and matrix/dashboard sync - posix-conformance-matrix bareOsSyscallOps includes recvfrom P2P / protocol / replication - Optional Protomux bare-os-app-v1 (BARE_OS_PROTOMUX_APP_CHANNEL) - Replication: guestReplicationPlan (BARE_OS_REPLICATION_PLAN_JSON), sync_window parallelismHint - Blind-relay swarm protomuxBackpressure (BARE_OS_SWARM_PROTOMUX_BACKPRESSURE_COUNT) - Remove bareOsProcBlindPeerRelayHintsStub; strict peer allowlist (BARE_OS_PEER_ALLOWLIST_STRICT) - Kernel multisig gate before kernel.ext.d; Pear updater integrationHints + audit env docs Tooling / tests / hygiene - Default holepunch clone drift in pretest; document BARE_OS_HOLEPUNCH_DRIFT_CHECK=0 - Widen bare holepunch catalog overrides; Mermaid architecture in docs hub + README link - Fix tests: corestore_snapshot proc schema 2, posixXsh schema 2, awk-engine export strip in xcu sweep - pear-updater-bridge: shared integration hints; identity-session import order - Handbook ch.3/7/9 + environment appendix updates; scripts README drift script behavior
262 lines
7.7 KiB
JavaScript
262 lines
7.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* List holepunchto/bare-* repos, resolve npm latest via registry API,
|
|
* merge scripts/bare-catalog-overrides.json, write docs/bare-holepunch-catalog.json
|
|
*
|
|
* node scripts/gen-bare-holepunch-catalog.mjs
|
|
* HOLEPUNCH_MIRROR=/path/to/repos node scripts/gen-bare-holepunch-catalog.mjs
|
|
* node scripts/gen-bare-holepunch-catalog.mjs --check
|
|
*/
|
|
import { readFile, writeFile, readdir, stat, mkdir } from 'node:fs/promises'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
import process from 'node:process'
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
const root = join(__dirname, '..')
|
|
const outPath = join(root, 'docs', 'bare-holepunch-catalog.json')
|
|
const overridesPath = join(__dirname, 'bare-catalog-overrides.json')
|
|
|
|
export function manifestCtxKeyForPackage(name) {
|
|
const legacy = {
|
|
b4a: 'b4a',
|
|
'compact-encoding': 'compactEncoding',
|
|
'safety-catch': 'safetyCatch',
|
|
'hypercore-id-encoding': 'hypercoreIdEncoding',
|
|
protomux: 'protomux',
|
|
'bare-url': 'bareUrl',
|
|
'bare-path': 'barePath',
|
|
'bare-encoding': 'bareEncoding',
|
|
'bare-events': 'bareEvents',
|
|
'bare-fetch': 'fetch',
|
|
'bare-readline': 'bareReadline',
|
|
'bare-crypto': 'bareCrypto'
|
|
}
|
|
if (legacy[name]) return legacy[name]
|
|
if (name.startsWith('bare-')) {
|
|
const parts = name.slice('bare-'.length).split('-')
|
|
const pascal = parts
|
|
.map((p) => (p.length ? p[0].toUpperCase() + p.slice(1) : ''))
|
|
.join('')
|
|
return 'bare' + pascal
|
|
}
|
|
return name.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
|
|
}
|
|
|
|
async function loadOverrides() {
|
|
const raw = await readFile(overridesPath, 'utf8')
|
|
return JSON.parse(raw)
|
|
}
|
|
|
|
async function fetchGithubBareRepos() {
|
|
const all = []
|
|
let url = 'https://api.github.com/orgs/holepunchto/repos?per_page=100'
|
|
for (let i = 0; i < 15 && url; i++) {
|
|
const r = await fetch(url, {
|
|
headers: { 'User-Agent': 'bare-operating-system-catalog' }
|
|
})
|
|
if (!r.ok) throw new Error(`GitHub API ${r.status}: ${await r.text()}`)
|
|
const page = await r.json()
|
|
if (!Array.isArray(page)) throw new Error('GitHub: expected array')
|
|
all.push(...page)
|
|
const link = r.headers.get('link')
|
|
url = null
|
|
if (link) {
|
|
const m = link.match(/<([^>]+)>;\s*rel="next"/)
|
|
if (m) url = m[1]
|
|
}
|
|
}
|
|
return all.filter((x) => x.name?.startsWith('bare-'))
|
|
}
|
|
|
|
async function fetchMirrorBareRepos(mirrorRoot) {
|
|
const names = []
|
|
for (const ent of await readdir(mirrorRoot, { withFileTypes: true })) {
|
|
if (ent.isDirectory() && ent.name.startsWith('bare-')) names.push(ent.name)
|
|
}
|
|
return names.sort().map((name) => ({
|
|
name,
|
|
html_url: `https://github.com/holepunchto/${name}`,
|
|
description: ''
|
|
}))
|
|
}
|
|
|
|
async function npmLatestVersion(packageName) {
|
|
const r = await fetch(
|
|
`https://registry.npmjs.org/${encodeURIComponent(packageName)}`,
|
|
{ headers: { Accept: 'application/json' } }
|
|
)
|
|
if (r.status === 404) return null
|
|
if (!r.ok) throw new Error(`npm ${packageName}: ${r.status}`)
|
|
const j = await r.json()
|
|
const v = j['dist-tags']?.latest
|
|
return typeof v === 'string' ? v : null
|
|
}
|
|
|
|
async function batchNpmVersions(names, concurrency = 16) {
|
|
const m = new Map()
|
|
let i = 0
|
|
async function worker() {
|
|
while (i < names.length) {
|
|
const idx = i++
|
|
const n = names[idx]
|
|
try {
|
|
m.set(n, await npmLatestVersion(n))
|
|
} catch {
|
|
m.set(n, null)
|
|
}
|
|
}
|
|
}
|
|
await Promise.all(Array.from({ length: concurrency }, () => worker()))
|
|
return m
|
|
}
|
|
|
|
async function main() {
|
|
const check = process.argv.includes('--check')
|
|
const mirror = process.env.HOLEPUNCH_MIRROR
|
|
|
|
const overrides = await loadOverrides()
|
|
const unpublished = new Set(overrides.unpublishedRepos || [])
|
|
const excluded = new Map()
|
|
for (const e of overrides.excludedFromBooter || []) {
|
|
if (typeof e === 'string') excluded.set(e, 'excluded_from_booter')
|
|
else if (e && typeof e.repo === 'string')
|
|
excluded.set(e.repo, e.reason || 'excluded_from_booter')
|
|
}
|
|
|
|
let ghRepos
|
|
if (mirror) {
|
|
const st = await stat(mirror).catch(() => null)
|
|
if (!st?.isDirectory()) {
|
|
throw new Error(`HOLEPUNCH_MIRROR is not a directory: ${mirror}`)
|
|
}
|
|
ghRepos = await fetchMirrorBareRepos(mirror)
|
|
} else {
|
|
ghRepos = await fetchGithubBareRepos()
|
|
}
|
|
|
|
ghRepos.sort((a, b) => a.name.localeCompare(b.name))
|
|
|
|
const names = ghRepos.map((r) => r.name)
|
|
const npmMap = await batchNpmVersions(
|
|
names.filter((n) => !unpublished.has(n)),
|
|
16
|
|
)
|
|
|
|
const entries = []
|
|
for (const r of ghRepos) {
|
|
const name = r.name
|
|
const npmVersion = unpublished.has(name) ? null : npmMap.get(name) ?? null
|
|
const npmPublished = Boolean(npmVersion)
|
|
let exclusion = unpublished.has(name) ? 'unpublished' : null
|
|
if (excluded.has(name)) exclusion = 'excluded_from_booter'
|
|
const includedInBooter =
|
|
npmPublished && !unpublished.has(name) && !excluded.has(name)
|
|
|
|
entries.push({
|
|
repo: name,
|
|
githubUrl: r.html_url,
|
|
description: r.description || '',
|
|
npmName: npmPublished ? name : null,
|
|
npmPublished,
|
|
npmVersion,
|
|
includedInBooter,
|
|
exclusion,
|
|
exclusionDetail: excluded.get(name) || null,
|
|
manifestCtxKey: includedInBooter ? manifestCtxKeyForPackage(name) : null
|
|
})
|
|
}
|
|
|
|
for (const x of overrides.additionalCatalogEntries || []) {
|
|
if (!x || typeof x.repo !== 'string') continue
|
|
const name = x.repo.trim()
|
|
if (!name || entries.some((e) => e.repo === name)) continue
|
|
const npmHint = typeof x.npmHint === 'string' ? x.npmHint.trim() : name
|
|
let npmVersion = null
|
|
try {
|
|
npmVersion = await npmLatestVersion(npmHint)
|
|
} catch {
|
|
npmVersion = null
|
|
}
|
|
entries.push({
|
|
repo: name,
|
|
githubUrl: x.githubUrl || `https://github.com/holepunchto/${name}`,
|
|
description: x.description || '',
|
|
npmName: npmVersion ? npmHint : null,
|
|
npmPublished: Boolean(npmVersion),
|
|
npmVersion,
|
|
includedInBooter: false,
|
|
exclusion: 'p2p_aux_manual',
|
|
exclusionDetail: 'Listed for P2P stack integration research; not stock ctx.bare keys.',
|
|
manifestCtxKey: null
|
|
})
|
|
}
|
|
|
|
const out = {
|
|
schemaVersion: 1,
|
|
generatedAt: new Date().toISOString(),
|
|
source: mirror
|
|
? { mode: 'local-mirror', mirrorRoot: mirror }
|
|
: { mode: 'github-api', githubOrg: 'holepunchto' },
|
|
npmRegistry: 'https://registry.npmjs.org',
|
|
entries
|
|
}
|
|
|
|
const json = JSON.stringify(out, null, 2) + '\n'
|
|
|
|
if (check) {
|
|
let existingRaw = ''
|
|
try {
|
|
existingRaw = await readFile(outPath, 'utf8')
|
|
} catch {
|
|
console.error('[gen-bare-holepunch-catalog] --check: missing', outPath)
|
|
process.exit(1)
|
|
}
|
|
let existing
|
|
try {
|
|
existing = JSON.parse(existingRaw)
|
|
} catch {
|
|
console.error('[gen-bare-holepunch-catalog] --check: invalid JSON on disk')
|
|
process.exit(1)
|
|
}
|
|
const stable = (o) => ({
|
|
schemaVersion: o.schemaVersion,
|
|
source: o.source,
|
|
entries: o.entries
|
|
})
|
|
const a = JSON.stringify(stable(out))
|
|
const b = JSON.stringify(stable(existing))
|
|
if (a !== b) {
|
|
console.error(
|
|
'[gen-bare-holepunch-catalog] --check: drift; run gen:bare-catalog to refresh'
|
|
)
|
|
process.exit(1)
|
|
}
|
|
console.log('[gen-bare-holepunch-catalog] --check ok')
|
|
return
|
|
}
|
|
|
|
await mkdir(dirname(outPath), { recursive: true })
|
|
await writeFile(outPath, json, 'utf8')
|
|
console.log(
|
|
'Wrote',
|
|
outPath,
|
|
'entries=',
|
|
entries.length,
|
|
'npmPublished=',
|
|
entries.filter((e) => e.npmPublished).length,
|
|
'includedInBooter=',
|
|
entries.filter((e) => e.includedInBooter).length
|
|
)
|
|
}
|
|
|
|
if (
|
|
process.argv[1] &&
|
|
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
) {
|
|
main().catch((e) => {
|
|
console.error(e)
|
|
process.exit(1)
|
|
})
|
|
}
|