@pylonsync/sdk 0.3.220 → 0.3.222
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 +150 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Route modes
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
-
export type RouteMode = "static" | "server" | "live";
|
|
5
|
+
export type RouteMode = "static" | "server" | "live" | "ssr";
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Field types
|
|
@@ -312,6 +312,18 @@ export interface RouteDefinition {
|
|
|
312
312
|
mode: RouteMode;
|
|
313
313
|
query?: string;
|
|
314
314
|
auth?: AuthMode;
|
|
315
|
+
/**
|
|
316
|
+
* Project-relative module path (e.g. `app/hello/page`) for SSR
|
|
317
|
+
* routes. Required when `mode === "ssr"`. Discovered automatically
|
|
318
|
+
* by `discoverAppRoutes()`; only specify manually for one-off
|
|
319
|
+
* SSR routes outside the `app/` tree.
|
|
320
|
+
*/
|
|
321
|
+
component?: string;
|
|
322
|
+
/**
|
|
323
|
+
* Layout module path chain (root→leaf). Each layout wraps the next
|
|
324
|
+
* as `children`. Only relevant for `mode === "ssr"`.
|
|
325
|
+
*/
|
|
326
|
+
layouts?: string[];
|
|
315
327
|
}
|
|
316
328
|
|
|
317
329
|
export function defineRoute(route: RouteDefinition): RouteDefinition {
|
|
@@ -483,6 +495,8 @@ export interface ManifestRoute {
|
|
|
483
495
|
mode: string;
|
|
484
496
|
query?: string;
|
|
485
497
|
auth?: string;
|
|
498
|
+
component?: string;
|
|
499
|
+
layouts?: string[];
|
|
486
500
|
}
|
|
487
501
|
|
|
488
502
|
export interface ManifestInputField {
|
|
@@ -621,10 +635,145 @@ export function routesToManifest(routes: RouteDefinition[]): ManifestRoute[] {
|
|
|
621
635
|
const result: ManifestRoute = { path: r.path, mode: r.mode };
|
|
622
636
|
if (r.query) result.query = r.query;
|
|
623
637
|
if (r.auth) result.auth = r.auth;
|
|
638
|
+
if (r.component) result.component = r.component;
|
|
639
|
+
if (r.layouts && r.layouts.length > 0) result.layouts = r.layouts;
|
|
624
640
|
return result;
|
|
625
641
|
});
|
|
626
642
|
}
|
|
627
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Walk the project's `app/` directory and discover file-based SSR
|
|
646
|
+
* routes. Returns `RouteDefinition[]` ready to slot into
|
|
647
|
+
* `buildManifest({ routes })`.
|
|
648
|
+
*
|
|
649
|
+
* Mapping (Next App Router-shaped):
|
|
650
|
+
* - `app/page.tsx` → `/`
|
|
651
|
+
* - `app/about/page.tsx` → `/about`
|
|
652
|
+
* - `app/blog/[slug]/page.tsx` → `/blog/:slug`
|
|
653
|
+
* - `app/layout.tsx` wraps every page below
|
|
654
|
+
* - `app/(marketing)/about/page.tsx` → `/about` (group strip)
|
|
655
|
+
*
|
|
656
|
+
* Sorts deterministically — literal segments before parameterized
|
|
657
|
+
* ones at each depth — so the Rust matcher's first-match-wins
|
|
658
|
+
* lookup picks the right route.
|
|
659
|
+
*
|
|
660
|
+
* Phase 1 only: no `loading.tsx` / `error.tsx` / `not-found.tsx`
|
|
661
|
+
* support yet.
|
|
662
|
+
*/
|
|
663
|
+
export async function discoverAppRoutes(opts?: {
|
|
664
|
+
appDir?: string;
|
|
665
|
+
}): Promise<RouteDefinition[]> {
|
|
666
|
+
// Pull node fs/path lazily. @pylonsync/sdk has no @types/node dep
|
|
667
|
+
// (kept light so client bundles stay tiny), so we type as `any`
|
|
668
|
+
// and validate at runtime — users call this from app.ts under
|
|
669
|
+
// Bun/Node, where the requires resolve.
|
|
670
|
+
//
|
|
671
|
+
// ESM Bun/Node doesn't expose `require` on globalThis. Build one
|
|
672
|
+
// via createRequire(import.meta.url) — the standard ESM →
|
|
673
|
+
// node-builtins escape hatch. The optional-chain on `require`
|
|
674
|
+
// covers the browser case (returns undefined, we fall through to
|
|
675
|
+
// []).
|
|
676
|
+
let fs: any;
|
|
677
|
+
let path: any;
|
|
678
|
+
try {
|
|
679
|
+
const nodeReq = (globalThis as any).require ??
|
|
680
|
+
(await import("node:module")).createRequire(import.meta.url);
|
|
681
|
+
fs = nodeReq("node:fs");
|
|
682
|
+
path = nodeReq("node:path");
|
|
683
|
+
} catch {
|
|
684
|
+
// Browser bundle hit — there's no `app/` to discover. Returning
|
|
685
|
+
// [] keeps the function safe to import from client code too.
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
if (!fs || !path) return [];
|
|
689
|
+
|
|
690
|
+
const cwd = (globalThis as any).process?.cwd?.() ?? ".";
|
|
691
|
+
const appDir =
|
|
692
|
+
opts?.appDir && path.isAbsolute(opts.appDir)
|
|
693
|
+
? opts.appDir
|
|
694
|
+
: path.join(cwd, opts?.appDir ?? "app");
|
|
695
|
+
if (!fs.existsSync(appDir) || !fs.statSync(appDir).isDirectory()) {
|
|
696
|
+
return [];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
type PageHit = {
|
|
700
|
+
segments: string[];
|
|
701
|
+
component: string;
|
|
702
|
+
layouts: string[];
|
|
703
|
+
};
|
|
704
|
+
const pages: PageHit[] = [];
|
|
705
|
+
|
|
706
|
+
function walk(dir: string, segments: string[], layouts: string[]): void {
|
|
707
|
+
let entries: Array<{ name: string; isDirectory(): boolean }>;
|
|
708
|
+
try {
|
|
709
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
710
|
+
} catch {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const layoutHere = [
|
|
714
|
+
"layout.tsx",
|
|
715
|
+
"layout.ts",
|
|
716
|
+
"layout.jsx",
|
|
717
|
+
"layout.js",
|
|
718
|
+
]
|
|
719
|
+
.map((n) => path.join(dir, n))
|
|
720
|
+
.find((p) => fs.existsSync(p));
|
|
721
|
+
const nextLayouts = layoutHere
|
|
722
|
+
? [
|
|
723
|
+
...layouts,
|
|
724
|
+
path.relative(cwd, layoutHere).replace(/\.(tsx?|jsx?)$/, ""),
|
|
725
|
+
]
|
|
726
|
+
: layouts;
|
|
727
|
+
const pageHere = ["page.tsx", "page.ts", "page.jsx", "page.js"]
|
|
728
|
+
.map((n) => path.join(dir, n))
|
|
729
|
+
.find((p) => fs.existsSync(p));
|
|
730
|
+
if (pageHere) {
|
|
731
|
+
pages.push({
|
|
732
|
+
segments: [...segments],
|
|
733
|
+
component: path
|
|
734
|
+
.relative(cwd, pageHere)
|
|
735
|
+
.replace(/\.(tsx?|jsx?)$/, ""),
|
|
736
|
+
layouts: nextLayouts,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
for (const e of entries) {
|
|
740
|
+
if (!e.isDirectory()) continue;
|
|
741
|
+
if (e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
742
|
+
const sub = path.join(dir, e.name);
|
|
743
|
+
const isGroup = e.name.startsWith("(") && e.name.endsWith(")");
|
|
744
|
+
const newSegments = isGroup ? segments : [...segments, e.name];
|
|
745
|
+
walk(sub, newSegments, nextLayouts);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
walk(appDir, [], []);
|
|
749
|
+
|
|
750
|
+
const isParam = (s: string): boolean =>
|
|
751
|
+
s.startsWith("[") && s.endsWith("]");
|
|
752
|
+
pages.sort((a, b) => {
|
|
753
|
+
const minLen = Math.min(a.segments.length, b.segments.length);
|
|
754
|
+
for (let i = 0; i < minLen; i++) {
|
|
755
|
+
const ap = isParam(a.segments[i]);
|
|
756
|
+
const bp = isParam(b.segments[i]);
|
|
757
|
+
if (ap !== bp) return ap ? 1 : -1;
|
|
758
|
+
if (a.segments[i] !== b.segments[i]) {
|
|
759
|
+
return a.segments[i] < b.segments[i] ? -1 : 1;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return a.segments.length - b.segments.length;
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
return pages.map((p) => ({
|
|
766
|
+
path:
|
|
767
|
+
"/" +
|
|
768
|
+
p.segments
|
|
769
|
+
.map((s) => (isParam(s) ? `:${s.slice(1, -1)}` : s))
|
|
770
|
+
.join("/"),
|
|
771
|
+
mode: "ssr" as const,
|
|
772
|
+
component: p.component,
|
|
773
|
+
layouts: p.layouts,
|
|
774
|
+
}));
|
|
775
|
+
}
|
|
776
|
+
|
|
628
777
|
export function queriesToManifest(queries: QueryDefinition[]): ManifestQuery[] {
|
|
629
778
|
return queries.map((q) => {
|
|
630
779
|
const result: ManifestQuery = { name: q.name };
|