fix(web): fully remove withPayload + admin routes from prod
withPayload crashes ALL routes with React #130 in standalone output — even with admin page replaced by redirect. The wrapper injects a client-side ConfigProvider that fails hydration. Removed: withPayload wrapper, entire (payload) route group. Kept: payload.config.ts, migrations, blog/changelog server-side queries with graceful DB fallback. Payload admin runs on local dev only (add withPayload back in next.config when running pnpm dev). Production content via static TSX pages or future API-based publishing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,6 @@ function readClaudeConfig(): Record<string, unknown> {
|
||||
|
||||
/**
|
||||
* Create a timestamped backup of ~/.claude.json before any write.
|
||||
* Keeps the last 3 backups to avoid clutter.
|
||||
*/
|
||||
function backupClaudeConfig(): void {
|
||||
if (!existsSync(CLAUDE_CONFIG)) return;
|
||||
@@ -80,30 +79,52 @@ function backupClaudeConfig(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity-check: abort if we're about to lose MCP servers that existed
|
||||
* on disk but are missing from the object we're about to write.
|
||||
* Atomic read-merge-write: re-reads ~/.claude.json at write time and
|
||||
* patches ONLY the `claudemesh` MCP entry. Never touches other keys.
|
||||
* Returns the action taken ("added" | "updated" | "unchanged").
|
||||
*/
|
||||
function assertNoMcpLoss(next: Record<string, unknown>): void {
|
||||
if (!existsSync(CLAUDE_CONFIG)) return;
|
||||
const prev = readClaudeConfig();
|
||||
const prevServers = Object.keys(
|
||||
(prev.mcpServers as Record<string, unknown>) ?? {},
|
||||
);
|
||||
const nextServers = Object.keys(
|
||||
(next.mcpServers as Record<string, unknown>) ?? {},
|
||||
);
|
||||
const lost = prevServers.filter((k) => !nextServers.includes(k));
|
||||
if (lost.length > 0) {
|
||||
throw new Error(
|
||||
`Aborting write: would lose ${lost.length} existing MCP server(s): ${lost.join(", ")}. ` +
|
||||
`This is a bug — please report it. A backup was saved in ~/.claude/backups/`,
|
||||
);
|
||||
function patchMcpServer(entry: McpEntry): "added" | "updated" | "unchanged" {
|
||||
backupClaudeConfig();
|
||||
const cfg = readClaudeConfig();
|
||||
const servers =
|
||||
((cfg.mcpServers as Record<string, McpEntry>) ?? {});
|
||||
if (!cfg.mcpServers) cfg.mcpServers = servers;
|
||||
|
||||
const existing = servers[MCP_NAME];
|
||||
let action: "added" | "updated" | "unchanged";
|
||||
if (!existing) {
|
||||
servers[MCP_NAME] = entry;
|
||||
action = "added";
|
||||
} else if (entriesEqual(existing, entry)) {
|
||||
return "unchanged";
|
||||
} else {
|
||||
servers[MCP_NAME] = entry;
|
||||
action = "updated";
|
||||
}
|
||||
|
||||
flushClaudeConfig(cfg);
|
||||
return action;
|
||||
}
|
||||
|
||||
function writeClaudeConfig(obj: Record<string, unknown>): void {
|
||||
/**
|
||||
* Atomic read-merge-write: re-reads ~/.claude.json at write time and
|
||||
* removes ONLY the `claudemesh` MCP entry. Never touches other keys.
|
||||
* Returns true if an entry was removed.
|
||||
*/
|
||||
function removeMcpServer(): boolean {
|
||||
if (!existsSync(CLAUDE_CONFIG)) return false;
|
||||
backupClaudeConfig();
|
||||
assertNoMcpLoss(obj);
|
||||
const cfg = readClaudeConfig();
|
||||
const servers = cfg.mcpServers as Record<string, McpEntry> | undefined;
|
||||
if (!servers || !(MCP_NAME in servers)) return false;
|
||||
delete servers[MCP_NAME];
|
||||
cfg.mcpServers = servers;
|
||||
flushClaudeConfig(cfg);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Low-level write — callers must backup + merge first. */
|
||||
function flushClaudeConfig(obj: Record<string, unknown>): void {
|
||||
mkdirSync(dirname(CLAUDE_CONFIG), { recursive: true });
|
||||
writeFileSync(
|
||||
CLAUDE_CONFIG,
|
||||
@@ -117,6 +138,7 @@ function writeClaudeConfig(obj: Record<string, unknown>): void {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Check `bun` is on PATH — OS-agnostic, node:child_process. */
|
||||
function bunAvailable(): boolean {
|
||||
const res =
|
||||
@@ -269,24 +291,8 @@ export function runInstall(args: string[] = []): void {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cfg = readClaudeConfig();
|
||||
const servers =
|
||||
((cfg.mcpServers ??= {}) as Record<string, McpEntry>) ?? {};
|
||||
const desired = buildMcpEntry(entry);
|
||||
const existing = servers[MCP_NAME];
|
||||
let action: "added" | "updated" | "unchanged";
|
||||
if (!existing) {
|
||||
servers[MCP_NAME] = desired;
|
||||
action = "added";
|
||||
} else if (entriesEqual(existing, desired)) {
|
||||
action = "unchanged";
|
||||
} else {
|
||||
servers[MCP_NAME] = desired;
|
||||
action = "updated";
|
||||
}
|
||||
cfg.mcpServers = servers;
|
||||
|
||||
writeClaudeConfig(cfg);
|
||||
const action = patchMcpServer(desired);
|
||||
|
||||
// Read-back verification.
|
||||
const verify = readClaudeConfig();
|
||||
@@ -362,22 +368,11 @@ export function runUninstall(): void {
|
||||
console.log("claudemesh uninstall");
|
||||
console.log("--------------------");
|
||||
|
||||
// MCP entry
|
||||
if (existsSync(CLAUDE_CONFIG)) {
|
||||
const cfg = readClaudeConfig();
|
||||
const servers = cfg.mcpServers as
|
||||
| Record<string, McpEntry>
|
||||
| undefined;
|
||||
if (servers && MCP_NAME in servers) {
|
||||
delete servers[MCP_NAME];
|
||||
cfg.mcpServers = servers;
|
||||
writeClaudeConfig(cfg);
|
||||
console.log(`✓ MCP server "${MCP_NAME}" removed`);
|
||||
} else {
|
||||
console.log(`· MCP server "${MCP_NAME}" not present`);
|
||||
}
|
||||
// MCP entry — only removes claudemesh, never touches other servers.
|
||||
if (removeMcpServer()) {
|
||||
console.log(`✓ MCP server "${MCP_NAME}" removed`);
|
||||
} else {
|
||||
console.log(`· no ${CLAUDE_CONFIG} — MCP entry skipped`);
|
||||
console.log(`· MCP server "${MCP_NAME}" not present`);
|
||||
}
|
||||
|
||||
// Hooks
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { withPayload } = require("@payloadcms/next/withPayload");
|
||||
|
||||
import env from "./env.config";
|
||||
|
||||
const INTERNAL_PACKAGES = [
|
||||
@@ -118,4 +115,4 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: env.ANALYZE,
|
||||
});
|
||||
|
||||
export default withPayload(withBundleAnalyzer(config));
|
||||
export default withBundleAnalyzer(config);
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import "@payloadcms/next/css";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export const metadata = {
|
||||
title: "CMS — claudemesh",
|
||||
};
|
||||
|
||||
export default function PayloadLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
// Payload admin panel disabled in production (standalone output
|
||||
// doesn't support Payload's server init). Content managed via
|
||||
// local dev server or API.
|
||||
export default function PayloadAdminRedirect() {
|
||||
redirect("/");
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
||||
|
||||
export const importMap = {
|
||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
||||
}
|
||||
Reference in New Issue
Block a user