@pylonsync/sdk 0.3.383 → 0.3.384
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 +1 -1
- package/src/index.ts +25 -3
- package/src/route-discovery.test.ts +22 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1291,13 +1291,35 @@ export async function discoverAppRoutes(opts?: {
|
|
|
1291
1291
|
kind: "og-image" as const,
|
|
1292
1292
|
}));
|
|
1293
1293
|
|
|
1294
|
-
|
|
1294
|
+
// Every navigable route — pages, method handlers, sitemap/robots, dynamic
|
|
1295
|
+
// OG images — shares one first-match-wins table in the Rust matcher, so
|
|
1296
|
+
// they must ALL sort literal-before-parameterized together. Appending the
|
|
1297
|
+
// special kinds after pages let a top-level dynamic page shadow its
|
|
1298
|
+
// literal siblings: with `app/[orgSlug]/page.tsx` in the table first,
|
|
1299
|
+
// GET /opengraph-image (and /sitemap.xml, /robots.txt) matched `/:orgSlug`
|
|
1300
|
+
// and rendered the page's 404 instead of the special route. Boundaries are
|
|
1301
|
+
// excluded — they're picked by longest-prefix, not table order.
|
|
1302
|
+
const tokenRank = (s: string): number =>
|
|
1303
|
+
s.startsWith("*?") ? 3 : s.startsWith("*") ? 2 : s.startsWith(":") ? 1 : 0;
|
|
1304
|
+
const navigable = [
|
|
1295
1305
|
...pageRoutes,
|
|
1296
|
-
...boundaryRoutes,
|
|
1297
1306
|
...routeRoutes,
|
|
1298
1307
|
...dataRoutes,
|
|
1299
1308
|
...ogImageRoutes,
|
|
1300
|
-
]
|
|
1309
|
+
].sort((a, b) => {
|
|
1310
|
+
const as = a.path.split("/").filter(Boolean);
|
|
1311
|
+
const bs = b.path.split("/").filter(Boolean);
|
|
1312
|
+
const minLen = Math.min(as.length, bs.length);
|
|
1313
|
+
for (let i = 0; i < minLen; i++) {
|
|
1314
|
+
const ar = tokenRank(as[i]);
|
|
1315
|
+
const br = tokenRank(bs[i]);
|
|
1316
|
+
if (ar !== br) return ar - br; // more specific (lower rank) first
|
|
1317
|
+
if (as[i] !== bs[i]) return as[i] < bs[i] ? -1 : 1;
|
|
1318
|
+
}
|
|
1319
|
+
return as.length - bs.length;
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
return [...navigable, ...boundaryRoutes];
|
|
1301
1323
|
}
|
|
1302
1324
|
|
|
1303
1325
|
export function queriesToManifest(queries: QueryDefinition[]): ManifestQuery[] {
|
|
@@ -82,4 +82,26 @@ describe("discoverAppRoutes — opengraph-image convention", () => {
|
|
|
82
82
|
const routes = await discoverAppRoutes();
|
|
83
83
|
expect(routes.some((r) => r.kind === "og-image")).toBe(false);
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
test("special routes sort before a top-level dynamic segment", async () => {
|
|
87
|
+
// The Rust matcher is first-match-wins over the whole table, so the
|
|
88
|
+
// literal special routes must come before `/:orgSlug` — otherwise
|
|
89
|
+
// GET /opengraph-image (and /sitemap.xml, /robots.txt) binds
|
|
90
|
+
// orgSlug="opengraph-image" and renders the page's 404.
|
|
91
|
+
app({
|
|
92
|
+
"page.tsx": PAGE,
|
|
93
|
+
"[orgSlug]/page.tsx": PAGE,
|
|
94
|
+
"opengraph-image.tsx": OG,
|
|
95
|
+
"sitemap.ts": "export default function S(){ return []; }",
|
|
96
|
+
"robots.ts": "export default function R(){ return {}; }",
|
|
97
|
+
});
|
|
98
|
+
const routes = await discoverAppRoutes();
|
|
99
|
+
const idx = (p: string) => routes.findIndex((r) => r.path === p);
|
|
100
|
+
const dynamic = idx("/:orgSlug");
|
|
101
|
+
expect(dynamic).toBeGreaterThan(-1);
|
|
102
|
+
for (const literal of ["/opengraph-image", "/sitemap.xml", "/robots.txt"]) {
|
|
103
|
+
expect(idx(literal)).toBeGreaterThan(-1);
|
|
104
|
+
expect(idx(literal)).toBeLessThan(dynamic);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
85
107
|
});
|