@ohhwells/bridge 0.1.54-next.151 → 0.1.54
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/dist/index.cjs +295 -1474
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -12
- package/dist/index.d.ts +2 -12
- package/dist/index.js +289 -1473
- package/dist/index.js.map +1 -1
- package/dist/styles.css +0 -87
- package/package.json +1 -6
- package/dist/pages.cjs +0 -141
- package/dist/pages.cjs.map +0 -1
- package/dist/pages.d.cts +0 -45
- package/dist/pages.d.ts +0 -45
- package/dist/pages.js +0 -107
- package/dist/pages.js.map +0 -1
package/dist/styles.css
CHANGED
|
@@ -104,9 +104,6 @@
|
|
|
104
104
|
.inset-0 {
|
|
105
105
|
inset: calc(var(--spacing) * 0);
|
|
106
106
|
}
|
|
107
|
-
.inset-y-0 {
|
|
108
|
-
inset-block: calc(var(--spacing) * 0);
|
|
109
|
-
}
|
|
110
107
|
.top-0 {
|
|
111
108
|
top: calc(var(--spacing) * 0);
|
|
112
109
|
}
|
|
@@ -134,9 +131,6 @@
|
|
|
134
131
|
.right-1\.5 {
|
|
135
132
|
right: calc(var(--spacing) * 1.5);
|
|
136
133
|
}
|
|
137
|
-
.right-2\.5 {
|
|
138
|
-
right: calc(var(--spacing) * 2.5);
|
|
139
|
-
}
|
|
140
134
|
.right-3 {
|
|
141
135
|
right: calc(var(--spacing) * 3);
|
|
142
136
|
}
|
|
@@ -280,9 +274,6 @@
|
|
|
280
274
|
.\!h-full {
|
|
281
275
|
height: 100% !important;
|
|
282
276
|
}
|
|
283
|
-
.h-2 {
|
|
284
|
-
height: calc(var(--spacing) * 2);
|
|
285
|
-
}
|
|
286
277
|
.h-6 {
|
|
287
278
|
height: calc(var(--spacing) * 6);
|
|
288
279
|
}
|
|
@@ -364,9 +355,6 @@
|
|
|
364
355
|
.w-56 {
|
|
365
356
|
width: calc(var(--spacing) * 56);
|
|
366
357
|
}
|
|
367
|
-
.w-64 {
|
|
368
|
-
width: calc(var(--spacing) * 64);
|
|
369
|
-
}
|
|
370
358
|
.w-\[3px\] {
|
|
371
359
|
width: 3px;
|
|
372
360
|
}
|
|
@@ -469,11 +457,6 @@
|
|
|
469
457
|
.resize {
|
|
470
458
|
resize: both;
|
|
471
459
|
}
|
|
472
|
-
.appearance-none {
|
|
473
|
-
-webkit-appearance: none;
|
|
474
|
-
-moz-appearance: none;
|
|
475
|
-
appearance: none;
|
|
476
|
-
}
|
|
477
460
|
.flex-col {
|
|
478
461
|
flex-direction: column;
|
|
479
462
|
}
|
|
@@ -697,9 +680,6 @@
|
|
|
697
680
|
.bg-primary {
|
|
698
681
|
background-color: var(--color-primary);
|
|
699
682
|
}
|
|
700
|
-
.bg-primary-50 {
|
|
701
|
-
background-color: var(--color-primary-50);
|
|
702
|
-
}
|
|
703
683
|
.bg-secondary {
|
|
704
684
|
background-color: var(--color-secondary);
|
|
705
685
|
}
|
|
@@ -728,9 +708,6 @@
|
|
|
728
708
|
.p-3 {
|
|
729
709
|
padding: calc(var(--spacing) * 3);
|
|
730
710
|
}
|
|
731
|
-
.p-5 {
|
|
732
|
-
padding: calc(var(--spacing) * 5);
|
|
733
|
-
}
|
|
734
711
|
.p-6 {
|
|
735
712
|
padding: calc(var(--spacing) * 6);
|
|
736
713
|
}
|
|
@@ -785,9 +762,6 @@
|
|
|
785
762
|
.py-4 {
|
|
786
763
|
padding-block: calc(var(--spacing) * 4);
|
|
787
764
|
}
|
|
788
|
-
.py-5 {
|
|
789
|
-
padding-block: calc(var(--spacing) * 5);
|
|
790
|
-
}
|
|
791
765
|
.py-8 {
|
|
792
766
|
padding-block: calc(var(--spacing) * 8);
|
|
793
767
|
}
|
|
@@ -812,9 +786,6 @@
|
|
|
812
786
|
.pr-3 {
|
|
813
787
|
padding-right: calc(var(--spacing) * 3);
|
|
814
788
|
}
|
|
815
|
-
.pr-11 {
|
|
816
|
-
padding-right: calc(var(--spacing) * 11);
|
|
817
|
-
}
|
|
818
789
|
.pr-12 {
|
|
819
790
|
padding-right: calc(var(--spacing) * 12);
|
|
820
791
|
}
|
|
@@ -830,9 +801,6 @@
|
|
|
830
801
|
.pl-4 {
|
|
831
802
|
padding-left: calc(var(--spacing) * 4);
|
|
832
803
|
}
|
|
833
|
-
.pl-5 {
|
|
834
|
-
padding-left: calc(var(--spacing) * 5);
|
|
835
|
-
}
|
|
836
804
|
.pl-8 {
|
|
837
805
|
padding-left: calc(var(--spacing) * 8);
|
|
838
806
|
}
|
|
@@ -892,10 +860,6 @@
|
|
|
892
860
|
--tw-leading: calc(var(--spacing) * 6);
|
|
893
861
|
line-height: calc(var(--spacing) * 6);
|
|
894
862
|
}
|
|
895
|
-
.leading-7 {
|
|
896
|
-
--tw-leading: calc(var(--spacing) * 7);
|
|
897
|
-
line-height: calc(var(--spacing) * 7);
|
|
898
|
-
}
|
|
899
863
|
.leading-none {
|
|
900
864
|
--tw-leading: 1;
|
|
901
865
|
line-height: 1;
|
|
@@ -990,9 +954,6 @@
|
|
|
990
954
|
.text-white {
|
|
991
955
|
color: var(--color-white);
|
|
992
956
|
}
|
|
993
|
-
.uppercase {
|
|
994
|
-
text-transform: uppercase;
|
|
995
|
-
}
|
|
996
957
|
.italic {
|
|
997
958
|
font-style: italic;
|
|
998
959
|
}
|
|
@@ -1131,9 +1092,6 @@
|
|
|
1131
1092
|
-moz-user-select: none;
|
|
1132
1093
|
user-select: none;
|
|
1133
1094
|
}
|
|
1134
|
-
.\[ow\:scheduling\] {
|
|
1135
|
-
ow: scheduling;
|
|
1136
|
-
}
|
|
1137
1095
|
.placeholder\:text-muted-foreground::-moz-placeholder {
|
|
1138
1096
|
color: var(--color-muted-foreground);
|
|
1139
1097
|
}
|
|
@@ -1337,9 +1295,6 @@
|
|
|
1337
1295
|
--tw-ring-color: color-mix(in oklab, var(--color-ring) 50%, transparent);
|
|
1338
1296
|
}
|
|
1339
1297
|
}
|
|
1340
|
-
.active\:cursor-grabbing:active {
|
|
1341
|
-
cursor: grabbing;
|
|
1342
|
-
}
|
|
1343
1298
|
@media (hover: hover) {
|
|
1344
1299
|
.enabled\:hover\:border:enabled:hover {
|
|
1345
1300
|
border-style: var(--tw-border-style);
|
|
@@ -1528,44 +1483,6 @@
|
|
|
1528
1483
|
.\[\&_svg\:not\(\[class\*\=absolute\]\)\]\:relative svg:not([class*=absolute]) {
|
|
1529
1484
|
position: relative;
|
|
1530
1485
|
}
|
|
1531
|
-
.\[\&\:\:-moz-range-thumb\]\:size-5::-moz-range-thumb {
|
|
1532
|
-
width: calc(var(--spacing) * 5);
|
|
1533
|
-
height: calc(var(--spacing) * 5);
|
|
1534
|
-
}
|
|
1535
|
-
.\[\&\:\:-moz-range-thumb\]\:rounded-full::-moz-range-thumb {
|
|
1536
|
-
border-radius: calc(infinity * 1px);
|
|
1537
|
-
}
|
|
1538
|
-
.\[\&\:\:-moz-range-thumb\]\:border-2::-moz-range-thumb {
|
|
1539
|
-
border-style: var(--tw-border-style);
|
|
1540
|
-
border-width: 2px;
|
|
1541
|
-
}
|
|
1542
|
-
.\[\&\:\:-moz-range-thumb\]\:border-primary::-moz-range-thumb {
|
|
1543
|
-
border-color: var(--color-primary);
|
|
1544
|
-
}
|
|
1545
|
-
.\[\&\:\:-moz-range-thumb\]\:bg-background::-moz-range-thumb {
|
|
1546
|
-
background-color: var(--color-background);
|
|
1547
|
-
}
|
|
1548
|
-
.\[\&\:\:-webkit-slider-thumb\]\:size-5::-webkit-slider-thumb {
|
|
1549
|
-
width: calc(var(--spacing) * 5);
|
|
1550
|
-
height: calc(var(--spacing) * 5);
|
|
1551
|
-
}
|
|
1552
|
-
.\[\&\:\:-webkit-slider-thumb\]\:appearance-none::-webkit-slider-thumb {
|
|
1553
|
-
-webkit-appearance: none;
|
|
1554
|
-
appearance: none;
|
|
1555
|
-
}
|
|
1556
|
-
.\[\&\:\:-webkit-slider-thumb\]\:rounded-full::-webkit-slider-thumb {
|
|
1557
|
-
border-radius: calc(infinity * 1px);
|
|
1558
|
-
}
|
|
1559
|
-
.\[\&\:\:-webkit-slider-thumb\]\:border-2::-webkit-slider-thumb {
|
|
1560
|
-
border-style: var(--tw-border-style);
|
|
1561
|
-
border-width: 2px;
|
|
1562
|
-
}
|
|
1563
|
-
.\[\&\:\:-webkit-slider-thumb\]\:border-primary::-webkit-slider-thumb {
|
|
1564
|
-
border-color: var(--color-primary);
|
|
1565
|
-
}
|
|
1566
|
-
.\[\&\:\:-webkit-slider-thumb\]\:bg-background::-webkit-slider-thumb {
|
|
1567
|
-
background-color: var(--color-background);
|
|
1568
|
-
}
|
|
1569
1486
|
.\[\&\>svg\:first-child\]\:absolute > svg:first-child {
|
|
1570
1487
|
position: absolute;
|
|
1571
1488
|
}
|
|
@@ -1815,10 +1732,6 @@ html[data-ohw-section-picking] [data-ohw-state-toggle] {
|
|
|
1815
1732
|
[data-ohw-bridge-root] {
|
|
1816
1733
|
font-family: var(--font-sans);
|
|
1817
1734
|
}
|
|
1818
|
-
[data-ohw-editable="icon"],
|
|
1819
|
-
[data-ohw-editable="icon"] * {
|
|
1820
|
-
cursor: pointer;
|
|
1821
|
-
}
|
|
1822
1735
|
[data-ohw-bridge-root] button:not([data-slot="button"]) {
|
|
1823
1736
|
border: none;
|
|
1824
1737
|
background: none;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ohhwells/bridge",
|
|
3
|
-
"version": "0.1.54
|
|
3
|
+
"version": "0.1.54",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -12,11 +12,6 @@
|
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
},
|
|
15
|
-
"./pages": {
|
|
16
|
-
"types": "./dist/pages.d.ts",
|
|
17
|
-
"import": "./dist/pages.js",
|
|
18
|
-
"require": "./dist/pages.cjs"
|
|
19
|
-
},
|
|
20
15
|
"./styles": "./dist/styles.css"
|
|
21
16
|
},
|
|
22
17
|
"files": [
|
package/dist/pages.cjs
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/pages.ts
|
|
21
|
-
var pages_exports = {};
|
|
22
|
-
__export(pages_exports, {
|
|
23
|
-
DELETED_PAGE_PREFIX: () => DELETED_PAGE_PREFIX,
|
|
24
|
-
fetchSitePages: () => fetchSitePages,
|
|
25
|
-
renderCatchAllPage: () => renderCatchAllPage,
|
|
26
|
-
resolvePageRewrite: () => resolvePageRewrite,
|
|
27
|
-
resolveSitePage: () => resolveSitePage,
|
|
28
|
-
resolveSubdomain: () => resolveSubdomain,
|
|
29
|
-
sitePagesMiddleware: () => sitePagesMiddleware,
|
|
30
|
-
sitePagesMiddlewareConfig: () => sitePagesMiddlewareConfig
|
|
31
|
-
});
|
|
32
|
-
module.exports = __toCommonJS(pages_exports);
|
|
33
|
-
|
|
34
|
-
// src/lib/site-pages.ts
|
|
35
|
-
var DELETED_PAGE_PREFIX = "/__ohw-deleted";
|
|
36
|
-
function resolveSubdomain(hostnameOverride) {
|
|
37
|
-
if (hostnameOverride) {
|
|
38
|
-
const parts = hostnameOverride.split(".");
|
|
39
|
-
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
40
|
-
}
|
|
41
|
-
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
42
|
-
if (!siteUrl) return "";
|
|
43
|
-
try {
|
|
44
|
-
const parts = new URL(siteUrl).hostname.split(".");
|
|
45
|
-
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
46
|
-
} catch {
|
|
47
|
-
}
|
|
48
|
-
return "";
|
|
49
|
-
}
|
|
50
|
-
async function fetchSitePages(subdomainOverride) {
|
|
51
|
-
const subdomain = subdomainOverride || resolveSubdomain();
|
|
52
|
-
const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL;
|
|
53
|
-
if (!subdomain || !apiUrl) return [];
|
|
54
|
-
try {
|
|
55
|
-
const res = await fetch(`${apiUrl}/api/public/sites/${subdomain}/pages`, {
|
|
56
|
-
cache: "no-store"
|
|
57
|
-
});
|
|
58
|
-
if (!res.ok) return [];
|
|
59
|
-
const { pages } = await res.json();
|
|
60
|
-
return pages;
|
|
61
|
-
} catch {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function resolvePageRewrite(pathname, pages) {
|
|
66
|
-
const current = pages.find((page) => page.path === pathname);
|
|
67
|
-
if (current) {
|
|
68
|
-
if (current.is_deleted) return { path: `${DELETED_PAGE_PREFIX}${pathname}` };
|
|
69
|
-
if (current.is_physical && current.original_path && current.original_path !== current.path) {
|
|
70
|
-
return { path: current.original_path };
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
const oldUrlOfRenamed = pages.find(
|
|
75
|
-
(page) => page.is_physical && page.original_path === pathname && page.path !== pathname
|
|
76
|
-
);
|
|
77
|
-
if (oldUrlOfRenamed) return { path: `${DELETED_PAGE_PREFIX}${pathname}` };
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
async function resolveSitePage(segments, subdomainOverride) {
|
|
81
|
-
if (`/${segments[0]}` === DELETED_PAGE_PREFIX) return { kind: "not-found" };
|
|
82
|
-
const path = `/${segments.join("/")}`;
|
|
83
|
-
const pages = await fetchSitePages(subdomainOverride);
|
|
84
|
-
const page = pages.find((entry) => entry.path === path && !entry.is_deleted);
|
|
85
|
-
if (!page) return { kind: "not-found" };
|
|
86
|
-
if (page.duplicated_from) {
|
|
87
|
-
const sourceSegment = page.duplicated_from === "/" ? "" : page.duplicated_from.replace(/^\//, "");
|
|
88
|
-
return { kind: "duplicate", sourceSegment, page };
|
|
89
|
-
}
|
|
90
|
-
return { kind: "blank", page };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// src/lib/site-pages-middleware.ts
|
|
94
|
-
var import_server = require("next/server");
|
|
95
|
-
async function sitePagesMiddleware(request) {
|
|
96
|
-
const { pathname } = request.nextUrl;
|
|
97
|
-
if (pathname.startsWith("/_next") || pathname.startsWith("/api") || /\.[a-zA-Z0-9]+$/.test(pathname)) {
|
|
98
|
-
return import_server.NextResponse.next();
|
|
99
|
-
}
|
|
100
|
-
const subdomain = request.nextUrl.searchParams.get("subdomain") || resolveSubdomain(request.nextUrl.hostname);
|
|
101
|
-
const pages = await fetchSitePages(subdomain || void 0);
|
|
102
|
-
if (pages.length === 0) return import_server.NextResponse.next();
|
|
103
|
-
const decision = resolvePageRewrite(pathname, pages);
|
|
104
|
-
if (!decision) return import_server.NextResponse.next();
|
|
105
|
-
return import_server.NextResponse.rewrite(new URL(decision.path, request.url));
|
|
106
|
-
}
|
|
107
|
-
var sitePagesMiddlewareConfig = {
|
|
108
|
-
matcher: ["/((?!_next|api).*)"]
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// src/lib/site-pages-render.ts
|
|
112
|
-
var import_headers = require("next/headers");
|
|
113
|
-
var import_navigation = require("next/navigation");
|
|
114
|
-
var import_react = require("react");
|
|
115
|
-
async function renderCatchAllPage(segments, subdomainOverride, handlers) {
|
|
116
|
-
const host = (await (0, import_headers.headers)()).get("host") ?? void 0;
|
|
117
|
-
const subdomain = subdomainOverride || resolveSubdomain(host) || void 0;
|
|
118
|
-
const result = await resolveSitePage(segments, subdomain);
|
|
119
|
-
if (result.kind === "not-found") (0, import_navigation.notFound)();
|
|
120
|
-
if (result.kind === "duplicate") {
|
|
121
|
-
try {
|
|
122
|
-
const sourceModule = await handlers.renderDuplicate(result.sourceSegment);
|
|
123
|
-
return (0, import_react.createElement)(sourceModule.default);
|
|
124
|
-
} catch {
|
|
125
|
-
(0, import_navigation.notFound)();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return handlers.renderBlank(result.page);
|
|
129
|
-
}
|
|
130
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
131
|
-
0 && (module.exports = {
|
|
132
|
-
DELETED_PAGE_PREFIX,
|
|
133
|
-
fetchSitePages,
|
|
134
|
-
renderCatchAllPage,
|
|
135
|
-
resolvePageRewrite,
|
|
136
|
-
resolveSitePage,
|
|
137
|
-
resolveSubdomain,
|
|
138
|
-
sitePagesMiddleware,
|
|
139
|
-
sitePagesMiddlewareConfig
|
|
140
|
-
});
|
|
141
|
-
//# sourceMappingURL=pages.cjs.map
|
package/dist/pages.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pages.ts","../src/lib/site-pages.ts","../src/lib/site-pages-middleware.ts","../src/lib/site-pages-render.ts"],"sourcesContent":["// Server-safe entry point (no 'use client' banner) — for middleware/Edge runtime and\n// Server Components. Keep this file free of React component exports; UI goes in the\n// default `@ohhwells/bridge` entry (src/index.ts).\nexport {\n DELETED_PAGE_PREFIX,\n resolveSubdomain,\n fetchSitePages,\n resolvePageRewrite,\n resolveSitePage,\n} from './lib/site-pages'\nexport type { SitePage, PageRewriteDecision, SitePageResolution } from './lib/site-pages'\nexport { sitePagesMiddleware, sitePagesMiddlewareConfig } from './lib/site-pages-middleware'\nexport { renderCatchAllPage } from './lib/site-pages-render'\nexport type { CatchAllPageHandlers } from './lib/site-pages-render'\n","// Shared page-CRUD infra (OHH-670): fetching a site's page list and deciding how the\n// catch-all route / middleware should handle deleted, renamed, and duplicated pages.\n// Server-safe (no React/browser APIs) so it can run in middleware (Edge runtime) and\n// Server Components — kept out of the main client-bundled entry point, see `../pages.ts`.\n\n// Prefix used to route deleted-physical-page requests into the catch-all `[...page]`\n// route, which has no file of its own at that segment and calls notFound() for it.\nexport const DELETED_PAGE_PREFIX = '/__ohw-deleted'\n\nexport type SitePage = {\n id: string\n path: string\n title: string\n is_physical: boolean\n original_path: string | null\n duplicated_from: string | null\n is_deleted: boolean\n}\n\n// Mirrors OhhwellsBridge's client-side resolveSubdomain: prefer the real request hostname\n// (window.location isn't available on the server, so callers pass the Host header/nextUrl\n// hostname instead) and only fall back to the NEXT_PUBLIC_SITE_URL env var when no hostname\n// is known — the only case that's true in local dev, where the host is just localhost.\nexport function resolveSubdomain(hostnameOverride?: string): string {\n if (hostnameOverride) {\n const parts = hostnameOverride.split('.')\n if (parts.length >= 3 && parts[0] !== 'www') return parts[0]\n }\n\n const siteUrl = process.env.NEXT_PUBLIC_SITE_URL\n if (!siteUrl) return ''\n try {\n const parts = new URL(siteUrl).hostname.split('.')\n if (parts.length >= 3 && parts[0] !== 'www') return parts[0]\n } catch {\n // ignore malformed env value\n }\n return ''\n}\n\n// `subdomainOverride` is the `?subdomain=` query param the canvas editor's iframe appends\n// (see `editUrl` in fo-class-experience). It's required locally: NEXT_PUBLIC_SITE_URL is\n// `http://localhost:3000` in dev, which resolveSubdomain() can't parse a subdomain from.\nexport async function fetchSitePages(subdomainOverride?: string): Promise<SitePage[]> {\n const subdomain = subdomainOverride || resolveSubdomain()\n const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL\n if (!subdomain || !apiUrl) return []\n\n try {\n // no-store, not a timed revalidate: middleware uses this list to decide whether a\n // path is deleted/renamed, and `next start`'s persistent Data Cache would otherwise\n // keep routing a just-deleted page's URL to its live content for longer than expected.\n const res = await fetch(`${apiUrl}/api/public/sites/${subdomain}/pages`, {\n cache: 'no-store',\n })\n if (!res.ok) return []\n const { pages } = (await res.json()) as { pages: SitePage[] }\n return pages\n } catch {\n return []\n }\n}\n\nexport type PageRewriteDecision = { path: string } | null\n\n// Middleware decision logic: given the current pathname and the site's page list, decide\n// whether to rewrite to the deleted-page placeholder or to a renamed physical page's real\n// file location. Callers still own the `_next`/`api`/static-file early-return and the actual\n// `NextResponse.rewrite` call, since those are Next.js-specific.\nexport function resolvePageRewrite(pathname: string, pages: SitePage[]): PageRewriteDecision {\n const current = pages.find((page) => page.path === pathname)\n\n if (current) {\n if (current.is_deleted) return { path: `${DELETED_PAGE_PREFIX}${pathname}` }\n // Renamed physical page: the file still lives at original_path, so rewrite there\n // internally while the browser keeps showing the new canonical path.\n if (current.is_physical && current.original_path && current.original_path !== current.path) {\n return { path: current.original_path }\n }\n return null\n }\n\n // Old URL of a renamed physical page — no redirect, it just no longer resolves to\n // anything (whether or not the page has since also been deleted).\n const oldUrlOfRenamed = pages.find(\n (page) => page.is_physical && page.original_path === pathname && page.path !== pathname,\n )\n if (oldUrlOfRenamed) return { path: `${DELETED_PAGE_PREFIX}${pathname}` }\n\n return null\n}\n\nexport type SitePageResolution =\n | { kind: 'not-found' }\n | { kind: 'duplicate'; sourceSegment: string; page: SitePage }\n | { kind: 'blank'; page: SitePage }\n\n// Catch-all route lookup: given the route's path segments, resolve which page (if any) this\n// request maps to. Callers still own the dynamic `import()` for `kind: 'duplicate'` — that\n// needs a literal relative path per app for webpack's static context analysis to work, so it\n// can't live in this shared package (see the comment at the call site).\nexport async function resolveSitePage(segments: string[], subdomainOverride?: string): Promise<SitePageResolution> {\n if (`/${segments[0]}` === DELETED_PAGE_PREFIX) return { kind: 'not-found' }\n\n const path = `/${segments.join('/')}`\n const pages = await fetchSitePages(subdomainOverride)\n const page = pages.find((entry) => entry.path === path && !entry.is_deleted)\n if (!page) return { kind: 'not-found' }\n\n if (page.duplicated_from) {\n const sourceSegment = page.duplicated_from === '/' ? '' : page.duplicated_from.replace(/^\\//, '')\n return { kind: 'duplicate', sourceSegment, page }\n }\n\n return { kind: 'blank', page }\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport { fetchSitePages, resolvePageRewrite, resolveSubdomain } from './site-pages'\n\n// Drop-in Next.js middleware for the OHH-670 page-CRUD system: rewrites deleted/renamed\n// pages to the right place before the catch-all route (see `resolveSitePage`) ever runs.\n// Apps that need extra middleware logic of their own can still compose `fetchSitePages`/\n// `resolvePageRewrite` directly instead of using this wrapper.\nexport async function sitePagesMiddleware(request: NextRequest): Promise<NextResponse> {\n const { pathname } = request.nextUrl\n\n if (pathname.startsWith('/_next') || pathname.startsWith('/api') || /\\.[a-zA-Z0-9]+$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // `?subdomain=` is the canvas editor iframe's override (its own origin isn't the site's\n // real domain). Every other request — every real visitor — hits the site's actual\n // `*.ohhwells.site`/custom domain, so the request's own hostname is the source of truth\n // there; without this fallback the middleware silently no-ops on every production request.\n const subdomain =\n request.nextUrl.searchParams.get('subdomain') || resolveSubdomain(request.nextUrl.hostname)\n const pages = await fetchSitePages(subdomain || undefined)\n if (pages.length === 0) return NextResponse.next()\n\n const decision = resolvePageRewrite(pathname, pages)\n if (!decision) return NextResponse.next()\n return NextResponse.rewrite(new URL(decision.path, request.url))\n}\n\n// Mirrors the `_next`/`api` early-return above so apps that re-export this directly as\n// their middleware `config` don't invoke the function needlessly on asset requests.\nexport const sitePagesMiddlewareConfig = {\n matcher: ['/((?!_next|api).*)'],\n}\n","import { headers } from 'next/headers'\nimport { notFound } from 'next/navigation'\nimport { createElement, type ComponentType, type ReactElement } from 'react'\nimport { resolveSitePage, resolveSubdomain, type SitePage } from './site-pages'\n\nexport interface CatchAllPageHandlers {\n // Called for a page that's a duplicate of another. Must contain the actual `import(...)`\n // call itself (relative, not the `@` alias) — webpack can only build a static context\n // module (bundling every matching src/app/*/page.tsx) when the prefix is a real relative\n // path, so this one expression has to stay in the calling app's own file, resolved against\n // its own src/app tree, not this shared package's. Everything else about turning the\n // resolved module into a rendered element lives here instead.\n renderDuplicate: (sourceSegment: string) => Promise<{ default: ComponentType }>\n // Called for a page with no section content of its own yet.\n renderBlank: (page: SitePage) => ReactElement\n}\n\n// Full catch-all route resolution for the OHH-670 page-CRUD system: looks the page up,\n// calls `notFound()` for anything unresolved, and delegates to the two things every\n// consuming app still owns (the duplicate-source import and its blank-page chrome).\nexport async function renderCatchAllPage(\n segments: string[],\n subdomainOverride: string | undefined,\n handlers: CatchAllPageHandlers,\n): Promise<ReactElement> {\n // `subdomainOverride` is the canvas editor iframe's `?subdomain=` query param — its own\n // origin isn't the site's real domain. Every real visitor hits the site's actual\n // `*.ohhwells.site`/custom domain, so fall back to the request's own Host header there,\n // mirroring `sitePagesMiddleware`'s hostname fallback.\n const host = (await headers()).get('host') ?? undefined\n const subdomain = subdomainOverride || resolveSubdomain(host) || undefined\n const result = await resolveSitePage(segments, subdomain)\n\n if (result.kind === 'not-found') notFound()\n\n if (result.kind === 'duplicate') {\n try {\n const sourceModule = await handlers.renderDuplicate(result.sourceSegment)\n return createElement(sourceModule.default)\n } catch {\n notFound()\n }\n }\n\n return handlers.renderBlank(result.page)\n}\n"],"mappings":"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","names":[]}
|
package/dist/pages.d.cts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { ComponentType, ReactElement } from 'react';
|
|
3
|
-
|
|
4
|
-
declare const DELETED_PAGE_PREFIX = "/__ohw-deleted";
|
|
5
|
-
type SitePage = {
|
|
6
|
-
id: string;
|
|
7
|
-
path: string;
|
|
8
|
-
title: string;
|
|
9
|
-
is_physical: boolean;
|
|
10
|
-
original_path: string | null;
|
|
11
|
-
duplicated_from: string | null;
|
|
12
|
-
is_deleted: boolean;
|
|
13
|
-
};
|
|
14
|
-
declare function resolveSubdomain(hostnameOverride?: string): string;
|
|
15
|
-
declare function fetchSitePages(subdomainOverride?: string): Promise<SitePage[]>;
|
|
16
|
-
type PageRewriteDecision = {
|
|
17
|
-
path: string;
|
|
18
|
-
} | null;
|
|
19
|
-
declare function resolvePageRewrite(pathname: string, pages: SitePage[]): PageRewriteDecision;
|
|
20
|
-
type SitePageResolution = {
|
|
21
|
-
kind: 'not-found';
|
|
22
|
-
} | {
|
|
23
|
-
kind: 'duplicate';
|
|
24
|
-
sourceSegment: string;
|
|
25
|
-
page: SitePage;
|
|
26
|
-
} | {
|
|
27
|
-
kind: 'blank';
|
|
28
|
-
page: SitePage;
|
|
29
|
-
};
|
|
30
|
-
declare function resolveSitePage(segments: string[], subdomainOverride?: string): Promise<SitePageResolution>;
|
|
31
|
-
|
|
32
|
-
declare function sitePagesMiddleware(request: NextRequest): Promise<NextResponse>;
|
|
33
|
-
declare const sitePagesMiddlewareConfig: {
|
|
34
|
-
matcher: string[];
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
interface CatchAllPageHandlers {
|
|
38
|
-
renderDuplicate: (sourceSegment: string) => Promise<{
|
|
39
|
-
default: ComponentType;
|
|
40
|
-
}>;
|
|
41
|
-
renderBlank: (page: SitePage) => ReactElement;
|
|
42
|
-
}
|
|
43
|
-
declare function renderCatchAllPage(segments: string[], subdomainOverride: string | undefined, handlers: CatchAllPageHandlers): Promise<ReactElement>;
|
|
44
|
-
|
|
45
|
-
export { type CatchAllPageHandlers, DELETED_PAGE_PREFIX, type PageRewriteDecision, type SitePage, type SitePageResolution, fetchSitePages, renderCatchAllPage, resolvePageRewrite, resolveSitePage, resolveSubdomain, sitePagesMiddleware, sitePagesMiddlewareConfig };
|
package/dist/pages.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
-
import { ComponentType, ReactElement } from 'react';
|
|
3
|
-
|
|
4
|
-
declare const DELETED_PAGE_PREFIX = "/__ohw-deleted";
|
|
5
|
-
type SitePage = {
|
|
6
|
-
id: string;
|
|
7
|
-
path: string;
|
|
8
|
-
title: string;
|
|
9
|
-
is_physical: boolean;
|
|
10
|
-
original_path: string | null;
|
|
11
|
-
duplicated_from: string | null;
|
|
12
|
-
is_deleted: boolean;
|
|
13
|
-
};
|
|
14
|
-
declare function resolveSubdomain(hostnameOverride?: string): string;
|
|
15
|
-
declare function fetchSitePages(subdomainOverride?: string): Promise<SitePage[]>;
|
|
16
|
-
type PageRewriteDecision = {
|
|
17
|
-
path: string;
|
|
18
|
-
} | null;
|
|
19
|
-
declare function resolvePageRewrite(pathname: string, pages: SitePage[]): PageRewriteDecision;
|
|
20
|
-
type SitePageResolution = {
|
|
21
|
-
kind: 'not-found';
|
|
22
|
-
} | {
|
|
23
|
-
kind: 'duplicate';
|
|
24
|
-
sourceSegment: string;
|
|
25
|
-
page: SitePage;
|
|
26
|
-
} | {
|
|
27
|
-
kind: 'blank';
|
|
28
|
-
page: SitePage;
|
|
29
|
-
};
|
|
30
|
-
declare function resolveSitePage(segments: string[], subdomainOverride?: string): Promise<SitePageResolution>;
|
|
31
|
-
|
|
32
|
-
declare function sitePagesMiddleware(request: NextRequest): Promise<NextResponse>;
|
|
33
|
-
declare const sitePagesMiddlewareConfig: {
|
|
34
|
-
matcher: string[];
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
interface CatchAllPageHandlers {
|
|
38
|
-
renderDuplicate: (sourceSegment: string) => Promise<{
|
|
39
|
-
default: ComponentType;
|
|
40
|
-
}>;
|
|
41
|
-
renderBlank: (page: SitePage) => ReactElement;
|
|
42
|
-
}
|
|
43
|
-
declare function renderCatchAllPage(segments: string[], subdomainOverride: string | undefined, handlers: CatchAllPageHandlers): Promise<ReactElement>;
|
|
44
|
-
|
|
45
|
-
export { type CatchAllPageHandlers, DELETED_PAGE_PREFIX, type PageRewriteDecision, type SitePage, type SitePageResolution, fetchSitePages, renderCatchAllPage, resolvePageRewrite, resolveSitePage, resolveSubdomain, sitePagesMiddleware, sitePagesMiddlewareConfig };
|
package/dist/pages.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
// src/lib/site-pages.ts
|
|
2
|
-
var DELETED_PAGE_PREFIX = "/__ohw-deleted";
|
|
3
|
-
function resolveSubdomain(hostnameOverride) {
|
|
4
|
-
if (hostnameOverride) {
|
|
5
|
-
const parts = hostnameOverride.split(".");
|
|
6
|
-
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
7
|
-
}
|
|
8
|
-
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
9
|
-
if (!siteUrl) return "";
|
|
10
|
-
try {
|
|
11
|
-
const parts = new URL(siteUrl).hostname.split(".");
|
|
12
|
-
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
13
|
-
} catch {
|
|
14
|
-
}
|
|
15
|
-
return "";
|
|
16
|
-
}
|
|
17
|
-
async function fetchSitePages(subdomainOverride) {
|
|
18
|
-
const subdomain = subdomainOverride || resolveSubdomain();
|
|
19
|
-
const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL;
|
|
20
|
-
if (!subdomain || !apiUrl) return [];
|
|
21
|
-
try {
|
|
22
|
-
const res = await fetch(`${apiUrl}/api/public/sites/${subdomain}/pages`, {
|
|
23
|
-
cache: "no-store"
|
|
24
|
-
});
|
|
25
|
-
if (!res.ok) return [];
|
|
26
|
-
const { pages } = await res.json();
|
|
27
|
-
return pages;
|
|
28
|
-
} catch {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function resolvePageRewrite(pathname, pages) {
|
|
33
|
-
const current = pages.find((page) => page.path === pathname);
|
|
34
|
-
if (current) {
|
|
35
|
-
if (current.is_deleted) return { path: `${DELETED_PAGE_PREFIX}${pathname}` };
|
|
36
|
-
if (current.is_physical && current.original_path && current.original_path !== current.path) {
|
|
37
|
-
return { path: current.original_path };
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
const oldUrlOfRenamed = pages.find(
|
|
42
|
-
(page) => page.is_physical && page.original_path === pathname && page.path !== pathname
|
|
43
|
-
);
|
|
44
|
-
if (oldUrlOfRenamed) return { path: `${DELETED_PAGE_PREFIX}${pathname}` };
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
async function resolveSitePage(segments, subdomainOverride) {
|
|
48
|
-
if (`/${segments[0]}` === DELETED_PAGE_PREFIX) return { kind: "not-found" };
|
|
49
|
-
const path = `/${segments.join("/")}`;
|
|
50
|
-
const pages = await fetchSitePages(subdomainOverride);
|
|
51
|
-
const page = pages.find((entry) => entry.path === path && !entry.is_deleted);
|
|
52
|
-
if (!page) return { kind: "not-found" };
|
|
53
|
-
if (page.duplicated_from) {
|
|
54
|
-
const sourceSegment = page.duplicated_from === "/" ? "" : page.duplicated_from.replace(/^\//, "");
|
|
55
|
-
return { kind: "duplicate", sourceSegment, page };
|
|
56
|
-
}
|
|
57
|
-
return { kind: "blank", page };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// src/lib/site-pages-middleware.ts
|
|
61
|
-
import { NextResponse } from "next/server";
|
|
62
|
-
async function sitePagesMiddleware(request) {
|
|
63
|
-
const { pathname } = request.nextUrl;
|
|
64
|
-
if (pathname.startsWith("/_next") || pathname.startsWith("/api") || /\.[a-zA-Z0-9]+$/.test(pathname)) {
|
|
65
|
-
return NextResponse.next();
|
|
66
|
-
}
|
|
67
|
-
const subdomain = request.nextUrl.searchParams.get("subdomain") || resolveSubdomain(request.nextUrl.hostname);
|
|
68
|
-
const pages = await fetchSitePages(subdomain || void 0);
|
|
69
|
-
if (pages.length === 0) return NextResponse.next();
|
|
70
|
-
const decision = resolvePageRewrite(pathname, pages);
|
|
71
|
-
if (!decision) return NextResponse.next();
|
|
72
|
-
return NextResponse.rewrite(new URL(decision.path, request.url));
|
|
73
|
-
}
|
|
74
|
-
var sitePagesMiddlewareConfig = {
|
|
75
|
-
matcher: ["/((?!_next|api).*)"]
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// src/lib/site-pages-render.ts
|
|
79
|
-
import { headers } from "next/headers";
|
|
80
|
-
import { notFound } from "next/navigation";
|
|
81
|
-
import { createElement } from "react";
|
|
82
|
-
async function renderCatchAllPage(segments, subdomainOverride, handlers) {
|
|
83
|
-
const host = (await headers()).get("host") ?? void 0;
|
|
84
|
-
const subdomain = subdomainOverride || resolveSubdomain(host) || void 0;
|
|
85
|
-
const result = await resolveSitePage(segments, subdomain);
|
|
86
|
-
if (result.kind === "not-found") notFound();
|
|
87
|
-
if (result.kind === "duplicate") {
|
|
88
|
-
try {
|
|
89
|
-
const sourceModule = await handlers.renderDuplicate(result.sourceSegment);
|
|
90
|
-
return createElement(sourceModule.default);
|
|
91
|
-
} catch {
|
|
92
|
-
notFound();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return handlers.renderBlank(result.page);
|
|
96
|
-
}
|
|
97
|
-
export {
|
|
98
|
-
DELETED_PAGE_PREFIX,
|
|
99
|
-
fetchSitePages,
|
|
100
|
-
renderCatchAllPage,
|
|
101
|
-
resolvePageRewrite,
|
|
102
|
-
resolveSitePage,
|
|
103
|
-
resolveSubdomain,
|
|
104
|
-
sitePagesMiddleware,
|
|
105
|
-
sitePagesMiddlewareConfig
|
|
106
|
-
};
|
|
107
|
-
//# sourceMappingURL=pages.js.map
|
package/dist/pages.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/site-pages.ts","../src/lib/site-pages-middleware.ts","../src/lib/site-pages-render.ts"],"sourcesContent":["// Shared page-CRUD infra (OHH-670): fetching a site's page list and deciding how the\n// catch-all route / middleware should handle deleted, renamed, and duplicated pages.\n// Server-safe (no React/browser APIs) so it can run in middleware (Edge runtime) and\n// Server Components — kept out of the main client-bundled entry point, see `../pages.ts`.\n\n// Prefix used to route deleted-physical-page requests into the catch-all `[...page]`\n// route, which has no file of its own at that segment and calls notFound() for it.\nexport const DELETED_PAGE_PREFIX = '/__ohw-deleted'\n\nexport type SitePage = {\n id: string\n path: string\n title: string\n is_physical: boolean\n original_path: string | null\n duplicated_from: string | null\n is_deleted: boolean\n}\n\n// Mirrors OhhwellsBridge's client-side resolveSubdomain: prefer the real request hostname\n// (window.location isn't available on the server, so callers pass the Host header/nextUrl\n// hostname instead) and only fall back to the NEXT_PUBLIC_SITE_URL env var when no hostname\n// is known — the only case that's true in local dev, where the host is just localhost.\nexport function resolveSubdomain(hostnameOverride?: string): string {\n if (hostnameOverride) {\n const parts = hostnameOverride.split('.')\n if (parts.length >= 3 && parts[0] !== 'www') return parts[0]\n }\n\n const siteUrl = process.env.NEXT_PUBLIC_SITE_URL\n if (!siteUrl) return ''\n try {\n const parts = new URL(siteUrl).hostname.split('.')\n if (parts.length >= 3 && parts[0] !== 'www') return parts[0]\n } catch {\n // ignore malformed env value\n }\n return ''\n}\n\n// `subdomainOverride` is the `?subdomain=` query param the canvas editor's iframe appends\n// (see `editUrl` in fo-class-experience). It's required locally: NEXT_PUBLIC_SITE_URL is\n// `http://localhost:3000` in dev, which resolveSubdomain() can't parse a subdomain from.\nexport async function fetchSitePages(subdomainOverride?: string): Promise<SitePage[]> {\n const subdomain = subdomainOverride || resolveSubdomain()\n const apiUrl = process.env.NEXT_PUBLIC_FLOWOPS_API_URL\n if (!subdomain || !apiUrl) return []\n\n try {\n // no-store, not a timed revalidate: middleware uses this list to decide whether a\n // path is deleted/renamed, and `next start`'s persistent Data Cache would otherwise\n // keep routing a just-deleted page's URL to its live content for longer than expected.\n const res = await fetch(`${apiUrl}/api/public/sites/${subdomain}/pages`, {\n cache: 'no-store',\n })\n if (!res.ok) return []\n const { pages } = (await res.json()) as { pages: SitePage[] }\n return pages\n } catch {\n return []\n }\n}\n\nexport type PageRewriteDecision = { path: string } | null\n\n// Middleware decision logic: given the current pathname and the site's page list, decide\n// whether to rewrite to the deleted-page placeholder or to a renamed physical page's real\n// file location. Callers still own the `_next`/`api`/static-file early-return and the actual\n// `NextResponse.rewrite` call, since those are Next.js-specific.\nexport function resolvePageRewrite(pathname: string, pages: SitePage[]): PageRewriteDecision {\n const current = pages.find((page) => page.path === pathname)\n\n if (current) {\n if (current.is_deleted) return { path: `${DELETED_PAGE_PREFIX}${pathname}` }\n // Renamed physical page: the file still lives at original_path, so rewrite there\n // internally while the browser keeps showing the new canonical path.\n if (current.is_physical && current.original_path && current.original_path !== current.path) {\n return { path: current.original_path }\n }\n return null\n }\n\n // Old URL of a renamed physical page — no redirect, it just no longer resolves to\n // anything (whether or not the page has since also been deleted).\n const oldUrlOfRenamed = pages.find(\n (page) => page.is_physical && page.original_path === pathname && page.path !== pathname,\n )\n if (oldUrlOfRenamed) return { path: `${DELETED_PAGE_PREFIX}${pathname}` }\n\n return null\n}\n\nexport type SitePageResolution =\n | { kind: 'not-found' }\n | { kind: 'duplicate'; sourceSegment: string; page: SitePage }\n | { kind: 'blank'; page: SitePage }\n\n// Catch-all route lookup: given the route's path segments, resolve which page (if any) this\n// request maps to. Callers still own the dynamic `import()` for `kind: 'duplicate'` — that\n// needs a literal relative path per app for webpack's static context analysis to work, so it\n// can't live in this shared package (see the comment at the call site).\nexport async function resolveSitePage(segments: string[], subdomainOverride?: string): Promise<SitePageResolution> {\n if (`/${segments[0]}` === DELETED_PAGE_PREFIX) return { kind: 'not-found' }\n\n const path = `/${segments.join('/')}`\n const pages = await fetchSitePages(subdomainOverride)\n const page = pages.find((entry) => entry.path === path && !entry.is_deleted)\n if (!page) return { kind: 'not-found' }\n\n if (page.duplicated_from) {\n const sourceSegment = page.duplicated_from === '/' ? '' : page.duplicated_from.replace(/^\\//, '')\n return { kind: 'duplicate', sourceSegment, page }\n }\n\n return { kind: 'blank', page }\n}\n","import { NextRequest, NextResponse } from 'next/server'\nimport { fetchSitePages, resolvePageRewrite, resolveSubdomain } from './site-pages'\n\n// Drop-in Next.js middleware for the OHH-670 page-CRUD system: rewrites deleted/renamed\n// pages to the right place before the catch-all route (see `resolveSitePage`) ever runs.\n// Apps that need extra middleware logic of their own can still compose `fetchSitePages`/\n// `resolvePageRewrite` directly instead of using this wrapper.\nexport async function sitePagesMiddleware(request: NextRequest): Promise<NextResponse> {\n const { pathname } = request.nextUrl\n\n if (pathname.startsWith('/_next') || pathname.startsWith('/api') || /\\.[a-zA-Z0-9]+$/.test(pathname)) {\n return NextResponse.next()\n }\n\n // `?subdomain=` is the canvas editor iframe's override (its own origin isn't the site's\n // real domain). Every other request — every real visitor — hits the site's actual\n // `*.ohhwells.site`/custom domain, so the request's own hostname is the source of truth\n // there; without this fallback the middleware silently no-ops on every production request.\n const subdomain =\n request.nextUrl.searchParams.get('subdomain') || resolveSubdomain(request.nextUrl.hostname)\n const pages = await fetchSitePages(subdomain || undefined)\n if (pages.length === 0) return NextResponse.next()\n\n const decision = resolvePageRewrite(pathname, pages)\n if (!decision) return NextResponse.next()\n return NextResponse.rewrite(new URL(decision.path, request.url))\n}\n\n// Mirrors the `_next`/`api` early-return above so apps that re-export this directly as\n// their middleware `config` don't invoke the function needlessly on asset requests.\nexport const sitePagesMiddlewareConfig = {\n matcher: ['/((?!_next|api).*)'],\n}\n","import { headers } from 'next/headers'\nimport { notFound } from 'next/navigation'\nimport { createElement, type ComponentType, type ReactElement } from 'react'\nimport { resolveSitePage, resolveSubdomain, type SitePage } from './site-pages'\n\nexport interface CatchAllPageHandlers {\n // Called for a page that's a duplicate of another. Must contain the actual `import(...)`\n // call itself (relative, not the `@` alias) — webpack can only build a static context\n // module (bundling every matching src/app/*/page.tsx) when the prefix is a real relative\n // path, so this one expression has to stay in the calling app's own file, resolved against\n // its own src/app tree, not this shared package's. Everything else about turning the\n // resolved module into a rendered element lives here instead.\n renderDuplicate: (sourceSegment: string) => Promise<{ default: ComponentType }>\n // Called for a page with no section content of its own yet.\n renderBlank: (page: SitePage) => ReactElement\n}\n\n// Full catch-all route resolution for the OHH-670 page-CRUD system: looks the page up,\n// calls `notFound()` for anything unresolved, and delegates to the two things every\n// consuming app still owns (the duplicate-source import and its blank-page chrome).\nexport async function renderCatchAllPage(\n segments: string[],\n subdomainOverride: string | undefined,\n handlers: CatchAllPageHandlers,\n): Promise<ReactElement> {\n // `subdomainOverride` is the canvas editor iframe's `?subdomain=` query param — its own\n // origin isn't the site's real domain. Every real visitor hits the site's actual\n // `*.ohhwells.site`/custom domain, so fall back to the request's own Host header there,\n // mirroring `sitePagesMiddleware`'s hostname fallback.\n const host = (await headers()).get('host') ?? undefined\n const subdomain = subdomainOverride || resolveSubdomain(host) || undefined\n const result = await resolveSitePage(segments, subdomain)\n\n if (result.kind === 'not-found') notFound()\n\n if (result.kind === 'duplicate') {\n try {\n const sourceModule = await handlers.renderDuplicate(result.sourceSegment)\n return createElement(sourceModule.default)\n } catch {\n notFound()\n }\n }\n\n return handlers.renderBlank(result.page)\n}\n"],"mappings":"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","names":[]}
|