Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ evals/*/local/supabase/.branches/
results/*/
.sync-tmp/


# local-dev runner (apps/framework/scripts/local.ts)
/results-local/
/.local-docs/
71 changes: 71 additions & 0 deletions apps/framework/scripts/docs/content-api-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// fallow-ignore-file unused-file -- loaded at runtime (spawned/injected by local-docs.ts), never statically imported
/**
* Standalone docs content GraphQL API for `search_docs`.
*
* Serves the docs app's own route handler (apps/docs/app/api/graphql/route.ts
* in a supabase/supabase checkout) over plain node:http — no Next server.
* Launched by `pnpm local docs api` with the docs checkout's tsx so the
* route's TS + tsconfig conditions resolve; DOCS_ROUTE_PATH points at the
* checkout, PORT picks the listen port.
*/
import { createServer } from 'node:http';
import { pathToFileURL } from 'node:url';

const routePath = process.env.DOCS_ROUTE_PATH;
if (!routePath) {
console.error(
'DOCS_ROUTE_PATH not set — run this through `pnpm local docs api`'
);
process.exit(1);
}
// The docs checkout location is user-supplied at runtime; a static import
// cannot name it.
const route = await import(pathToFileURL(routePath).href);
const handlers: Record<string, (req: Request) => Promise<Response>> = {
GET: route.GET,
OPTIONS: route.OPTIONS,
POST: route.POST,
};
const port = Number(process.env.PORT ?? 3001);

createServer(async (incoming, outgoing) => {
const url = new URL(
incoming.url ?? '/',
`http://${incoming.headers.host ?? `127.0.0.1:${port}`}`
);
const handler = handlers[incoming.method ?? ''];
if (url.pathname !== '/docs/api/graphql' || !handler) {
outgoing.writeHead(404).end();
return;
}

const headers = new Headers();
for (const [name, value] of Object.entries(incoming.headers)) {
if (Array.isArray(value))
for (const item of value) headers.append(name, item);
else if (value !== undefined) headers.set(name, value);
}

const chunks: Buffer[] = [];
for await (const chunk of incoming) chunks.push(Buffer.from(chunk));
const body =
incoming.method === 'GET' || incoming.method === 'HEAD'
? undefined
: Buffer.concat(chunks).toString('utf8');
const response = await handler(
new Request(url, { method: incoming.method, headers, body })
);

outgoing.writeHead(
response.status,
Object.fromEntries(response.headers.entries())
);
outgoing.end(Buffer.from(await response.arrayBuffer()));
// Bind every interface, advertise loopback — same rule as platform-lite in
// tools mode (see run-eval.ts: sandboxed CLI agents run their MCP servers
// INSIDE the container and reach host-side services via
// host.docker.internal, which arrives on the host's bridge interface, not
// loopback; a 127.0.0.1-only listener refuses those connections).
}).listen(port, '0.0.0.0', () => {
console.log(`Docs content API: http://127.0.0.1:${port}/docs/api/graphql`);
});
11 changes: 11 additions & 0 deletions apps/framework/scripts/docs/sentry-stub-loader.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// fallow-ignore-file unused-file -- registered at runtime by sentry-stub-register.mjs via module.register()
// Loader-thread resolve hook: '@sentry/nextjs' -> the no-op stub.
const stubUrl = new URL('./sentry-stub.mjs', import.meta.url).href;

// fallow-ignore-next-line unused-export -- Node loader-hook contract: the module system calls `resolve`
export async function resolve(specifier, context, next) {
if (specifier === '@sentry/nextjs') {
return { url: stubUrl, shortCircuit: true };
}
return next(specifier, context);
}
9 changes: 9 additions & 0 deletions apps/framework/scripts/docs/sentry-stub-register.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// fallow-ignore-file unused-file -- loaded at runtime (spawned/injected by local-docs.ts), never statically imported
// Registers a resolve hook that short-circuits '@sentry/nextjs' to the local
// no-op stub. Injected via NODE_OPTIONS from `pnpm local docs api`; chains with tsx's
// own hooks (ours only intercepts the one specifier). Uses module.register()
// (Node 20.6+) rather than registerHooks() (22.15+) — mise pins node "22",
// which an older 22.x install satisfies.
import { register } from 'node:module';

register('./sentry-stub-loader.mjs', import.meta.url);
9 changes: 9 additions & 0 deletions apps/framework/scripts/docs/sentry-stub.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// fallow-ignore-file unused-file -- loaded at runtime (spawned/injected by local-docs.ts), never statically imported
// No-op @sentry/nextjs stand-in for the standalone docs content API.
// The route handler calls Sentry.captureException/flush; under plain tsx
// (outside Next's Sentry instrumentation) the real package's ESM build
// resolves without those functions and every request crashes. A local dev
// adapter has no business sending telemetry anyway. Wired up by
// sentry-stub-register.mjs (see local-docs.ts).
export const captureException = () => '';
export const flush = async () => true;
Loading