@imjp/writenex-astro 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +539 -0
- package/dist/chunk-5PM6EQE5.js +151 -0
- package/dist/chunk-5PM6EQE5.js.map +1 -0
- package/dist/chunk-7XU5X6CW.js +1331 -0
- package/dist/chunk-7XU5X6CW.js.map +1 -0
- package/dist/chunk-AAOQHQPU.js +574 -0
- package/dist/chunk-AAOQHQPU.js.map +1 -0
- package/dist/chunk-CF2XXJFF.js +1410 -0
- package/dist/chunk-CF2XXJFF.js.map +1 -0
- package/dist/chunk-CRPZUUDU.js +52 -0
- package/dist/chunk-CRPZUUDU.js.map +1 -0
- package/dist/chunk-CYLDJ3HZ.js +310 -0
- package/dist/chunk-CYLDJ3HZ.js.map +1 -0
- package/dist/chunk-KIKIPIFA.js +1 -0
- package/dist/chunk-KIKIPIFA.js.map +1 -0
- package/dist/chunk-XNTQTTJU.js +145 -0
- package/dist/chunk-XNTQTTJU.js.map +1 -0
- package/dist/client/index.css +2 -0
- package/dist/client/index.css.map +1 -0
- package/dist/client/index.js +375 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/styles.css +584 -0
- package/dist/client/variables.css +304 -0
- package/dist/config/index.d.ts +54 -0
- package/dist/config/index.js +38 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config-BmEdBDo_.d.ts +220 -0
- package/dist/content-BWR52vD-.d.ts +64 -0
- package/dist/discovery/index.d.ts +310 -0
- package/dist/discovery/index.js +38 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/errors-C0iYiDTv.d.ts +107 -0
- package/dist/filesystem/index.d.ts +1292 -0
- package/dist/filesystem/index.js +203 -0
- package/dist/filesystem/index.js.map +1 -0
- package/dist/image-FP7w5ZIs.d.ts +47 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/loader-55LWCXHA.js +12 -0
- package/dist/loader-55LWCXHA.js.map +1 -0
- package/dist/loader-CrdnaAWR.d.ts +327 -0
- package/dist/server/index.d.ts +357 -0
- package/dist/server/index.js +37 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +94 -0
- package/src/client/App.tsx +900 -0
- package/src/client/components/ConfigPanel/ConfigPanel.css +553 -0
- package/src/client/components/ConfigPanel/ConfigPanel.tsx +396 -0
- package/src/client/components/ConfigPanel/index.ts +6 -0
- package/src/client/components/CreateContentModal/CreateContentModal.css +327 -0
- package/src/client/components/CreateContentModal/CreateContentModal.tsx +216 -0
- package/src/client/components/CreateContentModal/index.ts +7 -0
- package/src/client/components/Editor/Editor.css +885 -0
- package/src/client/components/Editor/Editor.tsx +484 -0
- package/src/client/components/Editor/ImageDialog.css +344 -0
- package/src/client/components/Editor/ImageDialog.tsx +367 -0
- package/src/client/components/Editor/LinkDialog.css +326 -0
- package/src/client/components/Editor/LinkDialog.tsx +332 -0
- package/src/client/components/Editor/index.ts +6 -0
- package/src/client/components/FrontmatterForm/FrontmatterForm.css +468 -0
- package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +914 -0
- package/src/client/components/FrontmatterForm/index.ts +7 -0
- package/src/client/components/Header/Header.css +300 -0
- package/src/client/components/Header/Header.tsx +300 -0
- package/src/client/components/Header/index.ts +7 -0
- package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.css +239 -0
- package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +151 -0
- package/src/client/components/KeyboardShortcuts/index.ts +6 -0
- package/src/client/components/LazyEditor.tsx +75 -0
- package/src/client/components/LiveRegion/LiveRegion.css +19 -0
- package/src/client/components/LiveRegion/LiveRegion.tsx +60 -0
- package/src/client/components/LiveRegion/index.ts +7 -0
- package/src/client/components/SearchReplace/SearchReplacePanel.css +300 -0
- package/src/client/components/SearchReplace/SearchReplacePanel.tsx +332 -0
- package/src/client/components/SearchReplace/index.ts +7 -0
- package/src/client/components/SelectCollectionModal/SelectCollectionModal.css +308 -0
- package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +223 -0
- package/src/client/components/SelectCollectionModal/index.ts +7 -0
- package/src/client/components/Sidebar/Sidebar.css +570 -0
- package/src/client/components/Sidebar/Sidebar.tsx +617 -0
- package/src/client/components/Sidebar/index.ts +7 -0
- package/src/client/components/SkipLink/SkipLink.css +51 -0
- package/src/client/components/SkipLink/SkipLink.tsx +67 -0
- package/src/client/components/SkipLink/index.ts +7 -0
- package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.css +233 -0
- package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +160 -0
- package/src/client/components/UnsavedChangesModal/index.ts +1 -0
- package/src/client/components/VersionHistory/DiffViewer.css +430 -0
- package/src/client/components/VersionHistory/DiffViewer.tsx +383 -0
- package/src/client/components/VersionHistory/VersionActions.css +318 -0
- package/src/client/components/VersionHistory/VersionActions.tsx +277 -0
- package/src/client/components/VersionHistory/VersionHistoryPanel.css +369 -0
- package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +469 -0
- package/src/client/components/VersionHistory/index.ts +9 -0
- package/src/client/context/ApiContext.tsx +154 -0
- package/src/client/context/ThemeContext.tsx +172 -0
- package/src/client/hooks/useAnnounce.ts +201 -0
- package/src/client/hooks/useApi.ts +374 -0
- package/src/client/hooks/useArrowNavigation.ts +286 -0
- package/src/client/hooks/useAutosave.ts +241 -0
- package/src/client/hooks/useFocusTrap.ts +178 -0
- package/src/client/hooks/useKeyboardShortcuts.ts +203 -0
- package/src/client/hooks/useSearch.ts +206 -0
- package/src/client/hooks/useVersionHistory.ts +451 -0
- package/src/client/index.tsx +70 -0
- package/src/client/styles.css +584 -0
- package/src/client/utils/focus.ts +57 -0
- package/src/client/utils/openInEditor.ts +130 -0
- package/src/client/variables.css +304 -0
- package/src/config/defaults.ts +109 -0
- package/src/config/index.ts +32 -0
- package/src/config/loader.ts +174 -0
- package/src/config/schema.ts +161 -0
- package/src/core/constants.ts +39 -0
- package/src/core/errors.ts +739 -0
- package/src/core/index.ts +11 -0
- package/src/discovery/collections.ts +216 -0
- package/src/discovery/index.ts +33 -0
- package/src/discovery/patterns.ts +702 -0
- package/src/discovery/schema.ts +453 -0
- package/src/filesystem/images.ts +798 -0
- package/src/filesystem/index.ts +107 -0
- package/src/filesystem/reader.ts +452 -0
- package/src/filesystem/version-config.ts +390 -0
- package/src/filesystem/versions.ts +1339 -0
- package/src/filesystem/watcher.ts +226 -0
- package/src/filesystem/writer.ts +540 -0
- package/src/index.ts +61 -0
- package/src/integration.ts +228 -0
- package/src/server/assets.ts +254 -0
- package/src/server/cache.ts +355 -0
- package/src/server/index.ts +33 -0
- package/src/server/middleware.ts +209 -0
- package/src/server/routes.ts +1428 -0
- package/src/types/api.ts +61 -0
- package/src/types/config.ts +134 -0
- package/src/types/content.ts +64 -0
- package/src/types/image.ts +48 -0
- package/src/types/index.ts +58 -0
- package/src/types/version.ts +117 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/cache.ts","../src/server/routes.ts","../src/server/assets.ts","../src/server/middleware.ts"],"sourcesContent":["/**\n * @fileoverview Server-side caching for Writenex\n *\n * Provides in-memory caching for collection discovery and content summaries\n * to improve performance by avoiding repeated filesystem operations.\n *\n * ## Features:\n * - TTL-based cache expiration\n * - Per-collection content caching\n * - Image discovery caching\n * - Manual cache invalidation\n * - Integration with file watcher for automatic invalidation\n *\n * @module @writenex/astro/server/cache\n */\n\nimport type {\n DiscoveredCollection,\n ContentSummary,\n DiscoveredImage,\n} from \"@/types\";\n\n/**\n * Cache entry with timestamp and data\n */\ninterface CacheEntry<T> {\n data: T;\n timestamp: number;\n}\n\n/**\n * Default TTL for cache entries (5 minutes)\n */\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\n/**\n * Short TTL for development (30 seconds)\n * Used when file watcher is not active\n */\nconst DEV_TTL_MS = 30 * 1000;\n\n/**\n * Server-side cache for collection and content data\n *\n * This cache stores:\n * - Collection discovery results (list of collections with metadata)\n * - Content summaries per collection (list of content items)\n * - Discovered images per content item\n *\n * Cache invalidation happens:\n * - Automatically when TTL expires\n * - Manually via invalidate methods\n * - Via file watcher integration\n */\nexport class ServerCache {\n /** Cache for collection discovery results */\n private collectionsCache: CacheEntry<DiscoveredCollection[]> | null = null;\n\n /** Cache for content summaries, keyed by collection name */\n private contentCache: Map<string, CacheEntry<ContentSummary[]>> = new Map();\n\n /** Cache for discovered images, keyed by \"collection:contentId\" */\n private imagesCache: Map<string, CacheEntry<DiscoveredImage[]>> = new Map();\n\n /** Time-to-live for cache entries in milliseconds */\n private ttl: number;\n\n /** Whether file watcher is integrated (allows longer TTL) */\n private hasWatcher: boolean = false;\n\n constructor(options: { ttl?: number; hasWatcher?: boolean } = {}) {\n this.ttl =\n options.ttl ?? (options.hasWatcher ? DEFAULT_TTL_MS : DEV_TTL_MS);\n this.hasWatcher = options.hasWatcher ?? false;\n }\n\n /**\n * Enable file watcher integration\n *\n * When watcher is enabled, cache can use longer TTL since\n * invalidation happens via watcher events.\n */\n enableWatcher(): void {\n this.hasWatcher = true;\n this.ttl = DEFAULT_TTL_MS;\n }\n\n /**\n * Check if a cache entry is still valid\n */\n private isValid<T>(entry: CacheEntry<T> | null | undefined): boolean {\n if (!entry) return false;\n return Date.now() - entry.timestamp < this.ttl;\n }\n\n // ==================== Collections Cache ====================\n\n /**\n * Get cached collections if valid\n *\n * @returns Cached collections or null if expired/not cached\n */\n getCollections(): DiscoveredCollection[] | null {\n if (this.isValid(this.collectionsCache)) {\n return this.collectionsCache!.data;\n }\n return null;\n }\n\n /**\n * Set collections cache\n *\n * @param collections - Collections to cache\n */\n setCollections(collections: DiscoveredCollection[]): void {\n this.collectionsCache = {\n data: collections,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Invalidate collections cache\n */\n invalidateCollections(): void {\n this.collectionsCache = null;\n }\n\n // ==================== Content Cache ====================\n\n /**\n * Get cached content summaries for a collection if valid\n *\n * @param collection - Collection name\n * @returns Cached content summaries or null if expired/not cached\n */\n getContent(collection: string): ContentSummary[] | null {\n const entry = this.contentCache.get(collection);\n if (this.isValid(entry)) {\n return entry!.data;\n }\n return null;\n }\n\n /**\n * Set content cache for a collection\n *\n * @param collection - Collection name\n * @param items - Content summaries to cache\n */\n setContent(collection: string, items: ContentSummary[]): void {\n this.contentCache.set(collection, {\n data: items,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Invalidate content cache for a specific collection\n *\n * @param collection - Collection name to invalidate\n */\n invalidateContent(collection: string): void {\n this.contentCache.delete(collection);\n }\n\n /**\n * Invalidate all content caches\n */\n invalidateAllContent(): void {\n this.contentCache.clear();\n }\n\n // ==================== Images Cache ====================\n\n /**\n * Generate cache key for image cache\n *\n * @param collection - Collection name\n * @param contentId - Content ID\n * @returns Cache key string\n */\n private getImagesCacheKey(collection: string, contentId: string): string {\n return `${collection}:${contentId}`;\n }\n\n /**\n * Get cached images for a content item if valid\n *\n * @param collection - Collection name\n * @param contentId - Content ID\n * @returns Cached images or null if expired/not cached\n */\n getImages(collection: string, contentId: string): DiscoveredImage[] | null {\n const key = this.getImagesCacheKey(collection, contentId);\n const entry = this.imagesCache.get(key);\n if (this.isValid(entry)) {\n return entry!.data;\n }\n return null;\n }\n\n /**\n * Set images cache for a content item\n *\n * @param collection - Collection name\n * @param contentId - Content ID\n * @param images - Discovered images to cache\n */\n setImages(\n collection: string,\n contentId: string,\n images: DiscoveredImage[]\n ): void {\n const key = this.getImagesCacheKey(collection, contentId);\n this.imagesCache.set(key, {\n data: images,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Invalidate image cache for a specific content item\n *\n * @param collection - Collection name\n * @param contentId - Content ID\n */\n invalidateImages(collection: string, contentId: string): void {\n const key = this.getImagesCacheKey(collection, contentId);\n this.imagesCache.delete(key);\n }\n\n /**\n * Invalidate all image caches for a collection\n *\n * @param collection - Collection name\n */\n invalidateCollectionImages(collection: string): void {\n const prefix = `${collection}:`;\n for (const key of this.imagesCache.keys()) {\n if (key.startsWith(prefix)) {\n this.imagesCache.delete(key);\n }\n }\n }\n\n /**\n * Invalidate all image caches\n */\n invalidateAllImages(): void {\n this.imagesCache.clear();\n }\n\n // ==================== Bulk Invalidation ====================\n\n /**\n * Invalidate all caches\n *\n * Called when a major change occurs that affects everything.\n */\n invalidateAll(): void {\n this.collectionsCache = null;\n this.contentCache.clear();\n this.imagesCache.clear();\n }\n\n /**\n * Handle file change event from watcher\n *\n * Intelligently invalidates only the affected caches.\n *\n * @param type - Type of file change (add, change, unlink)\n * @param collection - Collection that was affected\n * @param contentId - Optional content ID for targeted image cache invalidation\n */\n handleFileChange(\n type: \"add\" | \"change\" | \"unlink\",\n collection: string,\n contentId?: string\n ): void {\n // Always invalidate the affected collection's content cache\n this.invalidateContent(collection);\n\n // Invalidate image cache for the specific content item or entire collection\n if (contentId) {\n this.invalidateImages(collection, contentId);\n } else {\n this.invalidateCollectionImages(collection);\n }\n\n // For add/unlink, also invalidate collections cache (count changed)\n if (type === \"add\" || type === \"unlink\") {\n this.invalidateCollections();\n }\n }\n\n // ==================== Stats ====================\n\n /**\n * Get cache statistics for debugging\n *\n * @returns Object with cache stats\n */\n getStats(): {\n collectionsValid: boolean;\n contentCollections: string[];\n cachedImages: string[];\n ttl: number;\n hasWatcher: boolean;\n } {\n return {\n collectionsValid: this.isValid(this.collectionsCache),\n contentCollections: Array.from(this.contentCache.keys()).filter((key) =>\n this.isValid(this.contentCache.get(key))\n ),\n cachedImages: Array.from(this.imagesCache.keys()).filter((key) =>\n this.isValid(this.imagesCache.get(key))\n ),\n ttl: this.ttl,\n hasWatcher: this.hasWatcher,\n };\n }\n}\n\n/**\n * Global cache instance\n *\n * Shared across the server for consistent caching.\n */\nlet globalCache: ServerCache | null = null;\n\n/**\n * Get or create the global cache instance\n *\n * @param options - Cache options (only used on first call)\n * @returns The global cache instance\n */\nexport function getCache(options?: {\n ttl?: number;\n hasWatcher?: boolean;\n}): ServerCache {\n if (!globalCache) {\n globalCache = new ServerCache(options);\n }\n return globalCache;\n}\n\n/**\n * Reset the global cache instance\n *\n * Useful for testing or when configuration changes.\n */\nexport function resetCache(): void {\n globalCache = null;\n}\n","/**\n * @fileoverview API route handlers for Writenex\n *\n * This module provides the API router that handles CRUD operations\n * for content collections.\n *\n * ## API Endpoints:\n * - GET /api/collections - List all collections\n * - GET /api/content/:collection - List content in collection\n * - GET /api/content/:collection/:id - Get single content item\n * - POST /api/content/:collection - Create new content\n * - PUT /api/content/:collection/:id - Update content\n * - DELETE /api/content/:collection/:id - Delete content\n * - GET /api/images/:collection/:contentId - Discover images for content\n * - GET /api/images/:collection/:contentId/* - Serve image file\n * - POST /api/images - Upload image\n * - GET /api/versions/:collection/:id - List versions\n * - GET /api/versions/:collection/:id/:versionId - Get version\n * - POST /api/versions/:collection/:id - Create manual version\n * - POST /api/versions/:collection/:id/:versionId/restore - Restore version\n * - GET /api/versions/:collection/:id/:versionId/diff - Get diff data\n * - DELETE /api/versions/:collection/:id/:versionId - Delete version\n * - DELETE /api/versions/:collection/:id - Clear all versions\n *\n * @module @writenex/astro/server/routes\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { createReadStream, existsSync, statSync } from \"node:fs\";\nimport { join, extname } from \"node:path\";\nimport type { MiddlewareContext } from \"./middleware\";\nimport {\n sendJson,\n sendError,\n sendWritenexError,\n parseQueryParams,\n parseJsonBody,\n} from \"./middleware\";\nimport {\n ApiBadRequestError,\n ApiMethodNotAllowedError,\n CollectionNotFoundError,\n CollectionDiscoveryError,\n ContentNotFoundError,\n ImageInvalidTypeError,\n ImageNotFoundError,\n PathTraversalError,\n VersionNotFoundError,\n isWritenexError,\n wrapError,\n WritenexErrorCode,\n} from \"@/core/errors\";\nimport { getCache } from \"./cache\";\nimport { discoverCollections, mergeCollections } from \"@/discovery/collections\";\nimport { getCollectionSummaries, readContentFile } from \"@/filesystem/reader\";\nimport {\n createContent,\n updateContent,\n deleteContent,\n getContentFilePath,\n} from \"@/filesystem/writer\";\nimport {\n uploadImage,\n parseMultipartFormData,\n isValidImageFile,\n discoverContentImages,\n} from \"@/filesystem/images\";\nimport {\n getVersions,\n getVersion,\n saveVersion,\n restoreVersion,\n deleteVersion,\n clearVersions,\n} from \"@/filesystem/versions\";\nimport type { VersionHistoryConfig } from \"@/types\";\n\n/**\n * API route handler function type\n */\ntype RouteHandler = (\n req: IncomingMessage,\n res: ServerResponse,\n params: RouteParams,\n context: MiddlewareContext\n) => Promise<void>;\n\n/**\n * Route parameters extracted from URL\n */\ninterface RouteParams {\n collection?: string;\n id?: string;\n versionId?: string;\n query: Record<string, string>;\n}\n\n/**\n * Create the API router\n *\n * @param context - Middleware context\n * @returns Router function that handles API requests\n */\nexport function createApiRouter(\n context: MiddlewareContext\n): (req: IncomingMessage, res: ServerResponse, path: string) => Promise<void> {\n return async (req, res, path) => {\n const method = req.method?.toUpperCase() ?? \"GET\";\n const query = parseQueryParams(req.url ?? \"\");\n\n // Strip query string from path before parsing segments\n const pathWithoutQuery = path.split(\"?\")[0] ?? path;\n\n // Parse route segments\n const segments = pathWithoutQuery.split(\"/\").filter(Boolean);\n const params: RouteParams = { query };\n\n // Route: /collections\n if (segments[0] === \"collections\") {\n if (method === \"GET\") {\n return handleGetCollections(req, res, params, context);\n }\n return sendWritenexError(\n res,\n new ApiMethodNotAllowedError(method, [\"GET\"])\n );\n }\n\n // Route: /config or /config/path\n if (segments[0] === \"config\") {\n if (method === \"GET\") {\n if (segments[1] === \"path\") {\n return handleGetConfigPath(req, res, params, context);\n }\n return handleGetConfig(req, res, params, context);\n }\n return sendWritenexError(\n res,\n new ApiMethodNotAllowedError(method, [\"GET\"])\n );\n }\n\n // Route: /content/:collection/:id?\n if (segments[0] === \"content\") {\n params.collection = segments[1];\n params.id = segments[2];\n\n switch (method) {\n case \"GET\":\n if (params.id) {\n return handleGetContent(req, res, params, context);\n }\n return handleListContent(req, res, params, context);\n case \"POST\":\n return handleCreateContent(req, res, params, context);\n case \"PUT\":\n return handleUpdateContent(req, res, params, context);\n case \"DELETE\":\n return handleDeleteContent(req, res, params, context);\n default:\n return sendWritenexError(\n res,\n new ApiMethodNotAllowedError(method, [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n ])\n );\n }\n }\n\n // Route: /images/:collection/:contentId - Image discovery\n // Route: /images/:collection/:contentId/* - Serve image file\n if (segments[0] === \"images\") {\n params.collection = segments[1];\n params.id = segments[2];\n\n // Check if this is a file request (has more segments after contentId)\n if (\n method === \"GET\" &&\n params.collection &&\n params.id &&\n segments.length > 3\n ) {\n // Serve image file: /images/:collection/:contentId/path/to/image.jpg\n const imagePath = segments.slice(3).join(\"/\");\n return handleServeImage(req, res, params, imagePath, context);\n }\n\n if (method === \"GET\" && params.collection && params.id) {\n return handleImageDiscovery(req, res, params, context);\n }\n if (method === \"POST\") {\n return handleImageUpload(req, res, params, context);\n }\n return sendWritenexError(\n res,\n new ApiMethodNotAllowedError(method, [\"GET\", \"POST\"])\n );\n }\n\n // Route: /versions/:collection/:id/:versionId?\n if (segments[0] === \"versions\") {\n params.collection = segments[1];\n params.id = segments[2];\n params.versionId = segments[3];\n\n // Check for special action routes (restore, diff)\n const action = segments[4];\n\n switch (method) {\n case \"GET\":\n if (params.versionId) {\n // Check if this is a diff request\n if (action === \"diff\") {\n return handleGetVersionDiff(req, res, params, context);\n }\n return handleGetVersion(req, res, params, context);\n }\n return handleListVersions(req, res, params, context);\n case \"POST\":\n if (params.versionId && action === \"restore\") {\n return handleRestoreVersion(req, res, params, context);\n }\n if (!params.versionId) {\n return handleCreateVersion(req, res, params, context);\n }\n return sendWritenexError(\n res,\n new ApiMethodNotAllowedError(method, [\"GET\", \"POST\", \"DELETE\"])\n );\n case \"DELETE\":\n if (params.versionId) {\n return handleDeleteVersion(req, res, params, context);\n }\n return handleClearVersions(req, res, params, context);\n default:\n return sendWritenexError(\n res,\n new ApiMethodNotAllowedError(method, [\"GET\", \"POST\", \"DELETE\"])\n );\n }\n }\n\n // Route: /health (for testing)\n if (segments[0] === \"health\") {\n return sendJson(res, {\n status: \"ok\",\n timestamp: new Date().toISOString(),\n });\n }\n\n // Unknown route\n return sendError(res, \"Not found\", 404);\n };\n}\n\n/**\n * GET /api/config - Get current configuration\n *\n * Returns the current Writenex configuration including image settings\n * and Astro's trailingSlash setting for preview URLs.\n */\nconst handleGetConfig: RouteHandler = async (_req, res, _params, context) => {\n const { config, trailingSlash } = context;\n\n sendJson(res, {\n images: config.images,\n editor: config.editor,\n trailingSlash,\n });\n};\n\n/**\n * GET /api/config/path - Get config file path\n *\n * Returns the absolute path to the configuration file for opening in editor.\n * Also returns the project root for reference.\n */\nconst handleGetConfigPath: RouteHandler = async (\n _req,\n res,\n _params,\n context\n) => {\n const { projectRoot } = context;\n\n // Import findConfigFile from config loader\n const { findConfigFile } = await import(\"@/config/loader\");\n const configPath = findConfigFile(projectRoot);\n\n sendJson(res, {\n configPath,\n projectRoot,\n hasConfigFile: configPath !== null,\n });\n};\n\n/**\n * GET /api/collections - List all collections\n *\n * Returns discovered and configured collections with metadata.\n * Results are cached for performance.\n */\nconst handleGetCollections: RouteHandler = async (\n _req,\n res,\n _params,\n context\n) => {\n const { config, projectRoot } = context;\n const cache = getCache();\n\n try {\n // Try to get from cache first\n let collections = cache.getCollections();\n\n if (!collections) {\n // Cache miss - discover and merge collections\n const discovered = await discoverCollections(projectRoot);\n collections = mergeCollections(discovered, config.collections);\n\n // Store in cache\n cache.setCollections(collections);\n }\n\n sendJson(res, { collections });\n } catch (error) {\n const wrappedError = isWritenexError(error)\n ? error\n : new CollectionDiscoveryError(\n join(projectRoot, \"src/content\"),\n error instanceof Error ? error : undefined\n );\n sendWritenexError(res, wrappedError);\n }\n};\n\n/**\n * GET /api/content/:collection - List content in collection\n *\n * Query params:\n * - draft: Include drafts (default: false)\n * - sort: Sort field (default: pubDate)\n * - order: Sort order (asc/desc, default: desc)\n *\n * Results are cached for performance.\n */\nconst handleListContent: RouteHandler = async (_req, res, params, context) => {\n const { collection, query } = params;\n const { projectRoot } = context;\n\n if (!collection) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Collection name required\")\n );\n }\n\n const cache = getCache();\n\n try {\n const collectionPath = join(projectRoot, \"src/content\", collection);\n\n // Check if collection exists\n if (!existsSync(collectionPath)) {\n return sendWritenexError(res, new CollectionNotFoundError(collection));\n }\n\n // Parse query parameters\n const includeDrafts = query.draft === \"true\";\n const sortBy = query.sort ?? \"pubDate\";\n const sortOrder = (query.order as \"asc\" | \"desc\") ?? \"desc\";\n\n // Try to get from cache first (only for default queries)\n // We cache the \"all content\" query (includeDrafts=true, default sort)\n const isDefaultQuery =\n includeDrafts && sortBy === \"pubDate\" && sortOrder === \"desc\";\n\n let items = isDefaultQuery ? cache.getContent(collection) : null;\n\n if (!items) {\n items = await getCollectionSummaries(collectionPath, {\n includeDrafts,\n sortBy,\n sortOrder,\n });\n\n // Cache only the \"all content\" query\n if (isDefaultQuery) {\n cache.setContent(collection, items);\n }\n }\n\n sendJson(res, {\n items,\n total: items.length,\n });\n } catch (error) {\n console.error(\"[writenex] List content error:\", error);\n const wrappedError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.API_INTERNAL_ERROR);\n sendWritenexError(res, wrappedError);\n }\n};\n\n/**\n * GET /api/content/:collection/:id - Get single content item\n */\nconst handleGetContent: RouteHandler = async (_req, res, params, context) => {\n const { collection, id } = params;\n const { projectRoot } = context;\n\n if (!collection || !id) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Collection and content ID required\")\n );\n }\n\n try {\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const filePath = getContentFilePath(collectionPath, id);\n\n if (!filePath) {\n return sendWritenexError(res, new ContentNotFoundError(collection, id));\n }\n\n const result = await readContentFile(filePath, collectionPath);\n\n if (!result.success || !result.content) {\n return sendError(res, result.error ?? \"Failed to read content\", 500);\n }\n\n sendJson(res, result.content);\n } catch (error) {\n const wrappedError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.API_INTERNAL_ERROR);\n sendWritenexError(res, wrappedError);\n }\n};\n\n/**\n * POST /api/content/:collection - Create new content\n *\n * Automatically detects the file pattern from existing content in the collection\n * and creates new content following the same pattern.\n */\nconst handleCreateContent: RouteHandler = async (req, res, params, context) => {\n const { collection } = params;\n const { projectRoot, config } = context;\n\n if (!collection) {\n return sendError(res, \"Collection name required\", 400);\n }\n\n try {\n const body = await parseJsonBody(req);\n\n if (!body || typeof body !== \"object\") {\n return sendError(res, \"Invalid request body\", 400);\n }\n\n const {\n frontmatter,\n body: contentBody,\n slug,\n } = body as {\n frontmatter?: Record<string, unknown>;\n body?: string;\n slug?: string;\n };\n\n if (!frontmatter) {\n return sendError(res, \"Frontmatter is required\", 400);\n }\n\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const cache = getCache();\n\n // Get the file pattern for this collection\n let filePattern: string | undefined;\n\n // First, check if there's a configured pattern for this collection\n const configuredCollection = config.collections.find(\n (c) => c.name === collection\n );\n if (configuredCollection?.filePattern) {\n filePattern = configuredCollection.filePattern;\n } else {\n // Otherwise, get the detected pattern from discovered collections\n let collections = cache.getCollections();\n if (!collections) {\n const discovered = await discoverCollections(projectRoot);\n collections = mergeCollections(discovered, config.collections);\n cache.setCollections(collections);\n }\n\n const discoveredCollection = collections.find(\n (c) => c.name === collection\n );\n if (discoveredCollection?.filePattern) {\n filePattern = discoveredCollection.filePattern;\n }\n }\n\n const result = await createContent(collectionPath, {\n frontmatter,\n body: contentBody ?? \"\",\n slug,\n filePattern,\n });\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to create content\", 500);\n }\n\n // Invalidate cache for this collection (new content added)\n cache.handleFileChange(\"add\", collection);\n\n sendJson(res, {\n success: true,\n id: result.id,\n path: result.path,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to create content: ${message}`, 500);\n }\n};\n\n/**\n * PUT /api/content/:collection/:id - Update content\n *\n * Creates a version snapshot of the current content before updating\n * when version history is enabled in configuration.\n *\n * Supports conflict detection via expectedMtime parameter:\n * - If expectedMtime is provided and differs from current file mtime,\n * returns 409 Conflict with both versions for client-side resolution.\n *\n * Request body:\n * {\n * frontmatter?: Record<string, unknown>;\n * body?: string;\n * expectedMtime?: number; // For conflict detection\n * forceOverwrite?: boolean; // Skip conflict check (use with caution)\n * }\n *\n * Response on conflict (409):\n * {\n * error: string;\n * code: \"CONTENT_CONFLICT\";\n * serverContent: string;\n * serverMtime: number;\n * clientMtime: number;\n * }\n */\nconst handleUpdateContent: RouteHandler = async (req, res, params, context) => {\n const { collection, id } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Collection and content ID required\")\n );\n }\n\n try {\n const body = await parseJsonBody(req);\n\n if (!body || typeof body !== \"object\") {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Invalid request body\")\n );\n }\n\n const {\n frontmatter,\n body: contentBody,\n expectedMtime,\n forceOverwrite,\n } = body as {\n frontmatter?: Record<string, unknown>;\n body?: string;\n expectedMtime?: number;\n forceOverwrite?: boolean;\n };\n\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const filePath = getContentFilePath(collectionPath, id);\n\n if (!filePath) {\n return sendWritenexError(res, new ContentNotFoundError(collection, id));\n }\n\n // Pass version history config to updateContent for automatic version creation\n // Note: config.versionHistory is guaranteed to have all required fields\n // because applyConfigDefaults() applies DEFAULT_VERSION_HISTORY_CONFIG\n const result = await updateContent(filePath, collectionPath, {\n frontmatter,\n body: contentBody,\n projectRoot,\n collection,\n versionHistoryConfig: config.versionHistory as Required<\n typeof config.versionHistory\n >,\n // Only check mtime if not forcing overwrite\n expectedMtime: forceOverwrite ? undefined : expectedMtime,\n });\n\n // Handle conflict error specially\n if (!result.success && result.conflict) {\n return sendWritenexError(res, result.conflict);\n }\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to update content\", 500);\n }\n\n // Invalidate cache for this collection (content modified)\n const cache = getCache();\n cache.handleFileChange(\"change\", collection);\n\n sendJson(res, {\n success: true,\n id: result.id,\n path: result.path,\n mtime: result.mtime,\n });\n } catch (error) {\n const wrappedError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.API_INTERNAL_ERROR);\n sendWritenexError(res, wrappedError);\n }\n};\n\n/**\n * DELETE /api/content/:collection/:id - Delete content\n */\nconst handleDeleteContent: RouteHandler = async (\n _req,\n res,\n params,\n context\n) => {\n const { collection, id } = params;\n const { projectRoot } = context;\n\n if (!collection || !id) {\n return sendError(res, \"Collection and content ID required\", 400);\n }\n\n try {\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const filePath = getContentFilePath(collectionPath, id);\n\n if (!filePath) {\n return sendError(\n res,\n `Content '${id}' not found in '${collection}'`,\n 404\n );\n }\n\n const result = await deleteContent(filePath);\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to delete content\", 500);\n }\n\n // Invalidate cache for this collection (content removed)\n const cache = getCache();\n cache.handleFileChange(\"unlink\", collection);\n\n sendJson(res, {\n success: true,\n path: result.path,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to delete content: ${message}`, 500);\n }\n};\n\n/**\n * POST /api/images - Upload image\n *\n * Expects multipart/form-data with:\n * - file: The image file\n * - collection: Collection name\n * - contentId: Content ID (slug)\n */\nconst handleImageUpload: RouteHandler = async (req, res, _params, context) => {\n const { projectRoot, config } = context;\n\n try {\n // Read raw body\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk);\n }\n const body = Buffer.concat(chunks);\n\n // Get content type\n const contentType = req.headers[\"content-type\"] ?? \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Content-Type must be multipart/form-data\")\n );\n }\n\n // Parse multipart data\n const { file, fields } = parseMultipartFormData(body, contentType);\n\n if (!file) {\n return sendWritenexError(res, new ApiBadRequestError(\"No file uploaded\"));\n }\n\n if (!fields.collection || !fields.contentId) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"collection and contentId are required\")\n );\n }\n\n if (!isValidImageFile(file.filename)) {\n return sendWritenexError(\n res,\n new ImageInvalidTypeError(file.filename, [\n \".jpg\",\n \".jpeg\",\n \".png\",\n \".gif\",\n \".webp\",\n \".avif\",\n \".svg\",\n ])\n );\n }\n\n // Upload image\n const result = await uploadImage({\n filename: file.filename,\n data: file.data,\n collection: fields.collection,\n contentId: fields.contentId,\n projectRoot,\n config: config.images,\n });\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to upload image\", 500);\n }\n\n sendJson(res, {\n success: true,\n path: result.path,\n url: result.url,\n });\n } catch (error) {\n const wrappedError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.IMAGE_UPLOAD_ERROR);\n sendWritenexError(res, wrappedError);\n }\n};\n\n/**\n * GET /api/images/:collection/:contentId - Discover images for content\n *\n * Returns list of discovered images for a content item.\n * Results are cached for performance.\n *\n * Response:\n * {\n * success: boolean;\n * images: DiscoveredImage[];\n * contentPath: string;\n * }\n */\nconst handleImageDiscovery: RouteHandler = async (\n _req,\n res,\n params,\n context\n) => {\n const { collection, id: contentId } = params;\n const { projectRoot } = context;\n\n if (!collection || !contentId) {\n return sendError(res, \"Collection and content ID required\", 400);\n }\n\n const cache = getCache();\n\n try {\n const collectionPath = join(projectRoot, \"src/content\", collection);\n\n // Check if collection exists by discovering collections\n let collections = cache.getCollections();\n if (!collections) {\n // Cache miss - discover collections\n collections = await discoverCollections(projectRoot);\n cache.setCollections(collections);\n }\n\n if (!collections.some((c) => c.name === collection)) {\n return sendError(res, `Collection '${collection}' not found`, 404);\n }\n\n // Check if content exists\n const contentFilePath = getContentFilePath(collectionPath, contentId);\n if (!contentFilePath) {\n return sendError(\n res,\n `Content '${contentId}' not found in '${collection}'`,\n 404\n );\n }\n\n // Try to get from cache first\n let images = cache.getImages(collection, contentId);\n\n if (!images) {\n // Cache miss - discover images\n const result = await discoverContentImages(collectionPath, contentId);\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to discover images\", 500);\n }\n\n images = result.images;\n\n // Store in cache\n cache.setImages(collection, contentId, images);\n }\n\n sendJson(res, {\n success: true,\n images,\n contentPath: contentFilePath,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to discover images: ${message}`, 500);\n }\n};\n\n/**\n * MIME types for image files\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".svg\": \"image/svg+xml\",\n};\n\n/**\n * GET /api/images/:collection/:contentId/* - Serve image file\n *\n * Serves an image file from the content folder.\n * This allows the editor to display images with relative paths.\n */\nconst handleServeImage = async (\n _req: IncomingMessage,\n res: ServerResponse,\n params: RouteParams,\n imagePath: string,\n context: MiddlewareContext\n): Promise<void> => {\n const { collection, id: contentId } = params;\n const { projectRoot } = context;\n\n if (!collection || !contentId) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Collection and content ID required\")\n );\n }\n\n try {\n const collectionPath = join(projectRoot, \"src/content\", collection);\n\n // Check if content exists\n const contentFilePath = getContentFilePath(collectionPath, contentId);\n if (!contentFilePath) {\n return sendWritenexError(\n res,\n new ContentNotFoundError(collection, contentId)\n );\n }\n\n // Build the full image path\n // For folder-based content (index.md), images are in the same folder\n // For flat files (slug.md), images are in a sibling folder with the same name\n let fullImagePath: string;\n\n if (\n contentFilePath.endsWith(\"/index.md\") ||\n contentFilePath.endsWith(\"/index.mdx\")\n ) {\n // Folder-based: content is at slug/index.md, images are at slug/imagePath\n const contentFolder = contentFilePath.replace(/\\/index\\.mdx?$/, \"\");\n fullImagePath = join(contentFolder, imagePath);\n } else {\n // Flat file: content is at slug.md, images are at slug/imagePath\n fullImagePath = join(collectionPath, contentId, imagePath);\n }\n\n // Security check: ensure the path is within the content folder\n const normalizedPath = join(fullImagePath);\n if (!normalizedPath.startsWith(collectionPath)) {\n return sendWritenexError(\n res,\n new PathTraversalError(imagePath, collectionPath)\n );\n }\n\n // Check if file exists\n if (!existsSync(fullImagePath)) {\n return sendWritenexError(res, new ImageNotFoundError(imagePath));\n }\n\n // Get file stats\n const stats = statSync(fullImagePath);\n if (!stats.isFile()) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Requested path is not a file\")\n );\n }\n\n // Determine MIME type\n const ext = extname(fullImagePath).toLowerCase();\n const mimeType = IMAGE_MIME_TYPES[ext] ?? \"application/octet-stream\";\n\n // Set headers\n res.setHeader(\"Content-Type\", mimeType);\n res.setHeader(\"Content-Length\", stats.size);\n res.setHeader(\"Cache-Control\", \"public, max-age=3600\");\n\n // Stream the file\n const stream = createReadStream(fullImagePath);\n stream.pipe(res);\n } catch (error) {\n const wrappedError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.API_INTERNAL_ERROR);\n sendWritenexError(res, wrappedError);\n }\n};\n\n// =============================================================================\n// Version History Route Handlers\n// =============================================================================\n\n/**\n * Get the resolved version history config with all required fields\n *\n * @param config - The version history config from context\n * @returns Resolved config with all required fields\n */\nfunction getResolvedVersionConfig(\n config: VersionHistoryConfig | undefined\n): Required<VersionHistoryConfig> {\n return {\n enabled: config?.enabled ?? true,\n maxVersions: config?.maxVersions ?? 20,\n storagePath: config?.storagePath ?? \".writenex/versions\",\n };\n}\n\n/**\n * GET /api/versions/:collection/:id - List all versions\n *\n * Returns versions sorted by timestamp in descending order (newest first).\n *\n * Response:\n * {\n * success: boolean;\n * versions: VersionEntry[];\n * total: number;\n * }\n */\nconst handleListVersions: RouteHandler = async (_req, res, params, context) => {\n const { collection, id } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id) {\n return sendError(res, \"Collection and content ID required\", 400);\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n const versions = await getVersions(\n projectRoot,\n collection,\n id,\n versionConfig\n );\n\n sendJson(res, {\n success: true,\n versions,\n total: versions.length,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to list versions: ${message}`, 500);\n }\n};\n\n/**\n * GET /api/versions/:collection/:id/:versionId - Get specific version\n *\n * Returns the full content of a specific version.\n *\n * Response:\n * {\n * success: boolean;\n * version: Version;\n * }\n */\nconst handleGetVersion: RouteHandler = async (_req, res, params, context) => {\n const { collection, id, versionId } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id || !versionId) {\n return sendWritenexError(\n res,\n new ApiBadRequestError(\"Collection, content ID, and version ID required\")\n );\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n const version = await getVersion(\n projectRoot,\n collection,\n id,\n versionId,\n versionConfig\n );\n\n if (!version) {\n return sendWritenexError(\n res,\n new VersionNotFoundError(collection, id, versionId)\n );\n }\n\n sendJson(res, {\n success: true,\n version,\n });\n } catch (error) {\n const wrappedError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.API_INTERNAL_ERROR);\n sendWritenexError(res, wrappedError);\n }\n};\n\n/**\n * POST /api/versions/:collection/:id - Create manual version\n *\n * Creates a manual version snapshot with optional label.\n *\n * Request body:\n * {\n * label?: string;\n * }\n *\n * Response:\n * {\n * success: boolean;\n * version?: VersionEntry;\n * }\n */\nconst handleCreateVersion: RouteHandler = async (req, res, params, context) => {\n const { collection, id } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id) {\n return sendError(res, \"Collection and content ID required\", 400);\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n // Check if version history is enabled\n if (!versionConfig.enabled) {\n return sendError(res, \"Version history is disabled\", 400);\n }\n\n // Parse request body for optional label\n const body = await parseJsonBody(req);\n const label =\n body && typeof body === \"object\" && \"label\" in body\n ? String(body.label)\n : undefined;\n\n // Get current content\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const filePath = getContentFilePath(collectionPath, id);\n\n if (!filePath) {\n return sendError(\n res,\n `Content '${id}' not found in '${collection}'`,\n 404\n );\n }\n\n // Read current content\n const readResult = await readContentFile(filePath, collectionPath);\n\n if (!readResult.success || !readResult.content) {\n return sendError(res, readResult.error ?? \"Failed to read content\", 500);\n }\n\n // Create version\n const result = await saveVersion(\n projectRoot,\n collection,\n id,\n readResult.content.raw,\n versionConfig,\n { label }\n );\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to create version\", 500);\n }\n\n sendJson(res, {\n success: true,\n version: result.version,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to create version: ${message}`, 500);\n }\n};\n\n/**\n * POST /api/versions/:collection/:id/:versionId/restore - Restore version\n *\n * Restores a version to the current content file.\n * Creates a safety snapshot before restoring.\n *\n * Response:\n * {\n * success: boolean;\n * version?: VersionEntry;\n * content?: string;\n * safetySnapshot?: VersionEntry;\n * }\n */\nconst handleRestoreVersion: RouteHandler = async (\n _req,\n res,\n params,\n context\n) => {\n const { collection, id, versionId } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id || !versionId) {\n return sendError(\n res,\n \"Collection, content ID, and version ID required\",\n 400\n );\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n // Get content file path\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const filePath = getContentFilePath(collectionPath, id);\n\n if (!filePath) {\n return sendError(\n res,\n `Content '${id}' not found in '${collection}'`,\n 404\n );\n }\n\n // Restore version\n const result = await restoreVersion(\n projectRoot,\n collection,\n id,\n versionId,\n filePath,\n versionConfig\n );\n\n if (!result.success) {\n // Check if it's a not found error\n if (result.error?.includes(\"not found\")) {\n return sendError(res, result.error, 404);\n }\n return sendError(res, result.error ?? \"Failed to restore version\", 500);\n }\n\n // Invalidate cache for this collection (content modified)\n const cache = getCache();\n cache.handleFileChange(\"change\", collection);\n\n sendJson(res, {\n success: true,\n version: result.version,\n content: result.content,\n safetySnapshot: result.safetySnapshot,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to restore version: ${message}`, 500);\n }\n};\n\n/**\n * GET /api/versions/:collection/:id/:versionId/diff - Get diff data\n *\n * Returns both the version content and current content for comparison.\n *\n * Response:\n * {\n * success: boolean;\n * version: Version;\n * current: {\n * content: string;\n * frontmatter: Record<string, unknown>;\n * body: string;\n * };\n * }\n */\nconst handleGetVersionDiff: RouteHandler = async (\n _req,\n res,\n params,\n context\n) => {\n const { collection, id, versionId } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id || !versionId) {\n return sendError(\n res,\n \"Collection, content ID, and version ID required\",\n 400\n );\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n // Get version content\n const version = await getVersion(\n projectRoot,\n collection,\n id,\n versionId,\n versionConfig\n );\n\n if (!version) {\n return sendError(res, `Version '${versionId}' not found`, 404);\n }\n\n // Get current content\n const collectionPath = join(projectRoot, \"src/content\", collection);\n const filePath = getContentFilePath(collectionPath, id);\n\n if (!filePath) {\n return sendError(\n res,\n `Content '${id}' not found in '${collection}'`,\n 404\n );\n }\n\n const readResult = await readContentFile(filePath, collectionPath);\n\n if (!readResult.success || !readResult.content) {\n return sendError(\n res,\n readResult.error ?? \"Failed to read current content\",\n 500\n );\n }\n\n sendJson(res, {\n success: true,\n version,\n current: {\n content: readResult.content.raw,\n frontmatter: readResult.content.frontmatter,\n body: readResult.content.body,\n },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to get diff data: ${message}`, 500);\n }\n};\n\n/**\n * DELETE /api/versions/:collection/:id/:versionId - Delete specific version\n *\n * Deletes a specific version file and removes it from the manifest.\n *\n * Response:\n * {\n * success: boolean;\n * version?: VersionEntry;\n * }\n */\nconst handleDeleteVersion: RouteHandler = async (\n _req,\n res,\n params,\n context\n) => {\n const { collection, id, versionId } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id || !versionId) {\n return sendError(\n res,\n \"Collection, content ID, and version ID required\",\n 400\n );\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n const result = await deleteVersion(\n projectRoot,\n collection,\n id,\n versionId,\n versionConfig\n );\n\n if (!result.success) {\n // Check if it's a not found error\n if (result.error?.includes(\"not found\")) {\n return sendError(res, result.error, 404);\n }\n return sendError(res, result.error ?? \"Failed to delete version\", 500);\n }\n\n sendJson(res, {\n success: true,\n version: result.version,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to delete version: ${message}`, 500);\n }\n};\n\n/**\n * DELETE /api/versions/:collection/:id - Clear all versions\n *\n * Deletes all version files for a content item and resets the manifest.\n *\n * Response:\n * {\n * success: boolean;\n * }\n */\nconst handleClearVersions: RouteHandler = async (\n _req,\n res,\n params,\n context\n) => {\n const { collection, id } = params;\n const { projectRoot, config } = context;\n\n if (!collection || !id) {\n return sendError(res, \"Collection and content ID required\", 400);\n }\n\n try {\n const versionConfig = getResolvedVersionConfig(config.versionHistory);\n\n const result = await clearVersions(\n projectRoot,\n collection,\n id,\n versionConfig\n );\n\n if (!result.success) {\n return sendError(res, result.error ?? \"Failed to clear versions\", 500);\n }\n\n sendJson(res, {\n success: true,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n sendError(res, `Failed to clear versions: ${message}`, 500);\n }\n};\n","/**\n * @fileoverview Static asset serving for Writenex editor\n *\n * This module handles serving the editor UI HTML and static assets\n * (JavaScript, CSS) for the Writenex editor interface.\n *\n * ## Asset Strategy:\n * - In development: Serve from source with Vite transform\n * - In production: Serve pre-bundled assets from dist/client\n *\n * @module @writenex/astro/server/assets\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { readFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, extname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { MiddlewareContext } from \"./middleware\";\n\n/**\n * Get the package root directory\n *\n * This function determines the package root based on where the code is running from.\n * When installed from npm, the structure is:\n * node_modules/@writenex/astro/dist/index.js\n *\n * We need to find the package root to locate dist/client/ assets.\n */\nfunction getPackageRoot(): string {\n const currentFile = fileURLToPath(import.meta.url);\n const currentDir = fileURLToPath(new URL(\".\", import.meta.url));\n\n // When bundled, import.meta.url points to the dist/index.js file\n // We need to go up one level to get to package root\n if (currentFile.endsWith(\"dist/index.js\") || currentDir.endsWith(\"dist/\")) {\n return join(currentDir, \"..\");\n }\n\n // When running from source (development), we're in src/server/\n // Go up 2 levels to get to package root\n if (currentDir.includes(\"/src/\")) {\n return join(currentDir, \"..\", \"..\");\n }\n\n // Fallback: assume we're in dist\n return join(currentDir, \"..\");\n}\n\nconst PACKAGE_ROOT = getPackageRoot();\n\n/**\n * MIME types for static assets\n */\nconst MIME_TYPES: Record<string, string> = {\n \".js\": \"application/javascript\",\n \".mjs\": \"application/javascript\",\n \".css\": \"text/css\",\n \".json\": \"application/json\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n};\n\n/**\n * Serve the editor HTML page\n *\n * This generates the HTML shell that loads the React editor application.\n * The actual React components will be loaded via the bundled client assets.\n *\n * @param _req - The incoming request\n * @param res - The server response\n * @param context - Middleware context\n */\nexport async function serveEditorHtml(\n _req: IncomingMessage,\n res: ServerResponse,\n context: MiddlewareContext\n): Promise<void> {\n const { basePath } = context;\n\n const html = generateEditorHtml(basePath);\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.end(html);\n}\n\n/**\n * Serve static assets (JS, CSS, etc.)\n *\n * @param _req - The incoming request\n * @param res - The server response\n * @param assetPath - Path to the asset (relative to assets directory)\n * @param _context - Middleware context\n */\nexport async function serveAsset(\n _req: IncomingMessage,\n res: ServerResponse,\n assetPath: string,\n _context: MiddlewareContext\n): Promise<void> {\n // Determine asset location\n // Assets are always in dist/client (pre-bundled by tsup)\n const distPath = join(PACKAGE_ROOT, \"dist\", \"client\", assetPath);\n\n if (!existsSync(distPath)) {\n console.error(\"[writenex] Asset not found:\", distPath);\n res.statusCode = 404;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(`Asset not found: ${assetPath}`);\n return;\n }\n\n const filePath = distPath;\n\n try {\n const content = await readFile(filePath);\n const ext = extname(assetPath).toLowerCase();\n const mimeType = MIME_TYPES[ext] ?? \"application/octet-stream\";\n\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", mimeType);\n res.setHeader(\"Cache-Control\", \"public, max-age=31536000, immutable\");\n res.end(content);\n } catch (error) {\n console.error(`[writenex] Failed to serve asset: ${assetPath}`, error);\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"text/plain\");\n res.end(\"Failed to read asset\");\n }\n}\n\n/**\n * Generate the editor HTML shell\n *\n * This creates the HTML page that bootstraps the React editor application.\n * It includes:\n * - Meta tags for viewport and charset\n * - CSS for the editor\n * - React mount point\n * - JavaScript bundle\n *\n * @param basePath - Base path for the editor\n * @returns HTML string\n */\nfunction generateEditorHtml(basePath: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"robots\" content=\"noindex, nofollow\">\n <title>Writenex - Content Editor</title>\n \n <!-- Editor styles -->\n <link rel=\"stylesheet\" href=\"${basePath}/assets/index.css\">\n <link rel=\"stylesheet\" href=\"${basePath}/assets/styles.css\">\n \n <style>\n /* Critical CSS for initial load */\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n \n html, body, #root {\n height: 100%;\n width: 100%;\n }\n \n body {\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n background-color: #0a0a0a;\n color: #fafafa;\n }\n \n /* Loading state */\n .writenex-loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 1rem;\n }\n \n .writenex-loading-spinner {\n width: 40px;\n height: 40px;\n border: 3px solid rgba(59, 130, 246, 0.2);\n border-top-color: #3b82f6;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n \n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n \n .writenex-loading-text {\n color: #71717a;\n font-size: 0.875rem;\n }\n </style>\n</head>\n<body>\n <div id=\"root\">\n <!-- Loading state shown while React loads -->\n <div class=\"writenex-loading\">\n <div class=\"writenex-loading-spinner\"></div>\n <div class=\"writenex-loading-text\">Loading Writenex Editor...</div>\n </div>\n </div>\n \n <!-- Configuration for the client app -->\n <script>\n window.__WRITENEX_CONFIG__ = {\n basePath: \"${basePath}\",\n apiBase: \"${basePath}/api\",\n };\n </script>\n \n <!-- Editor application -->\n <script type=\"module\" src=\"${basePath}/assets/index.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * Get the path to bundled client assets\n *\n * @returns Path to the client dist directory\n */\nexport function getClientDistPath(): string {\n return join(PACKAGE_ROOT, \"dist\", \"client\");\n}\n\n/**\n * Check if client assets are bundled\n *\n * @returns True if bundled assets exist\n */\nexport function hasClientBundle(): boolean {\n const indexPath = join(getClientDistPath(), \"index.js\");\n return existsSync(indexPath);\n}\n","/**\n * @fileoverview Vite middleware for Writenex routes\n *\n * This module provides the main middleware handler that intercepts requests\n * to Writenex routes (/_writenex/*) and delegates to appropriate handlers.\n *\n * ## Route Structure:\n * - `/_writenex` - Editor UI (HTML page with React app)\n * - `/_writenex/api/*` - API endpoints for CRUD operations\n * - `/_writenex/assets/*` - Static assets (JS, CSS)\n *\n * @module @writenex/astro/server/middleware\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Connect } from \"vite\";\nimport type { WritenexConfig } from \"@/types\";\nimport { createApiRouter } from \"./routes\";\nimport { serveEditorHtml, serveAsset } from \"./assets\";\nimport type { WritenexError } from \"@/core/errors\";\nimport { WritenexErrorCode, isWritenexError, wrapError } from \"@/core/errors\";\n\n/**\n * Middleware context passed to handlers\n */\nexport interface MiddlewareContext {\n /** Base path for Writenex routes */\n basePath: string;\n /** Project root directory */\n projectRoot: string;\n /** Resolved Writenex configuration */\n config: Required<WritenexConfig>;\n /** Astro trailingSlash setting for preview URLs */\n trailingSlash: \"always\" | \"never\" | \"ignore\";\n}\n\n/**\n * Create the Writenex middleware handler\n *\n * @param context - Middleware context with configuration\n * @returns Connect middleware function\n *\n * @example\n * ```typescript\n * const middleware = createMiddleware({\n * basePath: '/_writenex',\n * projectRoot: '/path/to/project',\n * config: resolvedConfig,\n * });\n *\n * server.middlewares.use(middleware);\n * ```\n */\nexport function createMiddleware(\n context: MiddlewareContext\n): Connect.NextHandleFunction {\n const { basePath } = context;\n const apiRouter = createApiRouter(context);\n\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n next: Connect.NextFunction\n ) => {\n const url = req.url ?? \"\";\n\n // Only handle requests to our base path\n if (!url.startsWith(basePath)) {\n return next();\n }\n\n // Extract the path after base path\n const path = url.slice(basePath.length) || \"/\";\n\n try {\n // Handle API routes\n if (path.startsWith(\"/api/\")) {\n return await apiRouter(req, res, path.slice(4)); // Remove '/api' prefix\n }\n\n // Handle static assets\n if (path.startsWith(\"/assets/\")) {\n return await serveAsset(req, res, path.slice(8), context); // Remove '/assets' prefix\n }\n\n // Handle editor UI (root and any sub-routes for client-side routing)\n return await serveEditorHtml(req, res, context);\n } catch (error) {\n // Handle errors gracefully using WritenexError\n const writenexError = isWritenexError(error)\n ? error\n : wrapError(error, WritenexErrorCode.API_INTERNAL_ERROR);\n\n console.error(\n `[writenex] Middleware error [${writenexError.code}]: ${writenexError.message}`\n );\n\n res.statusCode = writenexError.httpStatus;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(writenexError.toJSON()));\n }\n };\n}\n\n/**\n * Parse URL query parameters\n *\n * @param url - The URL string to parse\n * @returns Object with query parameters\n */\nexport function parseQueryParams(url: string): Record<string, string> {\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex === -1) return {};\n\n const queryString = url.slice(queryIndex + 1);\n const params: Record<string, string> = {};\n\n for (const pair of queryString.split(\"&\")) {\n const [key, value] = pair.split(\"=\");\n if (key) {\n params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : \"\";\n }\n }\n\n return params;\n}\n\n/**\n * Parse request body as JSON\n *\n * @param req - The incoming request\n * @returns Parsed JSON body or null\n */\nexport async function parseJsonBody(\n req: IncomingMessage\n): Promise<unknown | null> {\n return new Promise((resolve) => {\n let body = \"\";\n\n req.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", () => {\n if (!body) {\n resolve(null);\n return;\n }\n\n try {\n resolve(JSON.parse(body));\n } catch {\n resolve(null);\n }\n });\n\n req.on(\"error\", () => {\n resolve(null);\n });\n });\n}\n\n/**\n * Send JSON response\n *\n * @param res - The server response\n * @param data - Data to send as JSON\n * @param statusCode - HTTP status code (default: 200)\n */\nexport function sendJson(\n res: ServerResponse,\n data: unknown,\n statusCode: number = 200\n): void {\n res.statusCode = statusCode;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify(data));\n}\n\n/**\n * Send error response\n *\n * @param res - The server response\n * @param message - Error message\n * @param statusCode - HTTP status code (default: 400)\n */\nexport function sendError(\n res: ServerResponse,\n message: string,\n statusCode: number = 400\n): void {\n sendJson(res, { error: message }, statusCode);\n}\n\n/**\n * Send WritenexError response\n *\n * Automatically uses the error's HTTP status code and formats\n * the response using the error's toJSON method.\n *\n * @param res - The server response\n * @param error - WritenexError instance\n */\nexport function sendWritenexError(\n res: ServerResponse,\n error: WritenexError\n): void {\n sendJson(res, error.toJSON(), error.httpStatus);\n}\n"],"mappings":"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","names":["existsSync","join","extname","join","existsSync","extname"]}
|