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
73 lines
2.4 KiB
TypeScript
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.',
|
|
};
|
|
}
|
|
}
|