Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
127 lines
3.2 KiB
TypeScript
127 lines
3.2 KiB
TypeScript
/**
|
|
* Content Serializer
|
|
*
|
|
* Converts Tiptap JSON page tree into markdown for agent prompts.
|
|
* Uses @tiptap/markdown's MarkdownManager for standard node serialization,
|
|
* with custom handling only for pageLink nodes.
|
|
*/
|
|
|
|
import { Node, type JSONContent } from '@tiptap/core';
|
|
import StarterKit from '@tiptap/starter-kit';
|
|
import Link from '@tiptap/extension-link';
|
|
import { MarkdownManager } from '@tiptap/markdown';
|
|
|
|
/**
|
|
* Minimal page shape needed for serialization.
|
|
*/
|
|
export interface PageForSerialization {
|
|
id: string;
|
|
parentPageId: string | null;
|
|
title: string;
|
|
content: string | null; // JSON string from Tiptap
|
|
sortOrder: number;
|
|
}
|
|
|
|
/**
|
|
* Server-side pageLink node — only needs schema definition + markdown rendering.
|
|
*/
|
|
const ServerPageLink = Node.create({
|
|
name: 'pageLink',
|
|
group: 'block',
|
|
atom: true,
|
|
|
|
addAttributes() {
|
|
return {
|
|
pageId: { default: null },
|
|
};
|
|
},
|
|
|
|
renderMarkdown(node: JSONContent) {
|
|
const pageId = (node.attrs?.pageId as string) ?? '';
|
|
return `[[page:${pageId}]]\n\n`;
|
|
},
|
|
});
|
|
|
|
let _manager: MarkdownManager | null = null;
|
|
|
|
function getManager(): MarkdownManager {
|
|
if (!_manager) {
|
|
_manager = new MarkdownManager({
|
|
extensions: [StarterKit, Link, ServerPageLink],
|
|
});
|
|
}
|
|
return _manager;
|
|
}
|
|
|
|
/**
|
|
* Convert a Tiptap JSON document to markdown.
|
|
*/
|
|
export function tiptapJsonToMarkdown(json: unknown): string {
|
|
if (!json || typeof json !== 'object') return '';
|
|
|
|
const doc = json as JSONContent;
|
|
if (doc.type !== 'doc' || !Array.isArray(doc.content)) return '';
|
|
|
|
return getManager().serialize(doc).trim();
|
|
}
|
|
|
|
/**
|
|
* Serialize an array of pages into a single markdown document.
|
|
* Pages are organized as a tree (root first, then children by sortOrder).
|
|
*
|
|
* Each page is marked with <!-- page:$id --> so the agent can reference them.
|
|
*/
|
|
export function serializePageTree(pages: PageForSerialization[]): string {
|
|
if (pages.length === 0) return '';
|
|
|
|
// Build parent→children map
|
|
const childrenMap = new Map<string | null, PageForSerialization[]>();
|
|
for (const page of pages) {
|
|
const parentKey = page.parentPageId;
|
|
if (!childrenMap.has(parentKey)) {
|
|
childrenMap.set(parentKey, []);
|
|
}
|
|
childrenMap.get(parentKey)!.push(page);
|
|
}
|
|
|
|
// Sort children by sortOrder
|
|
for (const children of childrenMap.values()) {
|
|
children.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
}
|
|
|
|
// Render tree depth-first
|
|
const sections: string[] = [];
|
|
|
|
function renderPage(page: PageForSerialization, depth: number): void {
|
|
const headerPrefix = '#'.repeat(Math.min(depth + 1, 6));
|
|
let section = `<!-- page:${page.id} -->\n${headerPrefix} ${page.title}`;
|
|
|
|
if (page.content) {
|
|
try {
|
|
const parsed = JSON.parse(page.content);
|
|
const md = tiptapJsonToMarkdown(parsed);
|
|
if (md.trim()) {
|
|
section += `\n\n${md}`;
|
|
}
|
|
} catch {
|
|
// Invalid JSON — skip content
|
|
}
|
|
}
|
|
|
|
sections.push(section);
|
|
|
|
const children = childrenMap.get(page.id) ?? [];
|
|
for (const child of children) {
|
|
renderPage(child, depth + 1);
|
|
}
|
|
}
|
|
|
|
// Start from root pages (parentPageId is null)
|
|
const roots = childrenMap.get(null) ?? [];
|
|
for (const root of roots) {
|
|
renderPage(root, 1);
|
|
}
|
|
|
|
return sections.join('\n\n');
|
|
}
|