@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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.383",
6
+ "version": "0.3.384",
7
7
  "type": "module",
8
8
  "main": "./src/index.ts",
9
9
  "types": "./dist/index.d.ts",
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
- return [
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
  });