e171bffa97
* fix(docs): unbreak the website export script, stub server-only scripts/export-docs-to-website.mts has been failing since PostHog landed: lib/api/v1/load-routes pulls in every v1 route, which reaches lib/init -> lib/analytics/posthog-observability -> posthog-server, and posthog-server imports `server-only`, which throws outside a Next.js server-component graph. The script only reads exported markdown builders, so it now neutralises that module with a Module._load hook before importing anything. Without this the docs cannot be regenerated at all, which is how /docs/api/connect-claude stayed unported (the page exists here but the redirect sends every request to the website repo, where it 404'd). Refs #1247 * refactor(docs): scope the server-only stub to the imports that need it Compliance-swarm finding (ISO 27001 A.8.28) on the export script: the Module._load hook stayed patched for the rest of the process, silently disarming the guard for anything imported later. Restore it in a finally block around the three content imports.
110 lines
5.0 KiB
TypeScript
110 lines
5.0 KiB
TypeScript
/**
|
|
* One-shot script that exports the registry-derived docs content (errors +
|
|
* reference) plus the static Connect-with-Claude page as TypeScript modules
|
|
* into the gnubok-website repo.
|
|
*
|
|
* Run with `npx tsx scripts/export-docs-to-website.mts`. Re-run whenever
|
|
* structured-errors, the v1 endpoint registry, or connect-claude materially
|
|
* changes.
|
|
*/
|
|
import { writeFileSync, mkdirSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { dirname, resolve } from 'node:path'
|
|
|
|
// `server-only` throws on import outside a Next.js server-component graph. The
|
|
// reference builder pulls it in transitively (lib/api/v1/load-routes -> every
|
|
// v1 route -> lib/init -> lib/analytics/posthog-observability ->
|
|
// posthog-server), which broke this script the moment PostHog landed. Nothing
|
|
// here executes request-time code: it only reads exported markdown builders,
|
|
// so a no-op stub is the honest resolution.
|
|
const require = createRequire(import.meta.url)
|
|
const ModuleCtor = require('node:module') as {
|
|
_load: (request: string, ...rest: unknown[]) => unknown
|
|
}
|
|
const originalLoad = ModuleCtor._load
|
|
ModuleCtor._load = function (request: string, ...rest: unknown[]) {
|
|
if (request === 'server-only') return {}
|
|
return originalLoad.call(this, request, ...rest)
|
|
}
|
|
|
|
let errors, reference, connectClaude
|
|
try {
|
|
errors = await import('@/lib/docs/content/errors')
|
|
reference = await import('@/lib/docs/content/reference')
|
|
connectClaude = await import('@/lib/docs/content/connect-claude')
|
|
} finally {
|
|
// Scope the stub to the imports that need it: leaving a global loader hook
|
|
// patched for the rest of the process would silently disarm the guard for
|
|
// anything imported later (compliance swarm, ISO 27001 A.8.28).
|
|
ModuleCtor._load = originalLoad
|
|
}
|
|
|
|
const buildErrorReferenceMd = errors.buildErrorReferenceMd ?? (errors as { default?: typeof errors }).default?.buildErrorReferenceMd
|
|
const buildResourcePages = reference.buildResourcePages ?? (reference as { default?: typeof reference }).default?.buildResourcePages
|
|
const buildReferenceOverviewMd = reference.buildReferenceOverviewMd ?? (reference as { default?: typeof reference }).default?.buildReferenceOverviewMd
|
|
|
|
if (!buildErrorReferenceMd || !buildResourcePages || !buildReferenceOverviewMd) {
|
|
console.error('Missing builder exports. Inspect:', {
|
|
errorsKeys: Object.keys(errors),
|
|
referenceKeys: Object.keys(reference),
|
|
})
|
|
process.exit(1)
|
|
}
|
|
|
|
const WEBSITE = resolve('/Users/jakobwennberg/gnubok-website')
|
|
|
|
/**
|
|
* The website is served from www.accounted.se while the API (and everything
|
|
* under /api/v1 and /.well-known) lives on app.gnubok.se — root-relative links
|
|
* to app-served resources would 404 on the website, so absolutise them.
|
|
*/
|
|
const APP_ORIGIN = 'https://app.gnubok.se'
|
|
function adaptForWebsite(md: string): string {
|
|
return md
|
|
.replaceAll('](/api/v1/', `](${APP_ORIGIN}/api/v1/`)
|
|
.replaceAll('](/.well-known/', `](${APP_ORIGIN}/.well-known/`)
|
|
}
|
|
|
|
function write(rel: string, content: string) {
|
|
const out = resolve(WEBSITE, rel)
|
|
mkdirSync(dirname(out), { recursive: true })
|
|
writeFileSync(out, content)
|
|
console.log(`wrote ${out} (${content.length} chars)`)
|
|
}
|
|
|
|
const errorsMd = adaptForWebsite(buildErrorReferenceMd())
|
|
write(
|
|
'lib/docs/content/errors.generated.ts',
|
|
`// AUTO-GENERATED from erp-base: do not hand-edit.\n// Regenerate via \`npx tsx scripts/export-docs-to-website.mts\` in erp-base.\nexport const ERRORS_MD = ${JSON.stringify(errorsMd)}\n`,
|
|
)
|
|
|
|
const refOverview = adaptForWebsite(buildReferenceOverviewMd())
|
|
const refPages = buildResourcePages()
|
|
const slugs = refPages.map((p: { slug: string }) => p.slug)
|
|
|
|
const pagesPayload = refPages.map((p: { slug: string; label: string; description: string; markdown: string }) => ({
|
|
slug: p.slug,
|
|
label: p.label,
|
|
description: p.description,
|
|
markdown: adaptForWebsite(p.markdown),
|
|
}))
|
|
|
|
write(
|
|
'lib/docs/content/reference.generated.ts',
|
|
`// AUTO-GENERATED from erp-base: do not hand-edit.\n// Regenerate via \`npx tsx scripts/export-docs-to-website.mts\` in erp-base.\n\nexport const REFERENCE_OVERVIEW_MD = ${JSON.stringify(refOverview)}\n\nexport interface ResourcePage {\n slug: string\n label: string\n description: string\n markdown: string\n}\n\nexport const RESOURCE_SLUGS: readonly string[] = ${JSON.stringify(
|
|
slugs,
|
|
)} as const\n\nexport const RESOURCE_PAGES: ResourcePage[] = ${JSON.stringify(pagesPayload, null, 2)}\n\nexport function findResourcePage(slug: string): ResourcePage | undefined {\n return RESOURCE_PAGES.find((p) => p.slug === slug)\n}\n`,
|
|
)
|
|
|
|
const connectClaudeMd = connectClaude.CONNECT_CLAUDE_MD && adaptForWebsite(connectClaude.CONNECT_CLAUDE_MD)
|
|
if (!connectClaudeMd) {
|
|
console.error('Missing CONNECT_CLAUDE_MD export. Inspect:', { connectClaudeKeys: Object.keys(connectClaude) })
|
|
process.exit(1)
|
|
}
|
|
write(
|
|
'lib/docs/content/connect-claude.generated.ts',
|
|
`// AUTO-GENERATED from erp-base: do not hand-edit.\n// Regenerate via \`npx tsx scripts/export-docs-to-website.mts\` in erp-base.\nexport const CONNECT_CLAUDE_MD = ${JSON.stringify(connectClaudeMd)}\n`,
|
|
)
|
|
|
|
console.log('done.')
|