@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.
- package/dist/cache-handlers/filesystem.js +12 -7
- package/dist/cache-handlers/filesystem.js.map +1 -1
- package/dist/generated-app-package-manifest.d.ts +7 -0
- package/dist/generated-app-package-manifest.js +28 -0
- package/dist/generated-app-package-manifest.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/routes/layout.d.ts +13 -0
- package/dist/routes/layout.js +23 -2
- package/dist/routes/layout.js.map +1 -1
- package/dist/routes/opengraph-image-format.d.ts +10 -0
- package/dist/routes/opengraph-image-format.js +28 -0
- package/dist/routes/opengraph-image-format.js.map +1 -0
- package/dist/routes/opengraph-image.d.ts +14 -0
- package/dist/routes/opengraph-image.js +171 -0
- package/dist/routes/opengraph-image.js.map +1 -0
- package/dist/routes/page.d.ts +20 -0
- package/dist/routes/page.js +29 -11
- package/dist/routes/page.js.map +1 -1
- package/dist/runtime-paths.d.ts +10 -0
- package/dist/runtime-paths.js +98 -0
- package/dist/runtime-paths.js.map +1 -0
- package/dist/server-data.d.ts +2 -1
- package/dist/server-data.js +16 -8
- package/dist/server-data.js.map +1 -1
- package/dist/template-files/generated-app/app/(site)/layout.tsx +6 -1
- package/dist/template-files/generated-app/app/api/silica/og/[[...slug]]/route.ts +1 -0
- package/dist/template-files/next.config.ts +44 -10
- package/dist/templates.d.ts +5 -3
- package/dist/templates.js +52 -3
- package/dist/templates.js.map +1 -1
- package/package.json +27 -9
|
@@ -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
|
|
83
|
-
const
|
|
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(
|
|
88
|
+
return path.isAbsolute(configured) ? configured : path.join(appRoot, configured);
|
|
87
89
|
}
|
|
88
|
-
return path.join(
|
|
90
|
+
return path.join(appRoot, "data/cache/next");
|
|
89
91
|
}
|
|
90
|
-
function readConfigFromVaultDb(
|
|
91
|
-
const databasePath = path.join(
|
|
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 =
|
|
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 {
|
|
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":[]}
|
package/dist/routes/layout.d.ts
CHANGED
|
@@ -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
|
|
package/dist/routes/layout.js
CHANGED
|
@@ -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,
|
|
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":[]}
|
package/dist/routes/page.d.ts
CHANGED
|
@@ -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<{
|
package/dist/routes/page.js
CHANGED
|
@@ -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
|
|
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,
|