@pagesmith/core 0.3.0 → 0.4.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.
Files changed (72) hide show
  1. package/README.md +9 -4
  2. package/REFERENCE.md +5 -2
  3. package/dist/ai/index.d.mts +5 -3
  4. package/dist/ai/index.d.mts.map +1 -1
  5. package/dist/ai/index.mjs +300 -206
  6. package/dist/ai/index.mjs.map +1 -1
  7. package/dist/assets/index.d.mts +10 -1
  8. package/dist/assets/index.d.mts.map +1 -1
  9. package/dist/assets/index.mjs +2 -2
  10. package/dist/{assets-DXiWF_KI.mjs → assets-CAPOqQ_P.mjs} +42 -5
  11. package/dist/assets-CAPOqQ_P.mjs.map +1 -0
  12. package/dist/{content-config-Bfe4W9us.d.mts → content-config-DJXUOcNG.d.mts} +49 -17
  13. package/dist/{content-config-Bfe4W9us.d.mts.map → content-config-DJXUOcNG.d.mts.map} +1 -1
  14. package/dist/{content-layer-DPK1EmfY.mjs → content-layer-B5enqWeJ.mjs} +123 -28
  15. package/dist/content-layer-B5enqWeJ.mjs.map +1 -0
  16. package/dist/content-layer-CpHYUYNN.d.mts +121 -0
  17. package/dist/content-layer-CpHYUYNN.d.mts.map +1 -0
  18. package/dist/create/index.d.mts.map +1 -1
  19. package/dist/create/index.mjs +26 -28
  20. package/dist/create/index.mjs.map +1 -1
  21. package/dist/css/index.d.mts +1 -1
  22. package/dist/{heading-BpDXnl-7.d.mts → heading-Dhvzlay-.d.mts} +1 -1
  23. package/dist/{heading-BpDXnl-7.d.mts.map → heading-Dhvzlay-.d.mts.map} +1 -1
  24. package/dist/{index-Bg9srb5U.d.mts → index-B7NRZAxd.d.mts} +1 -1
  25. package/dist/{index-Bg9srb5U.d.mts.map → index-B7NRZAxd.d.mts.map} +1 -1
  26. package/dist/{index-BBYkDxwI.d.mts → index-C0QFHYwb.d.mts} +1 -1
  27. package/dist/{index-BBYkDxwI.d.mts.map → index-C0QFHYwb.d.mts.map} +1 -1
  28. package/dist/{index-CbOKbkjJ.d.mts → index-CJkBs8YQ.d.mts} +2 -2
  29. package/dist/index-CJkBs8YQ.d.mts.map +1 -0
  30. package/dist/{index-YXQxMV6J.d.mts → index-DCznbvaV.d.mts} +2 -2
  31. package/dist/{index-YXQxMV6J.d.mts.map → index-DCznbvaV.d.mts.map} +1 -1
  32. package/dist/index.d.mts +15 -99
  33. package/dist/index.d.mts.map +1 -1
  34. package/dist/index.mjs +13 -9
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/loaders/index.d.mts +2 -2
  37. package/dist/markdown/index.d.mts +2 -2
  38. package/dist/markdown/index.mjs +1 -1
  39. package/dist/{markdown-CyrHoDhP.mjs → markdown-BmDJgYeB.mjs} +23 -1
  40. package/dist/{markdown-CyrHoDhP.mjs.map → markdown-BmDJgYeB.mjs.map} +1 -1
  41. package/dist/mcp/index.d.mts +23 -0
  42. package/dist/mcp/index.d.mts.map +1 -0
  43. package/dist/mcp/index.mjs +2 -0
  44. package/dist/mcp/server.d.mts +13 -0
  45. package/dist/mcp/server.d.mts.map +1 -0
  46. package/dist/mcp/server.mjs +2 -0
  47. package/dist/runtime/index.mjs +1 -1
  48. package/dist/schemas/index.d.mts +3 -3
  49. package/dist/server-D3DHoh5f.mjs +202 -0
  50. package/dist/server-D3DHoh5f.mjs.map +1 -0
  51. package/dist/ssg-utils/index.d.mts +61 -0
  52. package/dist/ssg-utils/index.d.mts.map +1 -0
  53. package/dist/ssg-utils/index.mjs +118 -0
  54. package/dist/ssg-utils/index.mjs.map +1 -0
  55. package/dist/{types-Cn52sdoq.d.mts → types-B-V5qemH.d.mts} +1 -1
  56. package/dist/{types-Cn52sdoq.d.mts.map → types-B-V5qemH.d.mts.map} +1 -1
  57. package/dist/vite/index.d.mts +69 -34
  58. package/dist/vite/index.d.mts.map +1 -1
  59. package/dist/vite/index.mjs +294 -226
  60. package/dist/vite/index.mjs.map +1 -1
  61. package/docs/agents/AGENTS.md.template +9 -0
  62. package/docs/agents/changelog-notes.md +15 -0
  63. package/docs/agents/errors.md +96 -0
  64. package/docs/agents/migration.md +25 -0
  65. package/docs/agents/recipes.md +26 -0
  66. package/docs/agents/usage.md +58 -0
  67. package/docs/llms-full.txt +53 -0
  68. package/docs/llms.txt +29 -0
  69. package/package.json +56 -4
  70. package/dist/assets-DXiWF_KI.mjs.map +0 -1
  71. package/dist/content-layer-DPK1EmfY.mjs.map +0 -1
  72. package/dist/index-CbOKbkjJ.d.mts.map +0 -1
