#!/usr/bin/env node /** * Merge docs/bare-holepunch-catalog.json into bare-module-manifest.json and * sync bare-os-booter optionalDependencies for every included bare-* not * already listed in dependencies. * * node scripts/sync-bare-module-manifest-from-catalog.mjs */ import { readFile, writeFile } from 'node:fs/promises' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import process from 'node:process' import { manifestCtxKeyForPackage } from './gen-bare-holepunch-catalog.mjs' import { generateBareModuleManifestData } from './generate-bare-module-manifest-data.mjs' const __dirname = dirname(fileURLToPath(import.meta.url)) const root = join(__dirname, '..') const catalogPath = join(root, 'docs', 'bare-holepunch-catalog.json') const manifestPath = join( root, 'packages/bare-os-booter/lib/bare-module-manifest.json' ) const manifestDataPath = join( root, 'packages/bare-os-booter/lib/bare-module-manifest.data.mjs' ) const booterPkgPath = join(root, 'packages/bare-os-booter/package.json') const ctxImportOverridesPath = join(__dirname, 'bare-ctx-import-overrides.json') /** Fixed first rows: non-org + curated bare-* (order preserved). */ const BASE_ENTRIES = [ { ctxKey: 'b4a', package: 'b4a', export: 'default', bundle: true, optional: false, nativeHint: false }, { ctxKey: 'compactEncoding', package: 'compact-encoding', export: 'default', bundle: true, optional: false, nativeHint: false }, { ctxKey: 'safetyCatch', package: 'safety-catch', export: 'default', bundle: true, optional: false, nativeHint: false }, { ctxKey: 'hypercoreIdEncoding', package: 'hypercore-id-encoding', export: 'default', bundle: true, optional: false, nativeHint: false }, { ctxKey: 'protomux', package: 'protomux', export: 'default', bundle: true, optional: false, nativeHint: false }, { ctxKey: 'holesail', package: 'holesail', export: 'default', bundle: true, optional: false, nativeHint: false }, { ctxKey: 'bareUrl', package: 'bare-url', export: 'default', bundle: false, optional: true, nativeHint: false, skipReason: 'Requires Bare global; host import on Pear/Bare only' }, { ctxKey: 'barePath', package: 'bare-path', export: 'default', bundle: false, optional: true, nativeHint: false, skipReason: 'Requires Bare global; host import on Pear/Bare only' }, { ctxKey: 'bareEncoding', package: 'bare-encoding', export: 'default', bundle: true, optional: true, nativeHint: false }, { ctxKey: 'bareEvents', package: 'bare-events', export: 'default', bundle: true, optional: true, nativeHint: false }, { ctxKey: 'fetch', package: 'bare-fetch', export: 'default', bundle: false, optional: true, nativeHint: true, skipReason: 'Native / runtime-specific deps; host import only' }, { ctxKey: 'bareReadline', package: 'bare-readline', export: 'default', bundle: false, optional: true, nativeHint: false, skipReason: 'TTY integration; host import only' }, { ctxKey: 'bareCrypto', package: 'bare-crypto', export: 'default', bundle: false, optional: true, nativeHint: true, skipReason: 'Native addon; host import only' } ] const basePackages = new Set(BASE_ENTRIES.map((e) => e.package)) /** @param {Record} raw */ function applyCtxImportOverrides(ent, npmName, raw) { const ns = new Set( Array.isArray(raw.namespaceExportPackages) ? raw.namespaceExportPackages : [] ) const se = new Set( Array.isArray(raw.sideEffectImportPackages) ? raw.sideEffectImportPackages : [] ) const sub = raw.packageImportSpecifiers && typeof raw.packageImportSpecifiers === 'object' ? raw.packageImportSpecifiers : {} const out = { ...ent } if (ns.has(npmName)) out.export = '*' if (typeof sub[npmName] === 'string') out.package = sub[npmName] if (se.has(npmName)) out.sideEffectImport = true return out } function buildManifestEntries(catalog, importOverrides) { const byPackage = new Map(BASE_ENTRIES.map((e) => [e.package, { ...e }])) for (const row of catalog.entries || []) { if (!row.includedInBooter || !row.npmName) continue const name = row.npmName if (byPackage.has(name)) continue const ctxKey = row.manifestCtxKey || manifestCtxKeyForPackage(name) byPackage.set(name, { ctxKey, package: name, export: 'default', bundle: false, optional: true, nativeHint: true, skipReason: 'Optional Holepunch bare-*; may be native, platform-specific, or Bare-only' }) } const finalized = new Map() for (const [npmName, ent] of byPackage) { finalized.set( npmName, applyCtxImportOverrides(ent, npmName, importOverrides) ) } const extras = [...finalized.entries()] .filter(([pkg]) => !basePackages.has(pkg)) .map(([, ent]) => ent) .sort((a, b) => a.package.localeCompare(b.package)) return [ ...BASE_ENTRIES.map((e) => ({ ...finalized.get(e.package) })), ...extras ] } function mergeOptionalDeps(pkgJson, catalog) { const deps = pkgJson.dependencies || {} const have = new Set([ ...Object.keys(deps), ...Object.keys(pkgJson.devDependencies || {}) ]) const opt = {} for (const row of catalog.entries || []) { if (!row.includedInBooter || !row.npmName || !row.npmVersion) continue const n = row.npmName if (have.has(n)) continue opt[n] = `^${row.npmVersion}` } const sorted = {} for (const k of Object.keys(opt).sort()) sorted[k] = opt[k] pkgJson.optionalDependencies = sorted } async function main() { let importOverrides = {} try { importOverrides = JSON.parse(await readFile(ctxImportOverridesPath, 'utf8')) } catch { /* optional */ } const catalogRaw = await readFile(catalogPath, 'utf8') const catalog = JSON.parse(catalogRaw) const entries = buildManifestEntries(catalog, importOverrides) const keys = new Set() for (const e of entries) { if (keys.has(e.ctxKey)) throw new Error(`Duplicate ctxKey: ${e.ctxKey}`) keys.add(e.ctxKey) } /** Preserve curated ctx.pear rows; catalog sync only rewrites `entries`. */ let pearEntries try { const prev = JSON.parse(await readFile(manifestPath, 'utf8')) if (Array.isArray(prev.pearEntries) && prev.pearEntries.length) { pearEntries = prev.pearEntries } } catch { /* first generate */ } const manifest = pearEntries ? { version: 1, entries, pearEntries } : { version: 1, entries } await writeFile( manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8' ) await generateBareModuleManifestData({ manifestPath, dataPath: manifestDataPath }) const pkgRaw = await readFile(booterPkgPath, 'utf8') const pkgJson = JSON.parse(pkgRaw) mergeOptionalDeps(pkgJson, catalog) await writeFile( booterPkgPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf8' ) console.log( '[sync-bare-module-manifest] manifest entries=', entries.length, 'optionalDependencies=', Object.keys(pkgJson.optionalDependencies || {}).length ) } main().catch((e) => { console.error(e) process.exit(1) })