@silicajs/next 0.4.0 → 0.6.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.
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { ReadableStream } from "node:stream/web";
4
4
  import Database from "better-sqlite3";
5
5
  import fs from "fs-extra";
6
+ import { tryResolveDataRoot } from "../runtime-paths.js";
6
7
  function createFilesystemCacheHandler(options = {}) {
7
8
  const root = options.root ?? resolveCacheRoot();
8
9
  const entriesRoot = path.join(root, "entries");
@@ -79,16 +80,17 @@ async function ensureCacheRoot(entriesRoot) {
79
80
  }
80
81
  function resolveCacheRoot() {
81
82
  if (process.env.SILICA_CACHE_DIR) return process.env.SILICA_CACHE_DIR;
82
- const projectRoot = process.env.SILICA_PROJECT_ROOT ?? process.cwd();
83
- const config = readConfigFromVaultDb(projectRoot);
83
+ const dataRoot = tryResolveDataRoot();
84
+ const appRoot = dataRoot ? path.dirname(dataRoot) : process.cwd();
85
+ const config = dataRoot ? readConfigFromVaultDb(dataRoot) : void 0;
84
86
  const configured = config?.render?.cache?.directory;
85
87
  if (configured) {
86
- return path.isAbsolute(configured) ? configured : path.join(projectRoot, configured);
88
+ return path.isAbsolute(configured) ? configured : path.join(appRoot, configured);
87
89
  }
88
- return path.join(projectRoot, ".silica/cache/next");
90
+ return path.join(appRoot, "data/cache/next");
89
91
  }
90
- function readConfigFromVaultDb(projectRoot) {
91
- const databasePath = path.join(projectRoot, ".silica/vault.db");
92
+ function readConfigFromVaultDb(dataRoot) {
93
+ const databasePath = path.join(dataRoot, "vault.db");
92
94
  if (!fs.existsSync(databasePath)) return;
93
95
  const db = new Database(databasePath, {
94
96
  fileMustExist: true,
@@ -103,9 +105,12 @@ function readConfigFromVaultDb(projectRoot) {
103
105
  }
104
106
  }
105
107
  function getEntryPath(entriesRoot, cacheKey) {
106
- const digest = crypto.createHash("sha256").update(cacheKey).digest("hex");
108
+ const digest = getCacheKeyDigest(cacheKey);
107
109
  return path.join(entriesRoot, digest.slice(0, 2), `${digest}.json`);
108
110
  }
111
+ function getCacheKeyDigest(cacheKey) {
112
+ return crypto.createHash("sha256").update(cacheKey).digest("hex");
113
+ }
109
114
  async function readStoredEntry(filePath) {
110
115
  try {
111
116
  return await fs.readJson(filePath);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cache-handlers/filesystem.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport path from \"node:path\";\nimport { ReadableStream } from \"node:stream/web\";\nimport Database from \"better-sqlite3\";\nimport fs from \"fs-extra\";\n\nexport type CacheEntry = {\n value: ReadableStream<Uint8Array>;\n tags: string[];\n stale: number;\n timestamp: number;\n expire: number;\n revalidate: number;\n};\n\ntype StoredCacheEntry = Omit<CacheEntry, \"value\"> & {\n value: string;\n};\n\ntype TagState = {\n version: 1;\n tags: Record<string, number>;\n};\n\nexport type FilesystemCacheHandlerOptions = {\n root?: string;\n};\n\nexport function createFilesystemCacheHandler(\n options: FilesystemCacheHandlerOptions = {},\n) {\n const root = options.root ?? resolveCacheRoot();\n const entriesRoot = path.join(root, \"entries\");\n const tagsPath = path.join(root, \"tags.json\");\n let tagState: TagState | undefined;\n\n return {\n async get(\n cacheKey: string,\n softTags: string[] = [],\n ): Promise<CacheEntry | undefined> {\n await ensureCacheRoot(entriesRoot);\n await loadTagState();\n const stored = await readStoredEntry(getEntryPath(entriesRoot, cacheKey));\n if (!stored) return undefined;\n const now = Date.now();\n if (\n Number.isFinite(stored.expire) &&\n stored.expire > 0 &&\n stored.timestamp + stored.expire * 1000 <= now\n ) {\n return undefined;\n }\n const expiration = getExpirationFromState([\n ...(stored.tags ?? []),\n ...softTags,\n ]);\n if (expiration > stored.timestamp) return undefined;\n return {\n ...stored,\n value: streamFromBuffer(Buffer.from(stored.value, \"base64\")),\n };\n },\n\n async set(\n cacheKey: string,\n pendingEntry: Promise<CacheEntry>,\n ): Promise<void> {\n await ensureCacheRoot(entriesRoot);\n const entry = await pendingEntry;\n const [storedStream, returnedStream] = entry.value.tee();\n entry.value = returnedStream;\n const stored: StoredCacheEntry = {\n ...entry,\n value: (await bufferFromStream(storedStream)).toString(\"base64\"),\n };\n const destination = getEntryPath(entriesRoot, cacheKey);\n await fs.ensureDir(path.dirname(destination));\n await writeJsonAtomic(destination, stored);\n },\n\n async refreshTags(): Promise<void> {\n await loadTagState();\n },\n\n async getExpiration(tags: string[]): Promise<number> {\n await loadTagState();\n return getExpirationFromState(tags);\n },\n\n async updateTags(\n tags: string[],\n durations?: { expire?: number },\n ): Promise<void> {\n await ensureCacheRoot(entriesRoot);\n await loadTagState();\n const now = Date.now();\n const uniqueTags = [...new Set(tags)];\n tagState ??= { version: 1, tags: {} };\n for (const tag of uniqueTags) {\n tagState.tags[tag] = now;\n }\n await writeJsonAtomic(tagsPath, tagState);\n if (durations?.expire === 0) {\n await deleteEntriesWithTags(entriesRoot, uniqueTags);\n }\n },\n };\n\n async function loadTagState(): Promise<void> {\n tagState = ((await fs.readJson(tagsPath).catch(() => undefined)) ?? {\n version: 1,\n tags: {},\n }) as TagState;\n }\n\n function getExpirationFromState(tags: string[]): number {\n const state = tagState ?? { version: 1, tags: {} };\n return Math.max(0, ...tags.map((tag) => state.tags[tag] ?? 0));\n }\n}\n\nasync function ensureCacheRoot(entriesRoot: string): Promise<void> {\n await fs.ensureDir(entriesRoot);\n}\n\nfunction resolveCacheRoot(): string {\n if (process.env.SILICA_CACHE_DIR) return process.env.SILICA_CACHE_DIR;\n const projectRoot = process.env.SILICA_PROJECT_ROOT ?? process.cwd();\n const config = readConfigFromVaultDb(projectRoot);\n const configured = config?.render?.cache?.directory;\n if (configured) {\n return path.isAbsolute(configured)\n ? configured\n : path.join(projectRoot, configured);\n }\n return path.join(projectRoot, \".silica/cache/next\");\n}\n\nfunction readConfigFromVaultDb(projectRoot: string):\n | {\n render?: { cache?: { directory?: string } };\n }\n | undefined {\n const databasePath = path.join(projectRoot, \".silica/vault.db\");\n if (!fs.existsSync(databasePath)) return;\n const db = new Database(databasePath, {\n fileMustExist: true,\n readonly: true,\n });\n try {\n db.pragma(\"query_only = ON\");\n const row = db\n .prepare(\"SELECT value FROM vault_metadata WHERE key = 'configJson'\")\n .get() as { value: string } | undefined;\n return row ? JSON.parse(row.value) : undefined;\n } finally {\n db.close();\n }\n}\n\nfunction getEntryPath(entriesRoot: string, cacheKey: string): string {\n const digest = crypto.createHash(\"sha256\").update(cacheKey).digest(\"hex\");\n return path.join(entriesRoot, digest.slice(0, 2), `${digest}.json`);\n}\n\nasync function readStoredEntry(\n filePath: string,\n): Promise<StoredCacheEntry | undefined> {\n try {\n return (await fs.readJson(filePath)) as StoredCacheEntry;\n } catch {\n return undefined;\n }\n}\n\nasync function writeJsonAtomic(\n filePath: string,\n value: unknown,\n): Promise<void> {\n const temporary = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;\n await fs.ensureDir(path.dirname(filePath));\n await fs.writeJson(temporary, value);\n await fs.rename(temporary, filePath);\n}\n\nasync function deleteEntriesWithTags(\n entriesRoot: string,\n tags: string[],\n): Promise<void> {\n if (!(await fs.pathExists(entriesRoot))) return;\n const wanted = new Set(tags);\n for (const filePath of await listJsonFiles(entriesRoot)) {\n const entry = await readStoredEntry(filePath);\n if (entry?.tags?.some((tag) => wanted.has(tag))) {\n await fs.remove(filePath);\n }\n }\n}\n\nasync function listJsonFiles(root: string): Promise<string[]> {\n const entries = await fs.readdir(root, { withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n const absolutePath = path.join(root, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await listJsonFiles(absolutePath)));\n } else if (entry.isFile() && entry.name.endsWith(\".json\")) {\n files.push(absolutePath);\n }\n }\n return files;\n}\n\nasync function bufferFromStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Buffer> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n return Buffer.concat(chunks);\n}\n\nfunction streamFromBuffer(buffer: Buffer): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(buffer);\n controller.close();\n },\n });\n}\n\nexport default createFilesystemCacheHandler();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cache-handlers/filesystem.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport path from \"node:path\";\nimport { ReadableStream } from \"node:stream/web\";\nimport Database from \"better-sqlite3\";\nimport fs from \"fs-extra\";\nimport { tryResolveDataRoot } from \"../runtime-paths.js\";\n\nexport type CacheEntry = {\n value: ReadableStream<Uint8Array>;\n tags: string[];\n stale: number;\n timestamp: number;\n expire: number;\n revalidate: number;\n};\n\ntype StoredCacheEntry = Omit<CacheEntry, \"value\"> & {\n value: string;\n};\n\ntype TagState = {\n version: 1;\n tags: Record<string, number>;\n};\n\nexport type FilesystemCacheHandlerOptions = {\n root?: string;\n};\n\nexport function createFilesystemCacheHandler(\n options: FilesystemCacheHandlerOptions = {},\n) {\n const root = options.root ?? resolveCacheRoot();\n const entriesRoot = path.join(root, \"entries\");\n const tagsPath = path.join(root, \"tags.json\");\n let tagState: TagState | undefined;\n\n return {\n async get(\n cacheKey: string,\n softTags: string[] = [],\n ): Promise<CacheEntry | undefined> {\n await ensureCacheRoot(entriesRoot);\n await loadTagState();\n const stored = await readStoredEntry(getEntryPath(entriesRoot, cacheKey));\n if (!stored) return undefined;\n const now = Date.now();\n if (\n Number.isFinite(stored.expire) &&\n stored.expire > 0 &&\n stored.timestamp + stored.expire * 1000 <= now\n ) {\n return undefined;\n }\n const expiration = getExpirationFromState([\n ...(stored.tags ?? []),\n ...softTags,\n ]);\n if (expiration > stored.timestamp) return undefined;\n return {\n ...stored,\n value: streamFromBuffer(Buffer.from(stored.value, \"base64\")),\n };\n },\n\n async set(\n cacheKey: string,\n pendingEntry: Promise<CacheEntry>,\n ): Promise<void> {\n await ensureCacheRoot(entriesRoot);\n const entry = await pendingEntry;\n const [storedStream, returnedStream] = entry.value.tee();\n entry.value = returnedStream;\n const stored: StoredCacheEntry = {\n ...entry,\n value: (await bufferFromStream(storedStream)).toString(\"base64\"),\n };\n const destination = getEntryPath(entriesRoot, cacheKey);\n await fs.ensureDir(path.dirname(destination));\n await writeJsonAtomic(destination, stored);\n },\n\n async refreshTags(): Promise<void> {\n await loadTagState();\n },\n\n async getExpiration(tags: string[]): Promise<number> {\n await loadTagState();\n return getExpirationFromState(tags);\n },\n\n async updateTags(\n tags: string[],\n durations?: { expire?: number },\n ): Promise<void> {\n await ensureCacheRoot(entriesRoot);\n await loadTagState();\n const now = Date.now();\n const uniqueTags = [...new Set(tags)];\n tagState ??= { version: 1, tags: {} };\n for (const tag of uniqueTags) {\n tagState.tags[tag] = now;\n }\n await writeJsonAtomic(tagsPath, tagState);\n if (durations?.expire === 0) {\n await deleteEntriesWithTags(entriesRoot, uniqueTags);\n }\n },\n };\n\n async function loadTagState(): Promise<void> {\n tagState = ((await fs.readJson(tagsPath).catch(() => undefined)) ?? {\n version: 1,\n tags: {},\n }) as TagState;\n }\n\n function getExpirationFromState(tags: string[]): number {\n const state = tagState ?? { version: 1, tags: {} };\n return Math.max(0, ...tags.map((tag) => state.tags[tag] ?? 0));\n }\n}\n\nasync function ensureCacheRoot(entriesRoot: string): Promise<void> {\n await fs.ensureDir(entriesRoot);\n}\n\nfunction resolveCacheRoot(): string {\n if (process.env.SILICA_CACHE_DIR) return process.env.SILICA_CACHE_DIR;\n\n const dataRoot = tryResolveDataRoot();\n const appRoot = dataRoot ? path.dirname(dataRoot) : process.cwd();\n const config = dataRoot ? readConfigFromVaultDb(dataRoot) : undefined;\n const configured = config?.render?.cache?.directory;\n if (configured) {\n return path.isAbsolute(configured)\n ? configured\n : path.join(appRoot, configured);\n }\n return path.join(appRoot, \"data/cache/next\");\n}\n\nfunction readConfigFromVaultDb(dataRoot: string):\n | {\n render?: { cache?: { directory?: string } };\n }\n | undefined {\n const databasePath = path.join(dataRoot, \"vault.db\");\n if (!fs.existsSync(databasePath)) return;\n const db = new Database(databasePath, {\n fileMustExist: true,\n readonly: true,\n });\n try {\n db.pragma(\"query_only = ON\");\n const row = db\n .prepare(\"SELECT value FROM vault_metadata WHERE key = 'configJson'\")\n .get() as { value: string } | undefined;\n return row ? JSON.parse(row.value) : undefined;\n } finally {\n db.close();\n }\n}\n\nfunction getEntryPath(entriesRoot: string, cacheKey: string): string {\n const digest = getCacheKeyDigest(cacheKey);\n return path.join(entriesRoot, digest.slice(0, 2), `${digest}.json`);\n}\n\nfunction getCacheKeyDigest(cacheKey: string): string {\n return crypto.createHash(\"sha256\").update(cacheKey).digest(\"hex\");\n}\n\nasync function readStoredEntry(\n filePath: string,\n): Promise<StoredCacheEntry | undefined> {\n try {\n return (await fs.readJson(filePath)) as StoredCacheEntry;\n } catch {\n return undefined;\n }\n}\n\nasync function writeJsonAtomic(\n filePath: string,\n value: unknown,\n): Promise<void> {\n const temporary = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;\n await fs.ensureDir(path.dirname(filePath));\n await fs.writeJson(temporary, value);\n await fs.rename(temporary, filePath);\n}\n\nasync function deleteEntriesWithTags(\n entriesRoot: string,\n tags: string[],\n): Promise<void> {\n if (!(await fs.pathExists(entriesRoot))) return;\n const wanted = new Set(tags);\n for (const filePath of await listJsonFiles(entriesRoot)) {\n const entry = await readStoredEntry(filePath);\n if (entry?.tags?.some((tag) => wanted.has(tag))) {\n await fs.remove(filePath);\n }\n }\n}\n\nasync function listJsonFiles(root: string): Promise<string[]> {\n const entries = await fs.readdir(root, { withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n const absolutePath = path.join(root, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await listJsonFiles(absolutePath)));\n } else if (entry.isFile() && entry.name.endsWith(\".json\")) {\n files.push(absolutePath);\n }\n }\n return files;\n}\n\nasync function bufferFromStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Buffer> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n return Buffer.concat(chunks);\n}\n\nfunction streamFromBuffer(buffer: Buffer): ReadableStream<Uint8Array> {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(buffer);\n controller.close();\n },\n });\n}\n\nexport default createFilesystemCacheHandler();\n"],"mappings":"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","names":[]}
@@ -0,0 +1,7 @@
1
+ type GeneratedAppPackageManifest = {
2
+ dependencies: Record<string, string>;
3
+ devDependencies: Record<string, string>;
4
+ };
5
+ declare const generatedAppPackageManifest: GeneratedAppPackageManifest;
6
+
7
+ export { type GeneratedAppPackageManifest, generatedAppPackageManifest };
@@ -0,0 +1,28 @@
1
+ const generatedAppPackageManifest = {
2
+ dependencies: {
3
+ "@silicajs/auth": "^0.1.3",
4
+ "@silicajs/components": "^0.4.1",
5
+ "@silicajs/core": "^0.9.0",
6
+ "@silicajs/next": "^0.6.0",
7
+ "@silicajs/remark-obsidian": "^0.1.0",
8
+ "@silicajs/search": "^0.3.2",
9
+ "@silicajs/theme-amethyst": "^0.5.1",
10
+ "@silicajs/ui": "^0.2.1",
11
+ "@tailwindcss/postcss": "^4.3.1",
12
+ "better-sqlite3": "^12.11.1",
13
+ jiti: "^2.7.0",
14
+ next: "^16.2.0",
15
+ react: "^19.2.0",
16
+ "react-dom": "^19.2.0",
17
+ tailwindcss: "^4.1.18"
18
+ },
19
+ devDependencies: {
20
+ "@types/better-sqlite3": "^7.6.13",
21
+ "@types/node": "^25.9.3",
22
+ typescript: "^6.0.3"
23
+ }
24
+ };
25
+ export {
26
+ generatedAppPackageManifest
27
+ };
28
+ //# sourceMappingURL=generated-app-package-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generated-app-package-manifest.ts"],"sourcesContent":["// This file is generated by scripts/sync-generated-app-manifest.mjs.\n// Run npm run generated-app-manifest:update after generated app dependency changes.\n\nexport type GeneratedAppPackageManifest = {\n dependencies: Record<string, string>;\n devDependencies: Record<string, string>;\n};\n\nexport const generatedAppPackageManifest: GeneratedAppPackageManifest = {\n dependencies: {\n \"@silicajs/auth\": \"^0.1.3\",\n \"@silicajs/components\": \"^0.4.1\",\n \"@silicajs/core\": \"^0.9.0\",\n \"@silicajs/next\": \"^0.6.0\",\n \"@silicajs/remark-obsidian\": \"^0.1.0\",\n \"@silicajs/search\": \"^0.3.2\",\n \"@silicajs/theme-amethyst\": \"^0.5.1\",\n \"@silicajs/ui\": \"^0.2.1\",\n \"@tailwindcss/postcss\": \"^4.3.1\",\n \"better-sqlite3\": \"^12.11.1\",\n jiti: \"^2.7.0\",\n next: \"^16.2.0\",\n react: \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n tailwindcss: \"^4.1.18\",\n },\n devDependencies: {\n \"@types/better-sqlite3\": \"^7.6.13\",\n \"@types/node\": \"^25.9.3\",\n typescript: \"^6.0.3\",\n },\n};\n"],"mappings":"AAQO,MAAM,8BAA2D;AAAA,EACtE,cAAc;AAAA,IACZ,kBAAkB;AAAA,IAClB,wBAAwB;AAAA,IACxB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,6BAA6B;AAAA,IAC7B,oBAAoB;AAAA,IACpB,4BAA4B;AAAA,IAC5B,gBAAgB;AAAA,IAChB,wBAAwB;AAAA,IACxB,kBAAkB;AAAA,IAClB,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AAAA,EACA,iBAAiB;AAAA,IACf,yBAAyB;AAAA,IACzB,eAAe;AAAA,IACf,YAAY;AAAA,EACd;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { TemplateFile, getSilicaTemplates, nextConfigTemplate, packageJsonTemplate, proxyTemplate, themeModuleTemplate, tsconfigTemplate } from './templates.js';
2
- export { LoadedVaultDb, getAllSlugs, getBacklinks, getBreadcrumbs, getCacheState, getConfig, getEntriesForTag, getNavigation, getPage, getPageRuntimeData, getPrerenderSlugs, getProjectRoot, getRelatedTagsForEntries, getRenderKey, getSilicaRoot, getTagSlugs, getVaultDatabasePath, loadRenderEnvironmentHash, loadSearchIndex, loadVaultDb, normalizeRouteSlug, resolveAssetFromDb, resolveWikiLinkFromDb } from './server-data.js';
1
+ export { TemplateFile, assistantModuleTemplate, assistantRouteTemplate, getSilicaTemplates, nextConfigTemplate, packageJsonTemplate, proxyTemplate, themeModuleTemplate, tsconfigTemplate } from './templates.js';
2
+ export { GeneratedAppPackageManifest, generatedAppPackageManifest } from './generated-app-package-manifest.js';
3
+ export { LoadedVaultDb, getAllSlugs, getBacklinks, getBreadcrumbs, getCacheState, getConfig, getEntriesForTag, getNavigation, getPage, getPageBySourcePath, getPageRuntimeData, getPrerenderSlugs, getProjectRoot, getRelatedTagsForEntries, getRenderKey, getSilicaRoot, getTagSlugs, getVaultDatabasePath, loadRenderEnvironmentHash, loadSearchIndex, loadVaultDb, normalizeRouteSlug, resolveAssetFromDb, resolveWikiLinkFromDb } from './server-data.js';
3
4
  export { SilicaNextRoutingProvider } from './routing-provider.js';
4
5
  import '@silicajs/core/runtime';
5
6
  import 'better-sqlite3';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import {
2
+ assistantModuleTemplate,
3
+ assistantRouteTemplate,
2
4
  getSilicaTemplates,
3
5
  nextConfigTemplate,
4
6
  packageJsonTemplate,
@@ -6,6 +8,9 @@ import {
6
8
  themeModuleTemplate,
7
9
  tsconfigTemplate
8
10
  } from "./templates.js";
11
+ import {
12
+ generatedAppPackageManifest
13
+ } from "./generated-app-package-manifest.js";
9
14
  import {
10
15
  getAllSlugs,
11
16
  getBacklinks,
@@ -15,6 +20,7 @@ import {
15
20
  getEntriesForTag,
16
21
  getNavigation,
17
22
  getPage,
23
+ getPageBySourcePath,
18
24
  getPageRuntimeData,
19
25
  getProjectRoot,
20
26
  getPrerenderSlugs,
@@ -33,6 +39,9 @@ import {
33
39
  import { SilicaNextRoutingProvider } from "./routing-provider.js";
34
40
  export {
35
41
  SilicaNextRoutingProvider,
42
+ assistantModuleTemplate,
43
+ assistantRouteTemplate,
44
+ generatedAppPackageManifest,
36
45
  getAllSlugs,
37
46
  getBacklinks,
38
47
  getBreadcrumbs,
@@ -41,6 +50,7 @@ export {
41
50
  getEntriesForTag,
42
51
  getNavigation,
43
52
  getPage,
53
+ getPageBySourcePath,
44
54
  getPageRuntimeData,
45
55
  getPrerenderSlugs,
46
56
  getProjectRoot,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n getSilicaTemplates,\n nextConfigTemplate,\n packageJsonTemplate,\n proxyTemplate,\n themeModuleTemplate,\n tsconfigTemplate,\n type TemplateFile,\n} from \"./templates.js\";\nexport {\n getAllSlugs,\n getBacklinks,\n getBreadcrumbs,\n getCacheState,\n getConfig,\n getEntriesForTag,\n getNavigation,\n getPage,\n getPageRuntimeData,\n getProjectRoot,\n getPrerenderSlugs,\n getRenderKey,\n getRelatedTagsForEntries,\n getSilicaRoot,\n getTagSlugs,\n getVaultDatabasePath,\n loadSearchIndex,\n loadRenderEnvironmentHash,\n loadVaultDb,\n normalizeRouteSlug,\n resolveAssetFromDb,\n resolveWikiLinkFromDb,\n type LoadedVaultDb,\n} from \"./server-data.js\";\nexport { SilicaNextRoutingProvider } from \"./routing-provider.js\";\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,iCAAiC;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n assistantModuleTemplate,\n assistantRouteTemplate,\n getSilicaTemplates,\n nextConfigTemplate,\n packageJsonTemplate,\n proxyTemplate,\n themeModuleTemplate,\n tsconfigTemplate,\n type TemplateFile,\n} from \"./templates.js\";\nexport {\n generatedAppPackageManifest,\n type GeneratedAppPackageManifest,\n} from \"./generated-app-package-manifest.js\";\nexport {\n getAllSlugs,\n getBacklinks,\n getBreadcrumbs,\n getCacheState,\n getConfig,\n getEntriesForTag,\n getNavigation,\n getPage,\n getPageBySourcePath,\n getPageRuntimeData,\n getProjectRoot,\n getPrerenderSlugs,\n getRenderKey,\n getRelatedTagsForEntries,\n getSilicaRoot,\n getTagSlugs,\n getVaultDatabasePath,\n loadSearchIndex,\n loadRenderEnvironmentHash,\n loadVaultDb,\n normalizeRouteSlug,\n resolveAssetFromDb,\n resolveWikiLinkFromDb,\n type LoadedVaultDb,\n} from \"./server-data.js\";\nexport { SilicaNextRoutingProvider } from \"./routing-provider.js\";\n"],"mappings":"AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,iCAAiC;","names":[]}
@@ -1,9 +1,21 @@
1
1
  declare function generateMetadata(): Promise<{
2
+ metadataBase: URL | undefined;
2
3
  title: {
3
4
  default: string;
4
5
  template: string;
5
6
  };
6
7
  description: string;
8
+ openGraph: {
9
+ type: string;
10
+ siteName: string;
11
+ title: string;
12
+ description: string;
13
+ };
14
+ twitter: {
15
+ card: string;
16
+ title: string;
17
+ description: string;
18
+ };
7
19
  }>;
8
20
  declare function getLayoutProps(): Promise<{
9
21
  navigationEndpoint: string;
@@ -13,6 +25,7 @@ declare function getLayoutProps(): Promise<{
13
25
  logo: string | undefined;
14
26
  baseUrl: string | undefined;
15
27
  authEnabled: boolean;
28
+ assistantEnabled: boolean;
16
29
  };
17
30
  }>;
18
31
 
@@ -4,13 +4,33 @@ import { getCacheState, getConfig } from "../server-data.js";
4
4
  async function generateMetadata() {
5
5
  const { config } = await getLayoutProps();
6
6
  return {
7
+ metadataBase: resolveMetadataBase(config.baseUrl),
7
8
  title: {
8
9
  default: config.title,
9
10
  template: `%s \xB7 ${config.title}`
10
11
  },
11
- description: config.description
12
+ description: config.description,
13
+ openGraph: {
14
+ type: "website",
15
+ siteName: config.title,
16
+ title: config.title,
17
+ description: config.description
18
+ },
19
+ twitter: {
20
+ card: "summary_large_image",
21
+ title: config.title,
22
+ description: config.description
23
+ }
12
24
  };
13
25
  }
26
+ function resolveMetadataBase(baseUrl) {
27
+ if (!baseUrl) return void 0;
28
+ try {
29
+ return new URL(baseUrl);
30
+ } catch {
31
+ return void 0;
32
+ }
33
+ }
14
34
  async function getLayoutProps() {
15
35
  const cacheState = getCacheState();
16
36
  return getCachedLayoutProps(cacheState.renderEnvironmentHash);
@@ -30,7 +50,8 @@ async function getCachedLayoutProps(renderEnvironmentHash) {
30
50
  description: config.description,
31
51
  logo: config.logo,
32
52
  baseUrl: config.baseUrl,
33
- authEnabled: auth.authEnabled
53
+ authEnabled: auth.authEnabled,
54
+ assistantEnabled: Boolean(config.assistant)
34
55
  }
35
56
  };
36
57
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/layout.tsx"],"sourcesContent":["import { cacheLife, cacheTag } from \"next/cache\";\nimport { resolveRuntimeAuthConfig } from \"../auth-config.js\";\nimport { getCacheState, getConfig } from \"../server-data.js\";\n\nexport async function generateMetadata() {\n const { config } = await getLayoutProps();\n return {\n title: {\n default: config.title,\n template: `%s · ${config.title}`,\n },\n description: config.description,\n };\n}\n\nexport async function getLayoutProps() {\n const cacheState = getCacheState();\n return getCachedLayoutProps(cacheState.renderEnvironmentHash);\n}\n\nasync function getCachedLayoutProps(renderEnvironmentHash: string) {\n \"use cache\";\n cacheLife(\"max\");\n cacheTag(`environment:${renderEnvironmentHash}`);\n const config = getConfig();\n const auth = resolveRuntimeAuthConfig(config);\n return {\n navigationEndpoint: `/api/navigation?build=${encodeURIComponent(\n renderEnvironmentHash,\n )}`,\n config: {\n title: config.title,\n description: config.description,\n logo: config.logo,\n baseUrl: config.baseUrl,\n authEnabled: auth.authEnabled,\n },\n };\n}\n"],"mappings":"AAAA,SAAS,WAAW,gBAAgB;AACpC,SAAS,gCAAgC;AACzC,SAAS,eAAe,iBAAiB;AAEzC,eAAsB,mBAAmB;AACvC,QAAM,EAAE,OAAO,IAAI,MAAM,eAAe;AACxC,SAAO;AAAA,IACL,OAAO;AAAA,MACL,SAAS,OAAO;AAAA,MAChB,UAAU,WAAQ,OAAO,KAAK;AAAA,IAChC;AAAA,IACA,aAAa,OAAO;AAAA,EACtB;AACF;AAEA,eAAsB,iBAAiB;AACrC,QAAM,aAAa,cAAc;AACjC,SAAO,qBAAqB,WAAW,qBAAqB;AAC9D;AAEA,eAAe,qBAAqB,uBAA+B;AACjE;AACA,YAAU,KAAK;AACf,WAAS,eAAe,qBAAqB,EAAE;AAC/C,QAAM,SAAS,UAAU;AACzB,QAAM,OAAO,yBAAyB,MAAM;AAC5C,SAAO;AAAA,IACL,oBAAoB,yBAAyB;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,IACD,QAAQ;AAAA,MACN,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,aAAa,KAAK;AAAA,IACpB;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/routes/layout.tsx"],"sourcesContent":["import { cacheLife, cacheTag } from \"next/cache\";\nimport { resolveRuntimeAuthConfig } from \"../auth-config.js\";\nimport { getCacheState, getConfig } from \"../server-data.js\";\n\nexport async function generateMetadata() {\n const { config } = await getLayoutProps();\n return {\n metadataBase: resolveMetadataBase(config.baseUrl),\n title: {\n default: config.title,\n template: `%s · ${config.title}`,\n },\n description: config.description,\n openGraph: {\n type: \"website\",\n siteName: config.title,\n title: config.title,\n description: config.description,\n },\n twitter: {\n card: \"summary_large_image\",\n title: config.title,\n description: config.description,\n },\n };\n}\n\nfunction resolveMetadataBase(baseUrl?: string): URL | undefined {\n if (!baseUrl) return undefined;\n try {\n return new URL(baseUrl);\n } catch {\n return undefined;\n }\n}\n\nexport async function getLayoutProps() {\n const cacheState = getCacheState();\n return getCachedLayoutProps(cacheState.renderEnvironmentHash);\n}\n\nasync function getCachedLayoutProps(renderEnvironmentHash: string) {\n \"use cache\";\n cacheLife(\"max\");\n cacheTag(`environment:${renderEnvironmentHash}`);\n const config = getConfig();\n const auth = resolveRuntimeAuthConfig(config);\n return {\n navigationEndpoint: `/api/navigation?build=${encodeURIComponent(\n renderEnvironmentHash,\n )}`,\n config: {\n title: config.title,\n description: config.description,\n logo: config.logo,\n baseUrl: config.baseUrl,\n authEnabled: auth.authEnabled,\n assistantEnabled: Boolean(config.assistant),\n },\n };\n}\n"],"mappings":"AAAA,SAAS,WAAW,gBAAgB;AACpC,SAAS,gCAAgC;AACzC,SAAS,eAAe,iBAAiB;AAEzC,eAAsB,mBAAmB;AACvC,QAAM,EAAE,OAAO,IAAI,MAAM,eAAe;AACxC,SAAO;AAAA,IACL,cAAc,oBAAoB,OAAO,OAAO;AAAA,IAChD,OAAO;AAAA,MACL,SAAS,OAAO;AAAA,MAChB,UAAU,WAAQ,OAAO,KAAK;AAAA,IAChC;AAAA,IACA,aAAa,OAAO;AAAA,IACpB,WAAW;AAAA,MACT,MAAM;AAAA,MACN,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,IACtB;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,IACtB;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,SAAmC;AAC9D,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,IAAI,IAAI,OAAO;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,iBAAiB;AACrC,QAAM,aAAa,cAAc;AACjC,SAAO,qBAAqB,WAAW,qBAAqB;AAC9D;AAEA,eAAe,qBAAqB,uBAA+B;AACjE;AACA,YAAU,KAAK;AACf,WAAS,eAAe,qBAAqB,EAAE;AAC/C,QAAM,SAAS,UAAU;AACzB,QAAM,OAAO,yBAAyB,MAAM;AAC5C,SAAO;AAAA,IACL,oBAAoB,yBAAyB;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,IACD,QAAQ;AAAA,MACN,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,MAAM,OAAO;AAAA,MACb,SAAS,OAAO;AAAA,MAChB,aAAa,KAAK;AAAA,MAClB,kBAAkB,QAAQ,OAAO,SAAS;AAAA,IAC5C;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public path of the auto-generated Open Graph image for a note slug. Mirrors
3
+ * the `app/api/silica/og/[[...slug]]/route` handler that renders it.
4
+ */
5
+ declare function opengraphImagePath(slug: string): string;
6
+ declare function titleFontSize(title: string): number;
7
+ declare function clampText(text: string, maxLength: number): string;
8
+ declare function hostnameFromBaseUrl(baseUrl?: string): string | undefined;
9
+
10
+ export { clampText, hostnameFromBaseUrl, opengraphImagePath, titleFontSize };
@@ -0,0 +1,28 @@
1
+ function opengraphImagePath(slug) {
2
+ if (!slug || slug === "index") return "/api/silica/og";
3
+ return `/api/silica/og/${slug.split("/").map(encodeURIComponent).join("/")}`;
4
+ }
5
+ function titleFontSize(title) {
6
+ if (title.length <= 28) return 84;
7
+ if (title.length <= 55) return 64;
8
+ return 48;
9
+ }
10
+ function clampText(text, maxLength) {
11
+ if (text.length <= maxLength) return text;
12
+ return `${text.slice(0, maxLength - 1).trimEnd()}\u2026`;
13
+ }
14
+ function hostnameFromBaseUrl(baseUrl) {
15
+ if (!baseUrl) return void 0;
16
+ try {
17
+ return new URL(baseUrl).hostname;
18
+ } catch {
19
+ return baseUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "") || void 0;
20
+ }
21
+ }
22
+ export {
23
+ clampText,
24
+ hostnameFromBaseUrl,
25
+ opengraphImagePath,
26
+ titleFontSize
27
+ };
28
+ //# sourceMappingURL=opengraph-image-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/opengraph-image-format.ts"],"sourcesContent":["/**\n * Public path of the auto-generated Open Graph image for a note slug. Mirrors\n * the `app/api/silica/og/[[...slug]]/route` handler that renders it.\n */\nexport function opengraphImagePath(slug: string): string {\n if (!slug || slug === \"index\") return \"/api/silica/og\";\n return `/api/silica/og/${slug.split(\"/\").map(encodeURIComponent).join(\"/\")}`;\n}\n\nexport function titleFontSize(title: string): number {\n if (title.length <= 28) return 84;\n if (title.length <= 55) return 64;\n return 48;\n}\n\nexport function clampText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return `${text.slice(0, maxLength - 1).trimEnd()}…`;\n}\n\nexport function hostnameFromBaseUrl(baseUrl?: string): string | undefined {\n if (!baseUrl) return undefined;\n try {\n return new URL(baseUrl).hostname;\n } catch {\n return baseUrl.replace(/^https?:\\/\\//, \"\").replace(/\\/+$/, \"\") || undefined;\n }\n}\n"],"mappings":"AAIO,SAAS,mBAAmB,MAAsB;AACvD,MAAI,CAAC,QAAQ,SAAS,QAAS,QAAO;AACtC,SAAO,kBAAkB,KAAK,MAAM,GAAG,EAAE,IAAI,kBAAkB,EAAE,KAAK,GAAG,CAAC;AAC5E;AAEO,SAAS,cAAc,OAAuB;AACnD,MAAI,MAAM,UAAU,GAAI,QAAO;AAC/B,MAAI,MAAM,UAAU,GAAI,QAAO;AAC/B,SAAO;AACT;AAEO,SAAS,UAAU,MAAc,WAA2B;AACjE,MAAI,KAAK,UAAU,UAAW,QAAO;AACrC,SAAO,GAAG,KAAK,MAAM,GAAG,YAAY,CAAC,EAAE,QAAQ,CAAC;AAClD;AAEO,SAAS,oBAAoB,SAAsC;AACxE,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI;AACF,WAAO,IAAI,IAAI,OAAO,EAAE;AAAA,EAC1B,QAAQ;AACN,WAAO,QAAQ,QAAQ,gBAAgB,EAAE,EAAE,QAAQ,QAAQ,EAAE,KAAK;AAAA,EACpE;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ import { ImageResponse } from 'next/og';
2
+
3
+ declare const OG_IMAGE_WIDTH = 1200;
4
+ declare const OG_IMAGE_HEIGHT = 630;
5
+ type RouteContext = {
6
+ params: Promise<{
7
+ slug?: string[];
8
+ }> | {
9
+ slug?: string[];
10
+ };
11
+ };
12
+ declare function GET(_request: Request, context: RouteContext): Promise<ImageResponse>;
13
+
14
+ export { GET, OG_IMAGE_HEIGHT, OG_IMAGE_WIDTH };
@@ -0,0 +1,171 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { ImageResponse } from "next/og";
3
+ import { cacheLife, cacheTag } from "next/cache";
4
+ import { getMetaDescription } from "@silicajs/core/runtime";
5
+ import {
6
+ getConfig,
7
+ getPage,
8
+ getRenderKey,
9
+ normalizeRouteSlug
10
+ } from "../server-data.js";
11
+ import {
12
+ clampText,
13
+ hostnameFromBaseUrl,
14
+ titleFontSize
15
+ } from "./opengraph-image-format.js";
16
+ const OG_IMAGE_WIDTH = 1200;
17
+ const OG_IMAGE_HEIGHT = 630;
18
+ const ACCENT = "#8b5cf6";
19
+ const BACKGROUND_TOP = "#1c1330";
20
+ const BACKGROUND_BOTTOM = "#0c0717";
21
+ async function GET(_request, context) {
22
+ const params = await context.params;
23
+ const slug = normalizeRouteSlug(params?.slug);
24
+ const renderKey = getRenderKey(slug);
25
+ const data = await getOpengraphImageData(
26
+ slug,
27
+ renderKey.renderHash,
28
+ renderKey.renderEnvironmentHash
29
+ );
30
+ return new ImageResponse(/* @__PURE__ */ jsx(OpengraphImageCard, { ...data }), {
31
+ width: OG_IMAGE_WIDTH,
32
+ height: OG_IMAGE_HEIGHT
33
+ });
34
+ }
35
+ async function getOpengraphImageData(slug, renderHash, renderEnvironmentHash) {
36
+ "use cache";
37
+ cacheLife("max");
38
+ cacheTag(
39
+ `environment:${renderEnvironmentHash}`,
40
+ `page:${slug}`,
41
+ `render:${renderHash}`
42
+ );
43
+ const config = getConfig();
44
+ const entry = getPage(slug);
45
+ return {
46
+ siteTitle: config.title,
47
+ title: entry?.title ?? config.title,
48
+ description: entry ? getMetaDescription(entry) : config.description,
49
+ tags: entry?.tags?.slice(0, 4) ?? [],
50
+ domain: hostnameFromBaseUrl(config.baseUrl)
51
+ };
52
+ }
53
+ function OpengraphImageCard({
54
+ siteTitle,
55
+ title,
56
+ description,
57
+ tags,
58
+ domain
59
+ }) {
60
+ return /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ style: {
64
+ display: "flex",
65
+ flexDirection: "column",
66
+ justifyContent: "space-between",
67
+ width: "100%",
68
+ height: "100%",
69
+ padding: "80px",
70
+ color: "#f8fafc",
71
+ backgroundColor: BACKGROUND_BOTTOM,
72
+ backgroundImage: `linear-gradient(135deg, ${BACKGROUND_TOP} 0%, ${BACKGROUND_BOTTOM} 100%)`,
73
+ fontFamily: "sans-serif"
74
+ },
75
+ children: [
76
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [
77
+ /* @__PURE__ */ jsx(
78
+ "div",
79
+ {
80
+ style: {
81
+ width: "20px",
82
+ height: "44px",
83
+ borderRadius: "6px",
84
+ backgroundColor: ACCENT,
85
+ marginRight: "24px"
86
+ }
87
+ }
88
+ ),
89
+ /* @__PURE__ */ jsx(
90
+ "div",
91
+ {
92
+ style: {
93
+ fontSize: "34px",
94
+ fontWeight: 600,
95
+ color: "#cbb6ff",
96
+ letterSpacing: "-0.01em"
97
+ },
98
+ children: siteTitle
99
+ }
100
+ )
101
+ ] }),
102
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column" }, children: [
103
+ /* @__PURE__ */ jsx(
104
+ "div",
105
+ {
106
+ style: {
107
+ display: "flex",
108
+ fontSize: titleFontSize(title),
109
+ fontWeight: 800,
110
+ lineHeight: 1.05,
111
+ letterSpacing: "-0.02em"
112
+ },
113
+ children: clampText(title, 90)
114
+ }
115
+ ),
116
+ description ? /* @__PURE__ */ jsx(
117
+ "div",
118
+ {
119
+ style: {
120
+ display: "flex",
121
+ marginTop: "28px",
122
+ fontSize: "32px",
123
+ lineHeight: 1.35,
124
+ color: "#c7c9d9"
125
+ },
126
+ children: clampText(description, 160)
127
+ }
128
+ ) : null
129
+ ] }),
130
+ /* @__PURE__ */ jsxs(
131
+ "div",
132
+ {
133
+ style: {
134
+ display: "flex",
135
+ alignItems: "center",
136
+ justifyContent: "space-between"
137
+ },
138
+ children: [
139
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center" }, children: tags.map((tag) => /* @__PURE__ */ jsxs(
140
+ "div",
141
+ {
142
+ style: {
143
+ display: "flex",
144
+ marginRight: "16px",
145
+ padding: "8px 20px",
146
+ borderRadius: "999px",
147
+ border: "1px solid rgba(139, 92, 246, 0.5)",
148
+ color: "#cbb6ff",
149
+ fontSize: "24px"
150
+ },
151
+ children: [
152
+ "#",
153
+ tag
154
+ ]
155
+ },
156
+ tag
157
+ )) }),
158
+ domain ? /* @__PURE__ */ jsx("div", { style: { display: "flex", fontSize: "26px", color: "#8c8fa3" }, children: domain }) : null
159
+ ]
160
+ }
161
+ )
162
+ ]
163
+ }
164
+ );
165
+ }
166
+ export {
167
+ GET,
168
+ OG_IMAGE_HEIGHT,
169
+ OG_IMAGE_WIDTH
170
+ };
171
+ //# sourceMappingURL=opengraph-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/opengraph-image.tsx"],"sourcesContent":["import { ImageResponse } from \"next/og\";\nimport { cacheLife, cacheTag } from \"next/cache\";\nimport { getMetaDescription } from \"@silicajs/core/runtime\";\nimport {\n getConfig,\n getPage,\n getRenderKey,\n normalizeRouteSlug,\n} from \"../server-data.js\";\nimport {\n clampText,\n hostnameFromBaseUrl,\n titleFontSize,\n} from \"./opengraph-image-format.js\";\n\nexport const OG_IMAGE_WIDTH = 1200;\nexport const OG_IMAGE_HEIGHT = 630;\n\nconst ACCENT = \"#8b5cf6\";\nconst BACKGROUND_TOP = \"#1c1330\";\nconst BACKGROUND_BOTTOM = \"#0c0717\";\n\ntype RouteContext = {\n params: Promise<{ slug?: string[] }> | { slug?: string[] };\n};\n\ntype OpengraphImageData = {\n siteTitle: string;\n title: string;\n description?: string;\n tags: string[];\n domain?: string;\n};\n\nexport async function GET(_request: Request, context: RouteContext) {\n const params = await context.params;\n const slug = normalizeRouteSlug(params?.slug);\n const renderKey = getRenderKey(slug);\n const data = await getOpengraphImageData(\n slug,\n renderKey.renderHash,\n renderKey.renderEnvironmentHash,\n );\n\n return new ImageResponse(<OpengraphImageCard {...data} />, {\n width: OG_IMAGE_WIDTH,\n height: OG_IMAGE_HEIGHT,\n });\n}\n\nasync function getOpengraphImageData(\n slug: string,\n renderHash: string,\n renderEnvironmentHash: string,\n): Promise<OpengraphImageData> {\n \"use cache\";\n cacheLife(\"max\");\n cacheTag(\n `environment:${renderEnvironmentHash}`,\n `page:${slug}`,\n `render:${renderHash}`,\n );\n const config = getConfig();\n const entry = getPage(slug);\n return {\n siteTitle: config.title,\n title: entry?.title ?? config.title,\n description: entry ? getMetaDescription(entry) : config.description,\n tags: entry?.tags?.slice(0, 4) ?? [],\n domain: hostnameFromBaseUrl(config.baseUrl),\n };\n}\n\nfunction OpengraphImageCard({\n siteTitle,\n title,\n description,\n tags,\n domain,\n}: OpengraphImageData) {\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n width: \"100%\",\n height: \"100%\",\n padding: \"80px\",\n color: \"#f8fafc\",\n backgroundColor: BACKGROUND_BOTTOM,\n backgroundImage: `linear-gradient(135deg, ${BACKGROUND_TOP} 0%, ${BACKGROUND_BOTTOM} 100%)`,\n fontFamily: \"sans-serif\",\n }}\n >\n <div style={{ display: \"flex\", alignItems: \"center\" }}>\n <div\n style={{\n width: \"20px\",\n height: \"44px\",\n borderRadius: \"6px\",\n backgroundColor: ACCENT,\n marginRight: \"24px\",\n }}\n />\n <div\n style={{\n fontSize: \"34px\",\n fontWeight: 600,\n color: \"#cbb6ff\",\n letterSpacing: \"-0.01em\",\n }}\n >\n {siteTitle}\n </div>\n </div>\n\n <div style={{ display: \"flex\", flexDirection: \"column\" }}>\n <div\n style={{\n display: \"flex\",\n fontSize: titleFontSize(title),\n fontWeight: 800,\n lineHeight: 1.05,\n letterSpacing: \"-0.02em\",\n }}\n >\n {clampText(title, 90)}\n </div>\n {description ? (\n <div\n style={{\n display: \"flex\",\n marginTop: \"28px\",\n fontSize: \"32px\",\n lineHeight: 1.35,\n color: \"#c7c9d9\",\n }}\n >\n {clampText(description, 160)}\n </div>\n ) : null}\n </div>\n\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n }}\n >\n <div style={{ display: \"flex\", alignItems: \"center\" }}>\n {tags.map((tag) => (\n <div\n key={tag}\n style={{\n display: \"flex\",\n marginRight: \"16px\",\n padding: \"8px 20px\",\n borderRadius: \"999px\",\n border: \"1px solid rgba(139, 92, 246, 0.5)\",\n color: \"#cbb6ff\",\n fontSize: \"24px\",\n }}\n >\n #{tag}\n </div>\n ))}\n </div>\n {domain ? (\n <div style={{ display: \"flex\", fontSize: \"26px\", color: \"#8c8fa3\" }}>\n {domain}\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -7,9 +7,29 @@ declare function generateStaticParams(): Promise<{
7
7
  declare function generateMetadata({ params }: PageProps): Promise<{
8
8
  title?: undefined;
9
9
  description?: undefined;
10
+ openGraph?: undefined;
11
+ twitter?: undefined;
10
12
  } | {
11
13
  title: string;
12
14
  description: string | undefined;
15
+ openGraph: {
16
+ type: string;
17
+ siteName: string;
18
+ title: string;
19
+ description: string | undefined;
20
+ images: {
21
+ url: string;
22
+ width: number;
23
+ height: number;
24
+ alt: string;
25
+ }[];
26
+ };
27
+ twitter: {
28
+ card: string;
29
+ title: string;
30
+ description: string | undefined;
31
+ images: string[];
32
+ };
13
33
  }>;