@@ -0,0 +1,202 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { basename, resolve } from "path";
3
+ import { z } from "zod";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ //#region src/mcp/shared.ts
7
+ /**
8
+ * Shared MCP server utilities.
9
+ *
10
+ * Common helpers used by both @pagesmith/core and @pagesmith/docs MCP servers.
11
+ */
12
+ /** Read the package version from a package.json relative to the calling module. */
13
+ function getPackageVersion(moduleDir) {
14
+ const pkgPath = resolve(moduleDir, "..", "..", "package.json");
15
+ return JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
16
+ }
17
+ /** Resolve a doc file path relative to the package root. */
18
+ function resolvePackageDocPath(moduleDir, relativePath) {
19
+ return resolve(moduleDir, "..", "..", relativePath);
20
+ }
21
+ /** Load a file as an MCP text resource response. Throws if the file doesn't exist. */
22
+ function asTextResource(uri, path) {
23
+ if (!existsSync(path)) throw new Error(`Resource file not found: ${path}`);
24
+ return { contents: [{
25
+ uri,
26
+ mimeType: "text/markdown",
27
+ text: readFileSync(path, "utf-8")
28
+ }] };
29
+ }
30
+ //#endregion
31
+ //#region src/mcp/server.ts
32
+ function getLoaderType(loader) {
33
+ if (typeof loader === "string") return loader;
34
+ return loader.name ?? "custom";
35
+ }
36
+ function getSchemaFieldNames(schema) {
37
+ if (schema instanceof z.ZodObject) return Object.keys(schema.shape);
38
+ if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) return getSchemaFieldNames(schema.unwrap());
39
+ }
40
+ function createCoreMcpServer(options) {
41
+ const { layer } = options;
42
+ const baseRoot = resolve(options.rootDir ?? process.cwd());
43
+ const server = new McpServer({
44
+ name: "@pagesmith/core-mcp",
45
+ version: getPackageVersion(import.meta.dirname)
46
+ }, { instructions: ["Use core_* tools to inspect @pagesmith/core content layers.", `Root directory: ${baseRoot}`].join("\n") });
47
+ server.registerTool("core_list_collections", {
48
+ description: "List all configured collections with their loader type, directory, and schema field names.",
49
+ inputSchema: {}
50
+ }, async () => {
51
+ const collections = layer.getCollectionNames().map((name) => {
52
+ const def = layer.getCollectionDef(name);
53
+ return {
54
+ name,
55
+ loader: def ? getLoaderType(def.loader) : "unknown",
56
+ directory: def?.directory,
57
+ schemaFields: def ? getSchemaFieldNames(def.schema) : void 0
58
+ };
59
+ });
60
+ return { content: [{
61
+ type: "text",
62
+ text: JSON.stringify(collections, null, 2)
63
+ }] };
64
+ });
65
+ server.registerTool("core_list_entries", {
66
+ description: "List entries in a collection with slug, title, description, and file path. Does not render content. Supports pagination via limit/offset.",
67
+ inputSchema: {
68
+ collection: z.string().describe("Collection name"),
69
+ limit: z.number().int().min(1).max(200).default(50).describe("Maximum number of entries to return (default 50)"),
70
+ offset: z.number().int().min(0).default(0).describe("Number of entries to skip (default 0)")
71
+ }
72
+ }, async ({ collection, limit, offset }) => {
73
+ const entries = await layer.getCollection(collection);
74
+ const total = entries.length;
75
+ const items = entries.slice(offset, offset + limit).map((entry) => {
76
+ const data = entry.data ?? {};
77
+ return {
78
+ slug: entry.slug,
79
+ title: typeof data.title === "string" ? data.title : void 0,
80
+ description: typeof data.description === "string" ? data.description : void 0,
81
+ filePath: entry.filePath
82
+ };
83
+ });
84
+ return { content: [{
85
+ type: "text",
86
+ text: JSON.stringify({
87
+ collection,
88
+ total,
89
+ offset,
90
+ limit,
91
+ count: items.length,
92
+ entries: items
93
+ }, null, 2)
94
+ }] };
95
+ });
96
+ server.registerTool("core_get_entry", {
97
+ description: "Load a single content entry by collection name and slug. Returns slug, validated data, rendered HTML, headings, and read time.",
98
+ inputSchema: {
99
+ collection: z.string().describe("Collection name"),
100
+ slug: z.string().describe("Entry slug")
101
+ }
102
+ }, async ({ collection, slug }) => {
103
+ const entry = await layer.getEntry(collection, slug);
104
+ if (!entry) throw new Error(`Entry not found: ${collection}/${slug}`);
105
+ const rendered = await entry.render();
106
+ return { content: [{
107
+ type: "text",
108
+ text: JSON.stringify({
109
+ slug: entry.slug,
110
+ collection: entry.collection,
111
+ filePath: entry.filePath,
112
+ data: entry.data,
113
+ html: rendered.html,
114
+ headings: rendered.headings,
115
+ readTime: rendered.readTime
116
+ }, null, 2)
117
+ }] };
118
+ });
119
+ server.registerTool("core_validate", {
120
+ description: "Run validation on a specific collection or all collections. Returns structured validation results with error and warning counts.",
121
+ inputSchema: { collection: z.string().optional().describe("Collection name (omit to validate all)") }
122
+ }, async ({ collection }) => {
123
+ const results = await layer.validate(collection);
124
+ return { content: [{
125
+ type: "text",
126
+ text: JSON.stringify(results, null, 2)
127
+ }] };
128
+ });
129
+ server.registerTool("core_search_entries", {
130
+ description: "Search entries across collections by matching a query string against titles, descriptions, tags, and slugs. Case-insensitive. Returns up to 20 matches.",
131
+ inputSchema: {
132
+ query: z.string().describe("Search query string"),
133
+ collection: z.string().optional().describe("Limit search to a specific collection")
134
+ }
135
+ }, async ({ query, collection }) => {
136
+ if (!query.trim()) return { content: [{
137
+ type: "text",
138
+ text: JSON.stringify({
139
+ query,
140
+ matches: [],
141
+ count: 0
142
+ }, null, 2)
143
+ }] };
144
+ const queryLower = query.toLowerCase();
145
+ const maxResults = 20;
146
+ const collectionNames = collection ? [collection] : layer.getCollectionNames();
147
+ const matches = [];
148
+ for (const name of collectionNames) {
149
+ if (matches.length >= maxResults) break;
150
+ const entries = await layer.getCollection(name);
151
+ for (const entry of entries) {
152
+ if (matches.length >= maxResults) break;
153
+ const data = entry.data ?? {};
154
+ const title = typeof data.title === "string" ? data.title : "";
155
+ const description = typeof data.description === "string" ? data.description : "";
156
+ const tags = Array.isArray(data.tags) ? data.tags.join(" ") : "";
157
+ if (`${entry.slug} ${title} ${description} ${tags}`.toLowerCase().includes(queryLower)) matches.push({
158
+ collection: name,
159
+ slug: entry.slug,
160
+ title: title || void 0,
161
+ description: description || void 0,
162
+ filePath: entry.filePath
163
+ });
164
+ }
165
+ }
166
+ return { content: [{
167
+ type: "text",
168
+ text: JSON.stringify({
169
+ query,
170
+ collection: collection ?? null,
171
+ count: matches.length,
172
+ matches
173
+ }, null, 2)
174
+ }] };
175
+ });
176
+ server.registerResource("core-agent-usage", "pagesmith://core/agents/usage", {
177
+ title: "@pagesmith/core agent usage",
178
+ description: "Version-matched AI usage guide for @pagesmith/core.",
179
+ mimeType: "text/markdown"
180
+ }, async () => asTextResource("pagesmith://core/agents/usage", resolvePackageDocPath(import.meta.dirname, "docs/agents/usage.md")));
181
+ server.registerResource("core-llms-full", "pagesmith://core/llms-full", {
182
+ title: "@pagesmith/core llms-full",
183
+ description: "Version-matched full AI reference for @pagesmith/core.",
184
+ mimeType: "text/markdown"
185
+ }, async () => asTextResource("pagesmith://core/llms-full", resolvePackageDocPath(import.meta.dirname, "docs/llms-full.txt")));
186
+ server.registerResource("core-reference", "pagesmith://core/reference", {
187
+ title: "@pagesmith/core reference",
188
+ description: "Core package reference for content layer, schemas, loaders, and markdown pipeline.",
189
+ mimeType: "text/markdown"
190
+ }, async () => asTextResource("pagesmith://core/reference", resolvePackageDocPath(import.meta.dirname, "REFERENCE.md")));
191
+ return server;
192
+ }
193
+ async function startCoreMcpServer(options) {
194
+ const server = createCoreMcpServer(options);
195
+ const transport = new StdioServerTransport();
196
+ await server.connect(transport);
197
+ console.error(`[pagesmith:mcp] @pagesmith/core MCP server started (root=${basename(resolve(options.rootDir ?? process.cwd()))})`);
198
+ }
199
+ //#endregion
200
+ export { resolvePackageDocPath as a, getPackageVersion as i, startCoreMcpServer as n, asTextResource as r, createCoreMcpServer as t };
201
+
202
+ //# sourceMappingURL=server-D3DHoh5f.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-D3DHoh5f.mjs","names":[],"sources":["../src/mcp/shared.ts","../src/mcp/server.ts"],"sourcesContent":["/**\n * Shared MCP server utilities.\n *\n * Common helpers used by both @pagesmith/core and @pagesmith/docs MCP servers.\n */\n\nimport { existsSync, readFileSync } from 'fs'\nimport { resolve } from 'path'\n\n/** Read the package version from a package.json relative to the calling module. */\nexport function getPackageVersion(moduleDir: string): string {\n const pkgPath = resolve(moduleDir, '..', '..', 'package.json')\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string }\n return pkg.version ?? '0.0.0'\n}\n\n/** Resolve a doc file path relative to the package root. */\nexport function resolvePackageDocPath(moduleDir: string, relativePath: string): string {\n return resolve(moduleDir, '..', '..', relativePath)\n}\n\n/** Load a file as an MCP text resource response. Throws if the file doesn't exist. */\nexport function asTextResource(uri: string, path: string) {\n if (!existsSync(path)) {\n throw new Error(`Resource file not found: ${path}`)\n }\n\n return {\n contents: [\n {\n uri,\n mimeType: 'text/markdown',\n text: readFileSync(path, 'utf-8'),\n },\n ],\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { basename, resolve } from 'path'\nimport { z } from 'zod'\nimport type { ContentLayer } from '../content-layer.js'\nimport type { Loader } from '../loaders/types.js'\nimport { asTextResource, getPackageVersion, resolvePackageDocPath } from './shared.js'\n\nexport type CoreMcpServerOptions = {\n layer: ContentLayer\n rootDir?: string\n}\n\nfunction getLoaderType(loader: string | Loader): string {\n if (typeof loader === 'string') return loader\n return loader.name ?? 'custom'\n}\n\nfunction getSchemaFieldNames(schema: z.ZodType): string[] | undefined {\n if (schema instanceof z.ZodObject) {\n return Object.keys(schema.shape)\n }\n if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {\n return getSchemaFieldNames(schema.unwrap() as z.ZodType)\n }\n return undefined\n}\n\nexport function createCoreMcpServer(options: CoreMcpServerOptions): McpServer {\n const { layer } = options\n const baseRoot = resolve(options.rootDir ?? process.cwd())\n\n const server = new McpServer(\n {\n name: '@pagesmith/core-mcp',\n version: getPackageVersion(import.meta.dirname),\n },\n {\n instructions: [\n 'Use core_* tools to inspect @pagesmith/core content layers.',\n `Root directory: ${baseRoot}`,\n ].join('\\n'),\n },\n )\n\n // ── Tools ──\n\n server.registerTool(\n 'core_list_collections',\n {\n description:\n 'List all configured collections with their loader type, directory, and schema field names.',\n inputSchema: {},\n },\n async () => {\n const names = layer.getCollectionNames()\n const collections = names.map((name) => {\n const def = layer.getCollectionDef(name)\n return {\n name,\n loader: def ? getLoaderType(def.loader) : 'unknown',\n directory: def?.directory,\n schemaFields: def ? getSchemaFieldNames(def.schema) : undefined,\n }\n })\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(collections, null, 2),\n },\n ],\n }\n },\n )\n\n server.registerTool(\n 'core_list_entries',\n {\n description:\n 'List entries in a collection with slug, title, description, and file path. Does not render content. Supports pagination via limit/offset.',\n inputSchema: {\n collection: z.string().describe('Collection name'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(200)\n .default(50)\n .describe('Maximum number of entries to return (default 50)'),\n offset: z\n .number()\n .int()\n .min(0)\n .default(0)\n .describe('Number of entries to skip (default 0)'),\n },\n },\n async ({\n collection,\n limit,\n offset,\n }: {\n collection: string\n limit: number\n offset: number\n }) => {\n const entries = await layer.getCollection(collection)\n const total = entries.length\n const page = entries.slice(offset, offset + limit)\n\n const items = page.map((entry) => {\n const data = (entry.data ?? {}) as Record<string, unknown>\n return {\n slug: entry.slug,\n title: typeof data.title === 'string' ? data.title : undefined,\n description: typeof data.description === 'string' ? data.description : undefined,\n filePath: entry.filePath,\n }\n })\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n collection,\n total,\n offset,\n limit,\n count: items.length,\n entries: items,\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n )\n\n server.registerTool(\n 'core_get_entry',\n {\n description:\n 'Load a single content entry by collection name and slug. Returns slug, validated data, rendered HTML, headings, and read time.',\n inputSchema: {\n collection: z.string().describe('Collection name'),\n slug: z.string().describe('Entry slug'),\n },\n },\n async ({ collection, slug }: { collection: string; slug: string }) => {\n const entry = await layer.getEntry(collection, slug)\n if (!entry) {\n throw new Error(`Entry not found: ${collection}/${slug}`)\n }\n\n const rendered = await entry.render()\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n {\n slug: entry.slug,\n collection: entry.collection,\n filePath: entry.filePath,\n data: entry.data,\n html: rendered.html,\n headings: rendered.headings,\n readTime: rendered.readTime,\n },\n null,\n 2,\n ),\n },\n ],\n }\n },\n )\n\n server.registerTool(\n 'core_validate',\n {\n description:\n 'Run validation on a specific collection or all collections. Returns structured validation results with error and warning counts.',\n inputSchema: {\n collection: z.string().optional().describe('Collection name (omit to validate all)'),\n },\n },\n async ({ collection }: { collection?: string }) => {\n const results = await layer.validate(collection)\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(results, null, 2),\n },\n ],\n }\n },\n )\n\n server.registerTool(\n 'core_search_entries',\n {\n description:\n 'Search entries across collections by matching a query string against titles, descriptions, tags, and slugs. Case-insensitive. Returns up to 20 matches.',\n inputSchema: {\n query: z.string().describe('Search query string'),\n collection: z.string().optional().describe('Limit search to a specific collection'),\n },\n },\n async ({ query, collection }: { query: string; collection?: string }) => {\n if (!query.trim()) {\n return {\n content: [\n { type: 'text', text: JSON.stringify({ query, matches: [], count: 0 }, null, 2) },\n ],\n }\n }\n\n const queryLower = query.toLowerCase()\n const maxResults = 20\n const collectionNames = collection ? [collection] : layer.getCollectionNames()\n\n type SearchMatch = {\n collection: string\n slug: string\n title?: string\n description?: string\n filePath: string\n }\n\n const matches: SearchMatch[] = []\n\n for (const name of collectionNames) {\n if (matches.length >= maxResults) break\n const entries = await layer.getCollection(name)\n for (const entry of entries) {\n if (matches.length >= maxResults) break\n const data = (entry.data ?? {}) as Record<string, unknown>\n const title = typeof data.title === 'string' ? data.title : ''\n const description = typeof data.description === 'string' ? data.description : ''\n const tags = Array.isArray(data.tags) ? data.tags.join(' ') : ''\n const searchText = `${entry.slug} ${title} ${description} ${tags}`.toLowerCase()\n\n if (searchText.includes(queryLower)) {\n matches.push({\n collection: name,\n slug: entry.slug,\n title: title || undefined,\n description: description || undefined,\n filePath: entry.filePath,\n })\n }\n }\n }\n\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(\n { query, collection: collection ?? null, count: matches.length, matches },\n null,\n 2,\n ),\n },\n ],\n }\n },\n )\n\n // ── Resources ──\n\n server.registerResource(\n 'core-agent-usage',\n 'pagesmith://core/agents/usage',\n {\n title: '@pagesmith/core agent usage',\n description: 'Version-matched AI usage guide for @pagesmith/core.',\n mimeType: 'text/markdown',\n },\n async () =>\n asTextResource(\n 'pagesmith://core/agents/usage',\n resolvePackageDocPath(import.meta.dirname, 'docs/agents/usage.md'),\n ),\n )\n\n server.registerResource(\n 'core-llms-full',\n 'pagesmith://core/llms-full',\n {\n title: '@pagesmith/core llms-full',\n description: 'Version-matched full AI reference for @pagesmith/core.',\n mimeType: 'text/markdown',\n },\n async () =>\n asTextResource(\n 'pagesmith://core/llms-full',\n resolvePackageDocPath(import.meta.dirname, 'docs/llms-full.txt'),\n ),\n )\n\n server.registerResource(\n 'core-reference',\n 'pagesmith://core/reference',\n {\n title: '@pagesmith/core reference',\n description:\n 'Core package reference for content layer, schemas, loaders, and markdown pipeline.',\n mimeType: 'text/markdown',\n },\n async () =>\n asTextResource(\n 'pagesmith://core/reference',\n resolvePackageDocPath(import.meta.dirname, 'REFERENCE.md'),\n ),\n )\n\n return server\n}\n\nexport async function startCoreMcpServer(options: CoreMcpServerOptions): Promise<void> {\n const server = createCoreMcpServer(options)\n const transport = new StdioServerTransport()\n await server.connect(transport)\n // Keep logs on stderr; stdout is reserved for MCP protocol messages.\n console.error(\n `[pagesmith:mcp] @pagesmith/core MCP server started (root=${basename(resolve(options.rootDir ?? process.cwd()))})`,\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ //#region src/ssg-utils/index.d.ts
2
+ /**
3
+ * Shared SSG utilities for framework example sites.
4
+ *
5
+ * Provides types that match virtual module output, icon SVGs,
6
+ * route helpers, date formatting, and HTML document rendering
7
+ * used across all framework examples.
8
+ */
9
+ type Heading = {
10
+ depth: number;
11
+ slug: string;
12
+ text: string;
13
+ };
14
+ type MarkdownEntry<T extends Record<string, unknown> = Record<string, unknown>> = {
15
+ contentSlug: string;
16
+ html: string;
17
+ headings: Heading[];
18
+ frontmatter: T;
19
+ };
20
+ type NavEntry = {
21
+ slug: string;
22
+ title: string;
23
+ description?: string;
24
+ url: string;
25
+ date?: string;
26
+ tags?: string[];
27
+ };
28
+ type NavGroup = {
29
+ series: string;
30
+ items: NavEntry[];
31
+ };
32
+ declare const menuIcon = "<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"M3 5h14M3 10h14M3 15h14\"/></svg>";
33
+ declare const closeIcon = "<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"m5 5 10 10M15 5 5 15\"/></svg>";
34
+ declare const searchIcon = "<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><circle cx=\"8.5\" cy=\"8.5\" r=\"5.5\"/><path d=\"m13 13 4 4\"/></svg>";
35
+ declare function normalizeRoute(url: string, base: string): string;
36
+ declare function leafSlug(contentSlug: string, collection: string): string;
37
+ declare function routeFor(entry: MarkdownEntry, collection: string, options?: {
38
+ topLevel?: string[];
39
+ }): string;
40
+ declare function getTime(date: string | Date | undefined): number;
41
+ declare function toIso(date: string | Date | undefined): string | undefined;
42
+ declare function formatDate(iso: string): string;
43
+ declare function estimateReadTime(html: string): number;
44
+ declare function escapeHtml(value: string): string;
45
+ declare function buildNavEntries(entries: MarkdownEntry[], base: string, section: string): NavEntry[];
46
+ declare function groupByField(entries: MarkdownEntry[], base: string, section: string, field: string, fallback?: string): NavGroup[];
47
+ type DocumentShellOptions = {
48
+ title: string;
49
+ description?: string;
50
+ basePath: string;
51
+ cssPath: string;
52
+ jsPath?: string;
53
+ searchEnabled?: boolean;
54
+ bodyHtml: string;
55
+ sidebarHtml?: string;
56
+ headHtml?: string;
57
+ };
58
+ declare function renderDocumentShell(options: DocumentShellOptions): string;
59
+ //#endregion
60
+ export { DocumentShellOptions, Heading, MarkdownEntry, NavEntry, NavGroup, buildNavEntries, closeIcon, escapeHtml, estimateReadTime, formatDate, getTime, groupByField, leafSlug, menuIcon, normalizeRoute, renderDocumentShell, routeFor, searchIcon, toIso };
61
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/ssg-utils/index.ts"],"mappings":";;AAUA;;;;;;KAAY,OAAA;EACV,KAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,KAGU,aAAA,WAAwB,MAAA,oBAA0B,MAAA;EAC5D,WAAA;EACA,IAAA;EACA,QAAA,EAAU,OAAA;EACV,WAAA,EAAa,CAAA;AAAA;AAAA,KAGH,QAAA;EACV,IAAA;EACA,KAAA;EACA,WAAA;EACA,GAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,KAGU,QAAA;EACV,MAAA;EACA,KAAA,EAAO,QAAA;AAAA;AAAA,cAKI,QAAA;AAAA,cAEA,SAAA;AAAA,cAEA,UAAA;AAAA,iBAKG,cAAA,CAAe,GAAA,UAAa,IAAA;AAAA,iBAO5B,QAAA,CAAS,WAAA,UAAqB,UAAA;AAAA,iBAI9B,QAAA,CACd,KAAA,EAAO,aAAA,EACP,UAAA,UACA,OAAA;EAAY,QAAA;AAAA;AAAA,iBASE,OAAA,CAAQ,IAAA,WAAe,IAAA;AAAA,iBAKvB,KAAA,CAAM,IAAA,WAAe,IAAA;AAAA,iBAKrB,UAAA,CAAW,GAAA;AAAA,iBAWX,gBAAA,CAAiB,IAAA;AAAA,iBAKjB,UAAA,CAAW,KAAA;AAAA,iBASX,eAAA,CACd,OAAA,EAAS,aAAA,IACT,IAAA,UACA,OAAA,WACC,QAAA;AAAA,iBAWa,YAAA,CACd,OAAA,EAAS,aAAA,IACT,IAAA,UACA,OAAA,UACA,KAAA,UACA,QAAA,YACC,QAAA;AAAA,KAuBS,oBAAA;EACV,KAAA;EACA,WAAA;EACA,QAAA;EACA,OAAA;EACA,MAAA;EACA,aAAA;EACA,QAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,OAAA,EAAS,oBAAA"}
@@ -0,0 +1,118 @@
1
+ //#region src/ssg-utils/index.ts
2
+ const menuIcon = "<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"M3 5h14M3 10h14M3 15h14\"/></svg>";
3
+ const closeIcon = "<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"m5 5 10 10M15 5 5 15\"/></svg>";
4
+ const searchIcon = "<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><circle cx=\"8.5\" cy=\"8.5\" r=\"5.5\"/><path d=\"m13 13 4 4\"/></svg>";
5
+ function normalizeRoute(url, base) {
6
+ if (!base || !url.startsWith(base)) return url === "" ? "/" : url;
7
+ const trimmed = url.slice(base.length);
8
+ if (trimmed === "") return "/";
9
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
10
+ }
11
+ function leafSlug(contentSlug, collection) {
12
+ return contentSlug.replace(new RegExp(`^${collection}/`), "");
13
+ }
14
+ function routeFor(entry, collection, options) {
15
+ const slug = leafSlug(entry.contentSlug, collection);
16
+ return (options?.topLevel ?? ["pages"]).includes(collection) ? `/${slug}` : `/${collection}/${slug}`;
17
+ }
18
+ function getTime(date) {
19
+ if (!date) return 0;
20
+ return date instanceof Date ? date.getTime() : new Date(date).getTime();
21
+ }
22
+ function toIso(date) {
23
+ if (!date) return void 0;
24
+ return (date instanceof Date ? date : new Date(date)).toISOString();
25
+ }
26
+ function formatDate(iso) {
27
+ return new Date(iso).toLocaleDateString("en-US", {
28
+ year: "numeric",
29
+ month: "long",
30
+ day: "numeric"
31
+ });
32
+ }
33
+ function estimateReadTime(html) {
34
+ const text = html.replace(/<[^>]+>/g, " ");
35
+ return Math.max(1, Math.ceil(text.split(/\s+/).filter(Boolean).length / 200));
36
+ }
37
+ function escapeHtml(value) {
38
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
39
+ }
40
+ function buildNavEntries(entries, base, section) {
41
+ return entries.map((entry) => ({
42
+ slug: leafSlug(entry.contentSlug, section),
43
+ title: entry.frontmatter.title ?? "",
44
+ description: entry.frontmatter.description,
45
+ url: `${base}/${section}/${leafSlug(entry.contentSlug, section)}`,
46
+ date: toIso(entry.frontmatter.date),
47
+ tags: entry.frontmatter.tags ?? []
48
+ }));
49
+ }
50
+ function groupByField(entries, base, section, field, fallback = "Other") {
51
+ const groups = [];
52
+ const seen = /* @__PURE__ */ new Map();
53
+ for (const entry of entries) {
54
+ const series = entry.frontmatter[field] ?? fallback;
55
+ if (!seen.has(series)) {
56
+ const items = [];
57
+ seen.set(series, items);
58
+ groups.push({
59
+ series,
60
+ items
61
+ });
62
+ }
63
+ seen.get(series).push({
64
+ slug: leafSlug(entry.contentSlug, section),
65
+ title: entry.frontmatter.title ?? "",
66
+ url: `${base}/${section}/${leafSlug(entry.contentSlug, section)}`
67
+ });
68
+ }
69
+ return groups;
70
+ }
71
+ function renderDocumentShell(options) {
72
+ const { title, description, basePath, cssPath, jsPath, searchEnabled, bodyHtml, sidebarHtml, headHtml } = options;
73
+ const base = basePath.replace(/\/+$/, "");
74
+ return `<html lang="en" class="no-js">
75
+ <head>
76
+ <meta charset="utf-8" />
77
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
78
+ <meta name="color-scheme" content="light dark" />
79
+ <title>${escapeHtml(title)}</title>
80
+ ${description ? `<meta name="description" content="${escapeHtml(description)}" />` : ""}
81
+ <meta property="og:type" content="website" />
82
+ <meta property="og:title" content="${escapeHtml(title)}" />
83
+ ${description ? `<meta property="og:description" content="${escapeHtml(description)}" />` : ""}
84
+ <link rel="icon" href="${base}/favicon.svg" type="image/svg+xml" />
85
+ <link rel="stylesheet" href="${base}/assets/fonts.css" />
86
+ <link rel="stylesheet" href="${cssPath}" />
87
+ ${searchEnabled ? `<link rel="stylesheet" href="${base}/pagefind/pagefind-ui.css" />` : ""}
88
+ <script>document.documentElement.classList.remove('no-js')<\/script>
89
+ ${searchEnabled ? `<script src="${base}/pagefind/pagefind-ui.js" defer><\/script>` : ""}
90
+ ${searchEnabled ? "<noscript><style>.doc-search-trigger{display:none!important}</style></noscript>" : ""}
91
+ ${headHtml ?? ""}
92
+ </head>
93
+ <body>
94
+ ${bodyHtml}
95
+ ${sidebarHtml ? `<dialog class="doc-sidebar-modal" id="sidebar-modal">
96
+ <div class="doc-sidebar-modal-backdrop" data-sidebar-close=""></div>
97
+ <div class="doc-sidebar-modal-panel">
98
+ <button type="button" class="doc-sidebar-modal-close" data-sidebar-close="" aria-label="Close navigation">${closeIcon}</button>
99
+ <nav class="doc-sidebar-nav" aria-label="Sidebar navigation">${sidebarHtml}</nav>
100
+ </div>
101
+ </dialog>` : ""}
102
+ ${searchEnabled ? `<dialog class="doc-search-modal" id="search-modal" aria-label="Search" data-search-show-images="false" data-search-show-sub-results="true">
103
+ <div class="doc-search-modal-inner">
104
+ <div class="doc-search-modal-header">
105
+ <span class="doc-search-modal-title">Search</span>
106
+ <button type="button" class="doc-search-modal-close" aria-label="Close" data-search-close="">${closeIcon}</button>
107
+ </div>
108
+ <div class="doc-search-modal-body" id="search-container" data-pagefind-search=""></div>
109
+ </div>
110
+ </dialog>` : ""}
111
+ ${jsPath ? `<script src="${jsPath}" defer><\/script>` : ""}
112
+ </body>
113
+ </html>`;
114
+ }
115
+ //#endregion
116
+ export { buildNavEntries, closeIcon, escapeHtml, estimateReadTime, formatDate, getTime, groupByField, leafSlug, menuIcon, normalizeRoute, renderDocumentShell, routeFor, searchIcon, toIso };
117
+
118
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/ssg-utils/index.ts"],"sourcesContent":["/**\n * Shared SSG utilities for framework example sites.\n *\n * Provides types that match virtual module output, icon SVGs,\n * route helpers, date formatting, and HTML document rendering\n * used across all framework examples.\n */\n\n// ── Types ──\n\nexport type Heading = {\n depth: number\n slug: string\n text: string\n}\n\nexport type MarkdownEntry<T extends Record<string, unknown> = Record<string, unknown>> = {\n contentSlug: string\n html: string\n headings: Heading[]\n frontmatter: T\n}\n\nexport type NavEntry = {\n slug: string\n title: string\n description?: string\n url: string\n date?: string\n tags?: string[]\n}\n\nexport type NavGroup = {\n series: string\n items: NavEntry[]\n}\n\n// ── Icons ──\n\nexport const menuIcon =\n '<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"M3 5h14M3 10h14M3 15h14\"/></svg>'\nexport const closeIcon =\n '<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"m5 5 10 10M15 5 5 15\"/></svg>'\nexport const searchIcon =\n '<svg viewBox=\"0 0 20 20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><circle cx=\"8.5\" cy=\"8.5\" r=\"5.5\"/><path d=\"m13 13 4 4\"/></svg>'\n\n// ── Route helpers ──\n\nexport function normalizeRoute(url: string, base: string): string {\n if (!base || !url.startsWith(base)) return url === '' ? '/' : url\n const trimmed = url.slice(base.length)\n if (trimmed === '') return '/'\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`\n}\n\nexport function leafSlug(contentSlug: string, collection: string): string {\n return contentSlug.replace(new RegExp(`^${collection}/`), '')\n}\n\nexport function routeFor(\n entry: MarkdownEntry,\n collection: string,\n options?: { topLevel?: string[] },\n): string {\n const slug = leafSlug(entry.contentSlug, collection)\n const topLevel = options?.topLevel ?? ['pages']\n return topLevel.includes(collection) ? `/${slug}` : `/${collection}/${slug}`\n}\n\n// ── Date helpers ──\n\nexport function getTime(date: string | Date | undefined): number {\n if (!date) return 0\n return date instanceof Date ? date.getTime() : new Date(date).getTime()\n}\n\nexport function toIso(date: string | Date | undefined): string | undefined {\n if (!date) return undefined\n return (date instanceof Date ? date : new Date(date)).toISOString()\n}\n\nexport function formatDate(iso: string): string {\n const date = new Date(iso)\n return date.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })\n}\n\n// ── Content helpers ──\n\nexport function estimateReadTime(html: string): number {\n const text = html.replace(/<[^>]+>/g, ' ')\n return Math.max(1, Math.ceil(text.split(/\\s+/).filter(Boolean).length / 200))\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n}\n\nexport function buildNavEntries(\n entries: MarkdownEntry[],\n base: string,\n section: string,\n): NavEntry[] {\n return entries.map((entry) => ({\n slug: leafSlug(entry.contentSlug, section),\n title: (entry.frontmatter.title as string) ?? '',\n description: entry.frontmatter.description as string | undefined,\n url: `${base}/${section}/${leafSlug(entry.contentSlug, section)}`,\n date: toIso(entry.frontmatter.date as string | Date | undefined),\n tags: (entry.frontmatter.tags as string[]) ?? [],\n }))\n}\n\nexport function groupByField(\n entries: MarkdownEntry[],\n base: string,\n section: string,\n field: string,\n fallback = 'Other',\n): NavGroup[] {\n const groups: NavGroup[] = []\n const seen = new Map<string, NavEntry[]>()\n\n for (const entry of entries) {\n const series = (entry.frontmatter[field] as string) ?? fallback\n if (!seen.has(series)) {\n const items: NavEntry[] = []\n seen.set(series, items)\n groups.push({ series, items })\n }\n seen.get(series)!.push({\n slug: leafSlug(entry.contentSlug, section),\n title: (entry.frontmatter.title as string) ?? '',\n url: `${base}/${section}/${leafSlug(entry.contentSlug, section)}`,\n })\n }\n\n return groups\n}\n\n// ── HTML document shell ──\n\nexport type DocumentShellOptions = {\n title: string\n description?: string\n basePath: string\n cssPath: string\n jsPath?: string\n searchEnabled?: boolean\n bodyHtml: string\n sidebarHtml?: string\n headHtml?: string\n}\n\nexport function renderDocumentShell(options: DocumentShellOptions): string {\n const {\n title,\n description,\n basePath,\n cssPath,\n jsPath,\n searchEnabled,\n bodyHtml,\n sidebarHtml,\n headHtml,\n } = options\n const base = basePath.replace(/\\/+$/, '')\n\n return `<html lang=\"en\" class=\"no-js\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <title>${escapeHtml(title)}</title>\n ${description ? `<meta name=\"description\" content=\"${escapeHtml(description)}\" />` : ''}\n <meta property=\"og:type\" content=\"website\" />\n <meta property=\"og:title\" content=\"${escapeHtml(title)}\" />\n ${description ? `<meta property=\"og:description\" content=\"${escapeHtml(description)}\" />` : ''}\n <link rel=\"icon\" href=\"${base}/favicon.svg\" type=\"image/svg+xml\" />\n <link rel=\"stylesheet\" href=\"${base}/assets/fonts.css\" />\n <link rel=\"stylesheet\" href=\"${cssPath}\" />\n ${searchEnabled ? `<link rel=\"stylesheet\" href=\"${base}/pagefind/pagefind-ui.css\" />` : ''}\n <script>document.documentElement.classList.remove('no-js')</script>\n ${searchEnabled ? `<script src=\"${base}/pagefind/pagefind-ui.js\" defer></script>` : ''}\n ${searchEnabled ? '<noscript><style>.doc-search-trigger{display:none!important}</style></noscript>' : ''}\n ${headHtml ?? ''}\n </head>\n <body>\n ${bodyHtml}\n ${\n sidebarHtml\n ? `<dialog class=\"doc-sidebar-modal\" id=\"sidebar-modal\">\n <div class=\"doc-sidebar-modal-backdrop\" data-sidebar-close=\"\"></div>\n <div class=\"doc-sidebar-modal-panel\">\n <button type=\"button\" class=\"doc-sidebar-modal-close\" data-sidebar-close=\"\" aria-label=\"Close navigation\">${closeIcon}</button>\n <nav class=\"doc-sidebar-nav\" aria-label=\"Sidebar navigation\">${sidebarHtml}</nav>\n </div>\n </dialog>`\n : ''\n }\n ${\n searchEnabled\n ? `<dialog class=\"doc-search-modal\" id=\"search-modal\" aria-label=\"Search\" data-search-show-images=\"false\" data-search-show-sub-results=\"true\">\n <div class=\"doc-search-modal-inner\">\n <div class=\"doc-search-modal-header\">\n <span class=\"doc-search-modal-title\">Search</span>\n <button type=\"button\" class=\"doc-search-modal-close\" aria-label=\"Close\" data-search-close=\"\">${closeIcon}</button>\n </div>\n <div class=\"doc-search-modal-body\" id=\"search-container\" data-pagefind-search=\"\"></div>\n </div>\n </dialog>`\n : ''\n }\n ${jsPath ? `<script src=\"${jsPath}\" defer></script>` : ''}\n </body>\n</html>`\n}\n"],"mappings":"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"}
@@ -25,4 +25,4 @@ interface Loader {
25
25
  }
