veryfront 0.1.82 → 0.1.83
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/esm/deno.js +1 -1
- package/esm/src/platform/adapters/base.d.ts +4 -1
- package/esm/src/platform/adapters/base.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/github/adapter.d.ts +2 -1
- package/esm/src/platform/adapters/fs/github/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/github/adapter.js +2 -2
- package/esm/src/platform/adapters/fs/github/stat-operations.d.ts +2 -1
- package/esm/src/platform/adapters/fs/github/stat-operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/github/stat-operations.js +2 -2
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts +2 -2
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +17 -2
- package/esm/src/platform/adapters/fs/veryfront/multi-project-adapter.d.ts +2 -2
- package/esm/src/platform/adapters/fs/veryfront/multi-project-adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/multi-project-adapter.js +2 -2
- package/esm/src/platform/adapters/fs/veryfront/read-operations.d.ts +1 -0
- package/esm/src/platform/adapters/fs/veryfront/read-operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.d.ts +6 -2
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/stat-operations.js +131 -21
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts +2 -1
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/wrapper.d.ts +2 -2
- package/esm/src/platform/adapters/fs/wrapper.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/wrapper.js +2 -2
- package/esm/src/rendering/app-route-resolver.js +0 -9
- package/esm/src/rendering/orchestrator/file-resolver/index.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/file-resolver/index.js +17 -0
- package/esm/src/rendering/page-resolution/page-resolver.d.ts.map +1 -1
- package/esm/src/rendering/page-resolution/page-resolver.js +19 -6
- package/esm/src/rendering/router-detection.d.ts +1 -0
- package/esm/src/rendering/router-detection.d.ts.map +1 -1
- package/esm/src/rendering/router-detection.js +3 -0
- package/esm/src/types/entities/getEntityInfo.d.ts.map +1 -1
- package/esm/src/types/entities/getEntityInfo.js +59 -42
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/platform/adapters/base.ts +5 -1
- package/src/src/platform/adapters/fs/github/adapter.ts +3 -2
- package/src/src/platform/adapters/fs/github/stat-operations.ts +3 -2
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +24 -3
- package/src/src/platform/adapters/fs/veryfront/multi-project-adapter.ts +6 -3
- package/src/src/platform/adapters/fs/veryfront/read-operations.ts +1 -0
- package/src/src/platform/adapters/fs/veryfront/stat-operations.ts +161 -25
- package/src/src/platform/adapters/fs/veryfront/types.ts +2 -1
- package/src/src/platform/adapters/fs/wrapper.ts +10 -3
- package/src/src/rendering/app-route-resolver.ts +0 -8
- package/src/src/rendering/orchestrator/file-resolver/index.ts +19 -0
- package/src/src/rendering/page-resolution/page-resolver.ts +26 -17
- package/src/src/rendering/router-detection.ts +7 -0
- package/src/src/types/entities/getEntityInfo.ts +78 -59
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-resolver.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/page-resolution/page-resolver.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"page-resolver.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/page-resolution/page-resolver.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAoCvD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAiB;gBAEpB,OAAO,EAAE,mBAAmB;IAOxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAsDxC,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YAoCxB,sBAAsB;IAoD9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAY1C,aAAa,IAAI,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC;CAShD"}
|
|
@@ -4,7 +4,7 @@ import { VeryfrontError } from "../../errors/index.js";
|
|
|
4
4
|
import { FILE_NOT_FOUND } from "../../errors/error-registry.js";
|
|
5
5
|
import { withSpan } from "../../observability/tracing/otlp-setup.js";
|
|
6
6
|
import { getEntityBySlug } from "../../types/entities/getEntityInfo.js";
|
|
7
|
-
import { detectAppRouter, getAppRouteEntity } from "../router-detection.js";
|
|
7
|
+
import { detectAppRouter, getAppRouteEntity, primeRouterDetectionCache, } from "../router-detection.js";
|
|
8
8
|
const PAGE_EXTENSIONS = /\.(mdx|md|tsx|jsx|ts|js)$/;
|
|
9
9
|
const APP_ROUTER_PAGE_PATTERN = /^page\.(mdx|md|tsx|jsx|ts|js)$/;
|
|
10
10
|
function isPageFile(name) {
|
|
@@ -40,20 +40,33 @@ export class PageResolver {
|
|
|
40
40
|
}
|
|
41
41
|
resolvePage(slug) {
|
|
42
42
|
return withSpan("routing.resolve_page", async () => {
|
|
43
|
-
const useAppRouter = await detectAppRouter(this.projectDir, this.config, this.adapter, { projectId: this.projectId });
|
|
44
43
|
const appDirName = this.config.directories?.app ?? "app";
|
|
44
|
+
const cacheKey = this.projectId ?? this.projectDir;
|
|
45
45
|
let pageInfo;
|
|
46
|
-
if (
|
|
46
|
+
if (this.config.router === "app") {
|
|
47
|
+
pageInfo = await getAppRouteEntity(this.projectDir, slug, this.adapter, appDirName);
|
|
48
|
+
if (pageInfo) {
|
|
49
|
+
primeRouterDetectionCache(cacheKey, "app");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (this.config.router === "pages") {
|
|
53
|
+
pageInfo = await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
54
|
+
if (pageInfo) {
|
|
55
|
+
primeRouterDetectionCache(cacheKey, "pages");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Auto mode stays structural: a single resolved route must not pin router mode
|
|
60
|
+
// for projects that are still transitioning between app/ and pages/.
|
|
47
61
|
pageInfo = await getAppRouteEntity(this.projectDir, slug, this.adapter, appDirName);
|
|
48
62
|
if (!pageInfo) {
|
|
49
|
-
|
|
63
|
+
pageInfo = await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
50
64
|
}
|
|
51
65
|
}
|
|
52
|
-
pageInfo ??= await getEntityBySlug(this.projectDir, slug, this.adapter);
|
|
53
66
|
if (!pageInfo) {
|
|
54
67
|
throw FILE_NOT_FOUND.create({
|
|
55
68
|
detail: `Page not found: ${slug}`,
|
|
56
|
-
context: { slug,
|
|
69
|
+
context: { slug, router: this.config.router ?? "auto" },
|
|
57
70
|
});
|
|
58
71
|
}
|
|
59
72
|
return pageInfo;
|
|
@@ -21,6 +21,7 @@ export declare function clearRouterDetectionCache(): void;
|
|
|
21
21
|
* @param projectId - The project ID used as cache key. Falls back to projectDir for local dev.
|
|
22
22
|
*/
|
|
23
23
|
export declare function clearRouterDetectionCacheForProject(projectId: string): void;
|
|
24
|
+
export declare function primeRouterDetectionCache(projectKey: string, mode: "app" | "pages"): void;
|
|
24
25
|
export interface DetectAppRouterOptions {
|
|
25
26
|
/** Project ID for cache isolation in multi-tenant deployments */
|
|
26
27
|
projectId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-detection.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/router-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;4BAO4B;AAK5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAQ1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAY5D;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC,CA6BlB"}
|
|
1
|
+
{"version":3,"file":"router-detection.d.ts","sourceRoot":"","sources":["../../../src/src/rendering/router-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;4BAO4B;AAK5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAQ1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAY5D;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,KAAK,GAAG,OAAO,GACpB,IAAI,CAEN;AAED,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC,CA6BlB"}
|
|
@@ -38,6 +38,9 @@ export function clearRouterDetectionCache() {
|
|
|
38
38
|
export function clearRouterDetectionCacheForProject(projectId) {
|
|
39
39
|
routerDetectionCache.delete(projectId);
|
|
40
40
|
}
|
|
41
|
+
export function primeRouterDetectionCache(projectKey, mode) {
|
|
42
|
+
routerDetectionCache.set(projectKey, mode === "app");
|
|
43
|
+
}
|
|
41
44
|
/**
|
|
42
45
|
* Detect if app router should be used based on config and directory structure.
|
|
43
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getEntityInfo.d.ts","sourceRoot":"","sources":["../../../../src/src/types/entities/getEntityInfo.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAU,UAAU,EAAe,MAAM,gBAAgB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAWtE,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"getEntityInfo.d.ts","sourceRoot":"","sources":["../../../../src/src/types/entities/getEntityInfo.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAU,UAAU,EAAe,MAAM,gBAAgB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAWtE,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAmJ5B;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAwM5B;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyE5B"}
|
|
@@ -29,37 +29,42 @@ export async function getEntityInfo(filePath, adapter) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
try {
|
|
32
|
+
const shouldReadDirectly = adapter
|
|
33
|
+
? isExtendedFSAdapter(adapter.fs) && adapter.fs.isVeryfrontAdapter()
|
|
34
|
+
: false;
|
|
35
|
+
let content;
|
|
32
36
|
if (adapter) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
if (!shouldReadDirectly) {
|
|
38
|
+
try {
|
|
39
|
+
const stat = await withFallback(() => adapter.fs.stat(normalizedPath), async () => {
|
|
40
|
+
const exists = await fs.exists(filePath);
|
|
41
|
+
if (!exists) {
|
|
42
|
+
throw toError(createError({
|
|
43
|
+
type: "file",
|
|
44
|
+
message: "File not found",
|
|
45
|
+
context: { path: filePath, operation: "read" },
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
return await fs.stat(filePath);
|
|
49
|
+
}, { operationName: "stat:getEntityInfo", logError: false });
|
|
50
|
+
if (!stat.isFile)
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
entityInfoScope.runSync(() => {
|
|
55
|
+
throw error;
|
|
56
|
+
}, { path: filePath, details: { reason: "stat-failed" } }, undefined);
|
|
46
57
|
return null;
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
|
-
|
|
49
|
-
entityInfoScope.runSync(() => {
|
|
50
|
-
throw error;
|
|
51
|
-
}, { path: filePath, details: { reason: "stat-failed" } }, undefined);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
60
|
+
content = await withFallback(() => adapter.fs.readFile(normalizedPath), () => fs.readTextFile(filePath), { operationName: "readFile:getEntityInfo", logError: false });
|
|
54
61
|
}
|
|
55
62
|
else {
|
|
56
63
|
const exists = await fs.exists(filePath);
|
|
57
64
|
if (!exists)
|
|
58
65
|
return null;
|
|
66
|
+
content = await fs.readTextFile(filePath);
|
|
59
67
|
}
|
|
60
|
-
const content = adapter
|
|
61
|
-
? await withFallback(() => adapter.fs.readFile(normalizedPath), () => fs.readTextFile(filePath), { operationName: "readFile:getEntityInfo", logError: false })
|
|
62
|
-
: await fs.readTextFile(filePath);
|
|
63
68
|
const ext = pathHelper.extname(filePath).toLowerCase();
|
|
64
69
|
let frontmatter = {};
|
|
65
70
|
let body = content;
|
|
@@ -120,23 +125,27 @@ export async function getEntityInfo(filePath, adapter) {
|
|
|
120
125
|
}
|
|
121
126
|
export async function getEntityBySlug(projectDir, slug, adapter) {
|
|
122
127
|
return await withSpan("types.getEntityBySlug", async () => {
|
|
123
|
-
const
|
|
128
|
+
const normalizedSlug = normalizeSlug(slug);
|
|
129
|
+
const isVeryfrontRoute = normalizedSlug.startsWith(".veryfront/") ||
|
|
130
|
+
normalizedSlug === ".veryfront";
|
|
124
131
|
const resolveFile = adapter?.fs.resolveFile;
|
|
125
132
|
logger.debug("START", {
|
|
126
133
|
slug,
|
|
134
|
+
normalizedSlug,
|
|
127
135
|
projectDir,
|
|
128
136
|
isVeryfrontRoute,
|
|
129
137
|
hasResolveFile: !!resolveFile,
|
|
130
138
|
});
|
|
131
139
|
if (resolveFile) {
|
|
132
|
-
const basePaths = [pathHelper.join(projectDir, "pages",
|
|
140
|
+
const basePaths = [pathHelper.join(projectDir, "pages", normalizedSlug)];
|
|
133
141
|
if (isVeryfrontRoute)
|
|
134
|
-
basePaths.unshift(pathHelper.join(projectDir,
|
|
135
|
-
if (
|
|
142
|
+
basePaths.unshift(pathHelper.join(projectDir, normalizedSlug));
|
|
143
|
+
if (normalizedSlug === "index" || normalizedSlug === "") {
|
|
136
144
|
basePaths.unshift(pathHelper.join(projectDir, "pages", "index"));
|
|
137
145
|
}
|
|
138
146
|
logger.debug("Checking paths (resolveFile branch)", {
|
|
139
147
|
slug,
|
|
148
|
+
normalizedSlug,
|
|
140
149
|
basePaths,
|
|
141
150
|
});
|
|
142
151
|
const pathResults = await parallelMap(basePaths, async (basePath) => {
|
|
@@ -153,12 +162,13 @@ export async function getEntityBySlug(projectDir, slug, adapter) {
|
|
|
153
162
|
if (info?.entity.isPage) {
|
|
154
163
|
logger.debug("Found page via resolveFile", {
|
|
155
164
|
slug,
|
|
165
|
+
normalizedSlug,
|
|
156
166
|
path: info.entity.path,
|
|
157
167
|
});
|
|
158
168
|
return info;
|
|
159
169
|
}
|
|
160
170
|
}
|
|
161
|
-
const slugParts =
|
|
171
|
+
const slugParts = normalizedSlug === "" ? [] : normalizedSlug.split("/");
|
|
162
172
|
for (let depth = slugParts.length - 1; depth >= 0; depth--) {
|
|
163
173
|
const parentPath = slugParts.slice(0, depth).join("/");
|
|
164
174
|
const pagesDir = parentPath
|
|
@@ -194,25 +204,25 @@ export async function getEntityBySlug(projectDir, slug, adapter) {
|
|
|
194
204
|
/* expected: directory may not exist or readDir may fail */
|
|
195
205
|
}
|
|
196
206
|
}
|
|
197
|
-
logger.debug("No page found via resolveFile branch", { slug });
|
|
207
|
+
logger.debug("No page found via resolveFile branch", { slug, normalizedSlug });
|
|
198
208
|
return null;
|
|
199
209
|
}
|
|
200
210
|
const possiblePaths = [
|
|
201
|
-
pathHelper.join(projectDir, "pages", `${
|
|
202
|
-
pathHelper.join(projectDir, "pages", `${
|
|
203
|
-
pathHelper.join(projectDir, "pages", `${
|
|
204
|
-
pathHelper.join(projectDir, "pages", `${
|
|
205
|
-
pathHelper.join(projectDir, "pages", `${
|
|
206
|
-
pathHelper.join(projectDir, "pages", `${
|
|
207
|
-
pathHelper.join(projectDir, "pages", `${
|
|
208
|
-
pathHelper.join(projectDir, "pages", `${
|
|
209
|
-
pathHelper.join(projectDir, "pages", `${
|
|
210
|
-
pathHelper.join(projectDir, "pages", `${
|
|
211
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}.mdx`),
|
|
212
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}.md`),
|
|
213
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}.tsx`),
|
|
214
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}.jsx`),
|
|
215
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}.ts`),
|
|
216
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}/index.mdx`),
|
|
217
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}/index.md`),
|
|
218
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}/index.tsx`),
|
|
219
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}/index.jsx`),
|
|
220
|
+
pathHelper.join(projectDir, "pages", `${normalizedSlug}/index.ts`),
|
|
211
221
|
];
|
|
212
222
|
if (isVeryfrontRoute) {
|
|
213
|
-
possiblePaths.unshift(pathHelper.join(projectDir, `${
|
|
223
|
+
possiblePaths.unshift(pathHelper.join(projectDir, `${normalizedSlug}.mdx`), pathHelper.join(projectDir, `${normalizedSlug}.md`), pathHelper.join(projectDir, `${normalizedSlug}.tsx`), pathHelper.join(projectDir, `${normalizedSlug}.ts`));
|
|
214
224
|
}
|
|
215
|
-
if (
|
|
225
|
+
if (normalizedSlug === "index" || normalizedSlug === "") {
|
|
216
226
|
possiblePaths.unshift(pathHelper.join(projectDir, "pages", "index.mdx"), pathHelper.join(projectDir, "pages", "index.md"), pathHelper.join(projectDir, "pages", "index.tsx"), pathHelper.join(projectDir, "pages", "index.ts"));
|
|
217
227
|
}
|
|
218
228
|
const pathResults = await parallelMap(possiblePaths, async (p) => {
|
|
@@ -222,7 +232,7 @@ export async function getEntityBySlug(projectDir, slug, adapter) {
|
|
|
222
232
|
if (info?.entity.isPage)
|
|
223
233
|
return info;
|
|
224
234
|
}
|
|
225
|
-
const slugParts =
|
|
235
|
+
const slugParts = normalizedSlug === "" ? [] : normalizedSlug.split("/");
|
|
226
236
|
for (let depth = slugParts.length - 1; depth >= 0; depth--) {
|
|
227
237
|
const parentPath = slugParts.slice(0, depth).join("/");
|
|
228
238
|
const pagesDir = parentPath
|
|
@@ -267,7 +277,11 @@ export async function getEntityBySlug(projectDir, slug, adapter) {
|
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
return null;
|
|
270
|
-
}, {
|
|
280
|
+
}, {
|
|
281
|
+
"entity.slug": slug,
|
|
282
|
+
"entity.normalized_slug": normalizeSlug(slug),
|
|
283
|
+
"entity.projectDir": projectDir,
|
|
284
|
+
});
|
|
271
285
|
}
|
|
272
286
|
export async function getLayoutEntity(projectDir, layoutName, adapter) {
|
|
273
287
|
return await withSpan("types.getLayoutEntity", async () => {
|
|
@@ -345,3 +359,6 @@ function getSlugFromPath(filePath) {
|
|
|
345
359
|
const parentDir = parts[parts.length - 2];
|
|
346
360
|
return parentDir === "pages" ? "" : parentDir ?? "";
|
|
347
361
|
}
|
|
362
|
+
function normalizeSlug(slug) {
|
|
363
|
+
return slug === "/" ? "" : slug.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
364
|
+
}
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -117,7 +117,11 @@ export interface FileSystemAdapter {
|
|
|
117
117
|
makeTempDir(prefix: string): Promise<string>;
|
|
118
118
|
watch(paths: string | string[], options?: WatchOptions): FileWatcher;
|
|
119
119
|
/** Resolve a file path with extension fallback (e.g., pages/test → pages/test.mdx) */
|
|
120
|
-
resolveFile?(basePath: string): Promise<string | null>;
|
|
120
|
+
resolveFile?(basePath: string, options?: ResolveFileOptions): Promise<string | null>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface ResolveFileOptions {
|
|
124
|
+
allowPagesPrefix?: boolean;
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
export interface DirEntry {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from "../../../../utils/index.js";
|
|
2
2
|
import { CONFIG_INVALID } from "../../../../errors/index.js";
|
|
3
3
|
import { getEnv } from "../../../compat/process.js";
|
|
4
|
+
import type { ResolveFileOptions } from "../../base.js";
|
|
4
5
|
import { FileCache } from "../cache/file-cache.js";
|
|
5
6
|
import type { FSAdapter, FSAdapterConfig } from "../veryfront/types.js";
|
|
6
7
|
import { GitHubApiClient } from "./github-api-client.js";
|
|
@@ -115,9 +116,9 @@ export class GitHubFSAdapter implements FSAdapter {
|
|
|
115
116
|
return this.dirOps.readdir(path);
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
async resolveFile(basePath: string): Promise<string | null> {
|
|
119
|
+
async resolveFile(basePath: string, options?: ResolveFileOptions): Promise<string | null> {
|
|
119
120
|
await this.ensureInitialized();
|
|
120
|
-
return this.statOps.resolveFile(basePath);
|
|
121
|
+
return this.statOps.resolveFile(basePath, options);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
getCacheStats(): {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
buildGitHubStatCacheKey,
|
|
6
6
|
buildGitHubTreeCacheKey,
|
|
7
7
|
} from "../../../../cache/index.js";
|
|
8
|
+
import type { ResolveFileOptions } from "../../base.js";
|
|
8
9
|
import type { FileCache } from "../cache/file-cache.js";
|
|
9
10
|
import type { GitHubApiClient } from "./github-api-client.js";
|
|
10
11
|
import type { FileIndexEntry, FileInfo, GitHubTreeEntry, ResolvedGitHubConfig } from "./types.js";
|
|
@@ -176,7 +177,7 @@ export class GitHubStatOperations {
|
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
async resolveFile(basePath: string): Promise<string | null> {
|
|
180
|
+
async resolveFile(basePath: string, options?: ResolveFileOptions): Promise<string | null> {
|
|
180
181
|
await this.ensureIndex();
|
|
181
182
|
|
|
182
183
|
const normalizedPath = normalizeGitHubPath(basePath, this.projectDir);
|
|
@@ -185,7 +186,7 @@ export class GitHubStatOperations {
|
|
|
185
186
|
if (cached !== undefined) return cached;
|
|
186
187
|
|
|
187
188
|
const resolved = this.tryResolve(normalizedPath) ??
|
|
188
|
-
this.tryResolveWithPagesPrefix(normalizedPath);
|
|
189
|
+
(options?.allowPagesPrefix === false ? null : this.tryResolveWithPagesPrefix(normalizedPath));
|
|
189
190
|
|
|
190
191
|
this.cache.set(cacheKey, resolved);
|
|
191
192
|
return resolved;
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
InvalidationCallbacks,
|
|
10
10
|
ResolvedContentContext,
|
|
11
11
|
} from "./types.js";
|
|
12
|
-
import type { FileInfo } from "../../base.js";
|
|
12
|
+
import type { FileInfo, ResolveFileOptions } from "../../base.js";
|
|
13
13
|
import { VeryfrontApiClient } from "../../veryfront-api-client/index.js";
|
|
14
14
|
import type { Project } from "../../veryfront-api-client/index.js";
|
|
15
15
|
import { FileCache } from "../cache/file-cache.js";
|
|
@@ -142,6 +142,24 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
142
142
|
|
|
143
143
|
return result;
|
|
144
144
|
},
|
|
145
|
+
hasCachedFileList: async () => {
|
|
146
|
+
if (!this.contentContext) {
|
|
147
|
+
logger.debug("hasCachedFileList: no contentContext");
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const cacheKey = buildFileListCacheKey(this.contentContext);
|
|
152
|
+
const result = await this.cache.getAsync<Array<{ path: string }>>(cacheKey);
|
|
153
|
+
const hasResult = Array.isArray(result) && result.length > 0;
|
|
154
|
+
|
|
155
|
+
logger.debug("hasCachedFileList lookup", {
|
|
156
|
+
cacheKey,
|
|
157
|
+
hasResult,
|
|
158
|
+
resultSize: result?.length ?? 0,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return hasResult;
|
|
162
|
+
},
|
|
145
163
|
isPersistentCacheInvalidated: (prefix: string) => this.isPersistentCacheInvalidated(prefix),
|
|
146
164
|
isReleaseBeingInvalidated: (releaseId: string) =>
|
|
147
165
|
this.isPersistentCacheInvalidated(
|
|
@@ -410,9 +428,12 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
410
428
|
return this.statOps.exists(path);
|
|
411
429
|
}
|
|
412
430
|
|
|
413
|
-
async resolveFile(
|
|
431
|
+
async resolveFile(
|
|
432
|
+
basePath: string,
|
|
433
|
+
options?: ResolveFileOptions,
|
|
434
|
+
): Promise<string | null> {
|
|
414
435
|
await this.ensureInitialized();
|
|
415
|
-
return this.statOps.resolveFile(basePath);
|
|
436
|
+
return this.statOps.resolveFile(basePath, options);
|
|
416
437
|
}
|
|
417
438
|
|
|
418
439
|
dispose(): void {
|
|
@@ -2,7 +2,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import { logger as baseLogger } from "../../../../utils/index.js";
|
|
3
3
|
import { INITIALIZATION_ERROR } from "../../../../errors/index.js";
|
|
4
4
|
import type { DirectoryEntry, FSAdapter, FSAdapterConfig } from "./types.js";
|
|
5
|
-
import type { FileInfo } from "../../base.js";
|
|
5
|
+
import type { FileInfo, ResolveFileOptions } from "../../base.js";
|
|
6
6
|
import { ProxyFSAdapterManager } from "./proxy-manager.js";
|
|
7
7
|
import type { VeryfrontFSAdapter } from "./index.js";
|
|
8
8
|
import { runWithCacheBatching } from "../../../../cache/request-cache-batcher.js";
|
|
@@ -213,9 +213,12 @@ export class MultiProjectFSAdapter implements FSAdapter {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
async resolveFile(
|
|
216
|
+
async resolveFile(
|
|
217
|
+
basePath: string,
|
|
218
|
+
options?: ResolveFileOptions,
|
|
219
|
+
): Promise<string | null> {
|
|
217
220
|
const adapter = await this.getAdapter();
|
|
218
|
-
return adapter.resolveFile(basePath);
|
|
221
|
+
return adapter.resolveFile(basePath, options);
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
dispose(): void {
|
|
@@ -41,6 +41,7 @@ export interface ContentContextProvider {
|
|
|
41
41
|
updated_at?: string;
|
|
42
42
|
}> | undefined
|
|
43
43
|
>;
|
|
44
|
+
hasCachedFileList?: () => Promise<boolean>;
|
|
44
45
|
/** True if cache prefix is being deleted - skip persistent cache reads */
|
|
45
46
|
isPersistentCacheInvalidated?: (prefix: string) => boolean;
|
|
46
47
|
/** Back-compat: release-scoped invalidation */
|