14
34
  type PageProps = {
15
35
  params: Promise<{
@@ -12,6 +12,7 @@ import { SilicaLink } from "@silicajs/components/routing";
12
12
  import {
13
13
  getBacklinks,
14
14
  getBreadcrumbs,
15
+ getConfig,
15
16
  getPage,
16
17
  getPageRuntimeData,
17
18
  getPrerenderSlugs,
@@ -20,6 +21,7 @@ import {
20
21
  resolveWikiLinkFromDb,
21
22
  normalizeRouteSlug
22
23
  } from "../server-data.js";
24
+ import { opengraphImagePath } from "./opengraph-image-format.js";
23
25
  function MarkdownLink({
24
26
  href,
25
27
  ...props
@@ -46,9 +48,25 @@ async function generateMetadata({ params }) {
46
48
  renderKey.renderEnvironmentHash
47
49
  );
48
50
  if (!entry) return {};
51
+ const description = getMetaDescription(entry);
52
+ const config = getConfig();
53
+ const imageUrl = opengraphImagePath(slug);
49
54
  return {
50
55
  title: entry.title,
51
- description: getMetaDescription(entry)
56
+ description,
57
+ openGraph: {
58
+ type: "article",
59
+ siteName: config.title,
60
+ title: entry.title,
61
+ description,
62
+ images: [{ url: imageUrl, width: 1200, height: 630, alt: entry.title }]
63
+ },
64
+ twitter: {
65
+ card: "summary_large_image",
66
+ title: entry.title,
67
+ description,
68
+ images: [imageUrl]
69
+ }
52
70
  };
53
71
  }
54
72
  async function getPageMetadata(slug, renderHash, renderEnvironmentHash) {
@@ -80,6 +98,16 @@ async function VaultContent({
80
98
  const renderContext = (currentSlug, currentSourcePath, embedDepth = 0) => ({
81
99
  slug: currentSlug,
82
100
  sourcePath: currentSourcePath,
101
+ assetBaseUrl: "/silica",
102
+ wikilinkStrategy: config.wikilinks.strategy,
103
+ tags: config.tags,
104
+ ordering: config.ordering,
105
+ embedDepth,
106
+ maxEmbedDepth: 3,
107
+ components: {
108
+ ...theme.components,
109
+ a: MarkdownLink
110
+ },
83
111
  resolveWikiLink: (_currentSlug, target) => resolveWikiLinkFromDb(
84
112
  currentSlug,
85
113
  target,
@@ -92,16 +120,6 @@ async function VaultContent({
92
120
  config.wikilinks.strategy,
93
121
  config.ordering
94
122
  ),
95
- assetBaseUrl: "/silica",
96
- wikilinkStrategy: config.wikilinks.strategy,
97
- tags: config.tags,
98
- ordering: config.ordering,
99
- embedDepth,
100
- maxEmbedDepth: 3,
101
- components: {
102
- ...theme.components,
103
- a: MarkdownLink
104
- },
105
123
  resolveEmbed: async (target) => {
106
124
  const resolved = resolveWikiLinkFromDb(
107
125
  currentSlug,