26
26
  //#endregion
27
27
  export { LoaderResult as n, LoaderType as r, Loader as t };
28
- //# sourceMappingURL=types-Cn52sdoq.d.mts.map
28
+ //# sourceMappingURL=types-B-V5qemH.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-Cn52sdoq.d.mts","names":[],"sources":["../src/loaders/types.ts"],"mappings":";;AAOA;;;;;KAAY,UAAA;AAAA,KACA,UAAA;AAAA,UAEK,YAAA;EAFK;EAIpB,IAAA,EAAM,MAAA;EAFS;EAIf,OAAA;AAAA;AAAA,UAGe,MAAA;EALf;EAOA,IAAA;EALA;EAOA,IAAA,EAAM,UAAA;EAPC;EASP,UAAA;EANqB;EAQrB,IAAA,CAAK,QAAA,WAAmB,OAAA,CAAQ,YAAA;AAAA"}
1
+ {"version":3,"file":"types-B-V5qemH.d.mts","names":[],"sources":["../src/loaders/types.ts"],"mappings":";;AAOA;;;;;KAAY,UAAA;AAAA,KACA,UAAA;AAAA,UAEK,YAAA;EAFK;EAIpB,IAAA,EAAM,MAAA;EAFS;EAIf,OAAA;AAAA;AAAA,UAGe,MAAA;EALf;EAOA,IAAA;EALA;EAOA,IAAA,EAAM,UAAA;EAPC;EASP,UAAA;EANqB;EAQrB,IAAA,CAAK,QAAA,WAAmB,OAAA,CAAQ,YAAA;AAAA"}