@aravindc26/velu 0.13.5 → 0.13.6
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/package.json
CHANGED
|
@@ -141,15 +141,10 @@ function prefixMdxComponentLinks(
|
|
|
141
141
|
export default async function PreviewPage({ params }: PageProps) {
|
|
142
142
|
const { sessionId, slug } = await params;
|
|
143
143
|
|
|
144
|
-
console.log(`[PREVIEW:page] START session=${sessionId} slug=${slug.join('/')}`);
|
|
145
|
-
|
|
146
144
|
const src = await getSessionSource(sessionId);
|
|
147
145
|
const page = src.getPage(slug);
|
|
148
146
|
|
|
149
|
-
if (!page)
|
|
150
|
-
console.log(`[PREVIEW:page] Page NOT FOUND for slug=${slug.join('/')}`);
|
|
151
|
-
notFound();
|
|
152
|
-
}
|
|
147
|
+
if (!page) notFound();
|
|
153
148
|
|
|
154
149
|
const pageDataRecord = page.data as unknown as Record<string, unknown>;
|
|
155
150
|
|
|
@@ -159,22 +154,16 @@ export default async function PreviewPage({ params }: PageProps) {
|
|
|
159
154
|
let pageToc: any;
|
|
160
155
|
|
|
161
156
|
if (typeof loadFn === 'function') {
|
|
162
|
-
console.log(`[PREVIEW:page] Calling load() for slug=${slug.join('/')}`);
|
|
163
157
|
const loaded = await loadFn();
|
|
164
158
|
MDX = loaded.body;
|
|
165
159
|
pageToc = loaded.toc;
|
|
166
|
-
console.log(`[PREVIEW:page] load() returned MDX=${typeof MDX} toc=${typeof pageToc}`);
|
|
167
160
|
} else {
|
|
168
161
|
// Fallback: pre-compiled entry (shouldn't happen in preview mode, but safe)
|
|
169
|
-
console.log(`[PREVIEW:page] No load() — using pre-compiled body for slug=${slug.join('/')}`);
|
|
170
162
|
MDX = pageDataRecord.body as any;
|
|
171
163
|
pageToc = pageDataRecord.toc as any;
|
|
172
164
|
}
|
|
173
165
|
|
|
174
|
-
if (typeof MDX !== 'function')
|
|
175
|
-
console.log(`[PREVIEW:page] MDX is not a function (type=${typeof MDX}), returning 404`);
|
|
176
|
-
notFound();
|
|
177
|
-
}
|
|
166
|
+
if (typeof MDX !== 'function') notFound();
|
|
178
167
|
|
|
179
168
|
const configSource = loadSessionConfigSource(sessionId);
|
|
180
169
|
const footerSocials = configSource ? getFooterSocials(configSource) : [];
|
|
@@ -186,7 +175,6 @@ export default async function PreviewPage({ params }: PageProps) {
|
|
|
186
175
|
if (pageInfo?.fullPath) {
|
|
187
176
|
try {
|
|
188
177
|
effectiveMarkdown = readFileSync(pageInfo.fullPath, 'utf-8');
|
|
189
|
-
console.log(`[PREVIEW:page] Read markdown from ${pageInfo.fullPath} (${effectiveMarkdown.length} chars, first 120: ${JSON.stringify(effectiveMarkdown.slice(0, 120))})`);
|
|
190
178
|
} catch { /* file may not exist */ }
|
|
191
179
|
}
|
|
192
180
|
|
|
@@ -755,10 +755,9 @@ export function syncSessionFile(
|
|
|
755
755
|
const workspaceDir = join(WORKSPACE_DIR, sessionId);
|
|
756
756
|
const outputDir = join(PREVIEW_CONTENT_DIR, sessionId);
|
|
757
757
|
|
|
758
|
-
console.log(`[PREVIEW:syncFile] session=${sessionId} file=${filePath}
|
|
758
|
+
console.log(`[PREVIEW:syncFile] session=${sessionId} file=${filePath}`);
|
|
759
759
|
|
|
760
760
|
if (filePath === PRIMARY_CONFIG_NAME || filePath === LEGACY_CONFIG_NAME) {
|
|
761
|
-
console.log(`[PREVIEW:syncFile] Config file changed — regenerating all content`);
|
|
762
761
|
generateSessionContent(sessionId);
|
|
763
762
|
return { synced: true };
|
|
764
763
|
}
|
|
@@ -780,13 +779,9 @@ export function syncSessionFile(
|
|
|
780
779
|
srcPath = join(workspaceDir, `${stripped}.md`);
|
|
781
780
|
}
|
|
782
781
|
if (!existsSync(srcPath)) {
|
|
783
|
-
console.log(`[PREVIEW:syncFile] Source file NOT FOUND at any candidate path for ${filePath}`);
|
|
784
782
|
return { synced: false };
|
|
785
783
|
}
|
|
786
784
|
|
|
787
|
-
const srcContent = readFileSync(srcPath, 'utf-8');
|
|
788
|
-
console.log(`[PREVIEW:syncFile] Source file: ${srcPath} (${srcContent.length} chars, first 120: ${JSON.stringify(srcContent.slice(0, 120))})`);
|
|
789
|
-
|
|
790
785
|
try {
|
|
791
786
|
const { config } = loadConfig(workspaceDir);
|
|
792
787
|
const artifacts = buildArtifacts(config, workspaceDir);
|
|
@@ -796,22 +791,15 @@ export function syncSessionFile(
|
|
|
796
791
|
|
|
797
792
|
if (mapping) {
|
|
798
793
|
const destPath = join(outputDir, `${mapping.dest}.mdx`);
|
|
799
|
-
console.log(`[PREVIEW:syncFile] Mapped: ${stripped} -> ${mapping.dest}, writing to ${destPath}`);
|
|
800
794
|
processPage(srcPath, destPath, stripped, variables, sessionId);
|
|
801
|
-
const written = readFileSync(destPath, 'utf-8');
|
|
802
|
-
console.log(`[PREVIEW:syncFile] Written dest (${written.length} chars, first 120: ${JSON.stringify(written.slice(0, 120))})`);
|
|
803
795
|
return { synced: true };
|
|
804
796
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
console.log(`[PREVIEW:syncFile] Mapping lookup failed, falling through to direct copy:`, err instanceof Error ? err.message : err);
|
|
797
|
+
} catch {
|
|
798
|
+
// Fall through to direct copy
|
|
808
799
|
}
|
|
809
800
|
|
|
810
801
|
const destPath = join(outputDir, `${stripped}.mdx`);
|
|
811
|
-
console.log(`[PREVIEW:syncFile] Direct copy: ${srcPath} -> ${destPath}`);
|
|
812
802
|
processPage(srcPath, destPath, stripped, variables, sessionId);
|
|
813
|
-
const written = readFileSync(destPath, 'utf-8');
|
|
814
|
-
console.log(`[PREVIEW:syncFile] Written dest (${written.length} chars, first 120: ${JSON.stringify(written.slice(0, 120))})`);
|
|
815
803
|
return { synced: true };
|
|
816
804
|
}
|
|
817
805
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Used only in preview mode (PREVIEW_MODE=true) — production builds still use
|
|
10
10
|
* the standard `source.ts` with build-time collections.
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync, readFileSync, readdirSync,
|
|
12
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
13
13
|
import { join, relative } from 'node:path';
|
|
14
14
|
import { loader } from 'fumadocs-core/source';
|
|
15
15
|
import { dynamic } from 'fumadocs-mdx/runtime/dynamic';
|
|
@@ -31,6 +31,30 @@ function log(tag: string, msg: string, data?: Record<string, unknown>) {
|
|
|
31
31
|
|
|
32
32
|
// ── Cache ──────────────────────────────────────────────────────────────────
|
|
33
33
|
|
|
34
|
+
// File-based invalidation signal. Next.js bundles API routes and page routes
|
|
35
|
+
// into separate module instances, so in-memory invalidation from the sync/init
|
|
36
|
+
// route handler does NOT clear the cache seen by the page renderer. Instead,
|
|
37
|
+
// the invalidation writes a timestamp to a file on disk which both sides can see.
|
|
38
|
+
const INVALIDATION_DIR = join(PREVIEW_CONTENT_DIR, '.invalidation');
|
|
39
|
+
|
|
40
|
+
function getInvalidationPath(sessionId: string): string {
|
|
41
|
+
return join(INVALIDATION_DIR, `${sessionId}.stamp`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readInvalidationStamp(sessionId: string): number {
|
|
45
|
+
const p = getInvalidationPath(sessionId);
|
|
46
|
+
try {
|
|
47
|
+
return Number(readFileSync(p, 'utf-8').trim()) || 0;
|
|
48
|
+
} catch {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function writeInvalidationStamp(sessionId: string): void {
|
|
54
|
+
mkdirSync(INVALIDATION_DIR, { recursive: true });
|
|
55
|
+
writeFileSync(getInvalidationPath(sessionId), String(Date.now()), 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
|
|
34
58
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
59
|
const sourceCache = new Map<string, { source: any; createdAt: number }>();
|
|
36
60
|
|
|
@@ -154,11 +178,17 @@ function scanContentDir(sessionDir: string): {
|
|
|
154
178
|
export async function getSessionSource(sessionId: string) {
|
|
155
179
|
const cached = sourceCache.get(sessionId);
|
|
156
180
|
if (cached) {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
181
|
+
const stamp = readInvalidationStamp(sessionId);
|
|
182
|
+
if (stamp <= cached.createdAt) {
|
|
183
|
+
const age = Date.now() - cached.createdAt;
|
|
184
|
+
log('source', `Cache HIT for session ${sessionId}`, { ageMs: age });
|
|
185
|
+
return cached.source;
|
|
186
|
+
}
|
|
187
|
+
log('source', `Cache STALE for session ${sessionId} (stamp=${stamp} > created=${cached.createdAt}), rebuilding`);
|
|
188
|
+
sourceCache.delete(sessionId);
|
|
189
|
+
} else {
|
|
190
|
+
log('source', `Cache MISS for session ${sessionId}`);
|
|
160
191
|
}
|
|
161
|
-
log('source', `Cache MISS for session ${sessionId}`);
|
|
162
192
|
|
|
163
193
|
const sessionDir = join(PREVIEW_CONTENT_DIR, sessionId);
|
|
164
194
|
if (!existsSync(sessionDir)) {
|
|
@@ -176,15 +206,6 @@ export async function getSessionSource(sessionId: string) {
|
|
|
176
206
|
log('source', `Scanned ${sessionDir}`, {
|
|
177
207
|
entryCount: entries.length,
|
|
178
208
|
metaFileCount: Object.keys(metaFiles).length,
|
|
179
|
-
files: entries.map((e) => {
|
|
180
|
-
const stat = statSync(e.info.fullPath, { throwIfNoEntry: false });
|
|
181
|
-
return {
|
|
182
|
-
path: e.info.path,
|
|
183
|
-
title: e.data.title,
|
|
184
|
-
sizeBytes: stat?.size,
|
|
185
|
-
mtimeMs: stat?.mtimeMs,
|
|
186
|
-
};
|
|
187
|
-
}),
|
|
188
209
|
});
|
|
189
210
|
|
|
190
211
|
const collection = await dyn.docs('docs', sessionDir, metaFiles, entries);
|
|
@@ -222,12 +243,15 @@ export async function getSessionSource(sessionId: string) {
|
|
|
222
243
|
|
|
223
244
|
/**
|
|
224
245
|
* Invalidate the cached source for a session.
|
|
225
|
-
*
|
|
246
|
+
* Writes a file-based timestamp so that ALL Next.js module instances
|
|
247
|
+
* (API routes AND page routes) see the invalidation, even though they
|
|
248
|
+
* run in separate bundles with separate in-memory Maps.
|
|
226
249
|
*/
|
|
227
250
|
export function invalidateSessionSource(sessionId: string): void {
|
|
228
251
|
const had = sourceCache.has(sessionId);
|
|
229
252
|
sourceCache.delete(sessionId);
|
|
230
|
-
|
|
253
|
+
writeInvalidationStamp(sessionId);
|
|
254
|
+
log('invalidate', `session=${sessionId} hadCache=${had} wroteStamp=true`);
|
|
231
255
|
}
|
|
232
256
|
|
|
233
257
|
/**
|