Files
Rami BitarandClaude Opus 5 f11a764426 Load page.json by import, publish via fs, drop cookie auth
The editor no longer round-trips through /api/pages. Each route's
editor/page.tsx imports its own `../page.json` and hands it to
PageEditor as a prop, so the editor opens with the page already in
hand — no fetch, no loading state, no undo history seeded from a
placeholder. Globals still come from app.globals.json.

Publishing moves from `PUT /api/pages` to a `publishPage` server
action that writes the route's page.json with node:fs directly.
The target path is still built from the lib/pages.ts registry rather
than from the caller, so an unknown route key is rejected instead of
escaping app/.

Removes the customer account auth entirely: the httpOnly cookie
session, the /api/account/* handlers, the customer service and
GraphQL documents, the account-* blocks, and the /account/* routes.
The template has no auth, so nothing reads a cookie now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STGDvL4X7FhHHnxdRE2ayo
2026-08-09 16:36:18 -04:00

73 lines
2.4 KiB
TypeScript

'use server';
import { writeFile } from 'node:fs/promises';
import path from 'node:path';
import { findPageRoute } from '@/lib/pages';
/**
* Writes a published page straight to its `page.json` on disk.
*
* A server action rather than a route handler: the editor calls it like a
* function, and there is no HTTP endpoint sitting in front of the filesystem.
*
* The path is built from the registry in `lib/pages.ts`, never from the caller,
* so an unknown or crafted route key is rejected outright rather than escaping
* the `app/` directory.
*/
// Props of blocks marked `global: true` (header, footer) live in one file that
// every page.json references, so editing them once updates every route.
const GLOBALS_FILE = path.join(process.cwd(), 'app.globals.json');
export interface PublishResult {
/** Path of the written file, relative to the project root. */
file?: string;
error?: string;
}
export async function publishPage(
routeKey: string,
page: unknown
): Promise<PublishResult> {
const route = findPageRoute(routeKey);
if (!route) return { error: `Unknown route: ${routeKey}` };
if (!page || typeof page !== 'object') {
return { error: 'Expected a page object.' };
}
const appDir = path.join(process.cwd(), 'app');
const file = path.join(appDir, route.dir, 'page.json');
// Belt-and-braces against a registry entry with a traversing `dir`.
if (file !== path.join(appDir, 'page.json') && !file.startsWith(appDir + path.sep)) {
return { error: `Unknown route: ${routeKey}` };
}
// Globals belong to the whole site, not this route, so they go to their own
// file and are stripped from the page before it is written.
const { globals, ...pageData } = page as Record<string, unknown>;
try {
await writeFile(file, `${JSON.stringify(pageData, null, 2)}\n`, 'utf8');
if (globals && typeof globals === 'object') {
await writeFile(
GLOBALS_FILE,
`${JSON.stringify(globals, null, 2)}\n`,
'utf8'
);
}
return { file: path.relative(process.cwd(), file) };
} catch (err) {
// Read-only filesystems (most serverless hosts) land here. Say so plainly
// rather than reporting a save that did not happen.
console.error(`Failed to write ${file}:`, err);
return {
error:
'Could not write page.json. The filesystem is read-only — run the editor locally to save.',
};
}
}