@decocms/nextjs 8.5.2 → 8.7.0
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/README.md +82 -15
- package/dist/DraftPreviewIndicator.d.ts.map +1 -1
- package/dist/DraftPreviewIndicator.js +4 -3
- package/dist/DraftPreviewIndicator.js.map +1 -1
- package/dist/assignments.d.ts +95 -0
- package/dist/assignments.d.ts.map +1 -0
- package/dist/assignments.js +229 -0
- package/dist/assignments.js.map +1 -0
- package/dist/draft.d.ts +36 -10
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +38 -12
- package/dist/draft.js.map +1 -1
- package/dist/drafts.d.ts +39 -60
- package/dist/drafts.d.ts.map +1 -1
- package/dist/drafts.js +58 -117
- package/dist/drafts.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/instrumentation/index.d.ts +1 -1
- package/dist/instrumentation/index.js +3 -3
- package/dist/instrumentation/index.js.map +1 -1
- package/dist/proxy/index.d.ts +175 -35
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +242 -41
- package/dist/proxy/index.js.map +1 -1
- package/dist/server/index.d.ts +59 -20
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +143 -25
- package/dist/server/index.js.map +1 -1
- package/dist/server/slot.d.ts +25 -4
- package/dist/server/slot.d.ts.map +1 -1
- package/dist/server/slot.js +13 -9
- package/dist/server/slot.js.map +1 -1
- package/dist/visitor.d.ts +16 -0
- package/dist/visitor.d.ts.map +1 -0
- package/dist/visitor.js +16 -0
- package/dist/visitor.js.map +1 -0
- package/package.json +3 -3
- package/src/DraftPreviewIndicator.tsx +4 -3
- package/src/assignments.ts +278 -0
- package/src/config/index.cjs +1 -1
- package/src/draft.ts +43 -12
- package/src/drafts.ts +71 -126
- package/src/index.ts +5 -2
- package/src/instrumentation/index.ts +3 -3
- package/src/proxy/index.ts +371 -59
- package/src/server/index.ts +173 -33
- package/src/server/slot.ts +33 -5
- package/src/visitor.ts +16 -0
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ Peers: `@decocms/blocks` (same version), `next` 16, `react` 19; optionally `@ope
|
|
|
6
6
|
|
|
7
7
|
| Import | What it is for |
|
|
8
8
|
| --- | --- |
|
|
9
|
-
| `@decocms/nextjs/proxy` |
|
|
10
|
-
| `@decocms/nextjs/server` |
|
|
9
|
+
| `@decocms/nextjs/proxy` | What a request reads, decided once in `proxy.ts`: `decideDraft`, `draftRequestHeaders`, `rewriteToDraftRoute`, `applyDraft`, `isDraftRoutePath`, `decideVisitor`, `visitorRequestHeaders`, `applyVisitor`, `decideAssignments`, `assignmentsRequestHeaders`, or `draftProxy` for a site with no proxy of its own; `assignmentsKey` for a site's own cache key; `rewriteToAssignmentsRoute`, `isAssignmentsRoutePath`, `assignmentsRoutePath` and `assignmentsSegment` for Next's own cache. No React and no block map. |
|
|
10
|
+
| `@decocms/nextjs/server` | What the proxy decided, in Server Components: `bindDraft`, `activeDraftPointer`, `contentClient`; `assignedClient` for a page on the assignments route. |
|
|
11
11
|
| `@decocms/nextjs` | `DraftPreviewIndicator` (the "Preview mode" badge on a draft page), `StudioBridge` (lets Studio's site editor drive the page) and `StudioPage` (tells it which page is on screen), plus the draft names and Studio's origins (below). |
|
|
12
12
|
| `@decocms/nextjs/config` | `withDeco(nextConfig)`: the draft header rules and Studio's dev origins. CommonJS, so `next.config.js` can `require()` it. |
|
|
13
13
|
| `@decocms/nextjs/instrumentation` | `register`, the `instrumentation.ts` hook that records `http.server.request.duration` from Next's own request span, and `serverRequestSpanProcessor()` for a site with its own OpenTelemetry SDK. |
|
|
@@ -18,9 +18,9 @@ Peers: `@decocms/blocks` (same version), `next` 16, `react` 19; optionally `@ope
|
|
|
18
18
|
## Drafts
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
|
-
// src/cms.config.ts: the options both CMS handles share
|
|
22
|
-
|
|
23
|
-
export const
|
|
21
|
+
// src/cms.config.ts: the options both CMS handles share (with `site`, hosted releases and
|
|
22
|
+
// the preview hosts Studio writes into them)
|
|
23
|
+
export const cmsConfig = { content, site: "my-store" };
|
|
24
24
|
|
|
25
25
|
// src/cms.ts and src/cms.proxy.ts
|
|
26
26
|
export const cms = createCMS({ blocks, ...cmsConfig });
|
|
@@ -29,28 +29,95 @@ export const proxyCms = createCMS({ blocks: {}, ...cmsConfig }); // no block map
|
|
|
29
29
|
// src/proxy.ts
|
|
30
30
|
export async function proxy(request: NextRequest) {
|
|
31
31
|
if (isDraftRoutePath(request.nextUrl.pathname)) return new NextResponse(null, { status: 404 });
|
|
32
|
-
const draft = await decideDraft(proxyCms, request
|
|
32
|
+
const draft = await decideDraft(proxyCms, request);
|
|
33
|
+
const visitor = decideVisitor(request, draft);
|
|
34
|
+
const assignments = await decideAssignments(proxyCms, draft, visitor);
|
|
35
|
+
const headers = assignmentsRequestHeaders(
|
|
36
|
+
visitorRequestHeaders(draftRequestHeaders(request, draft), visitor),
|
|
37
|
+
assignments,
|
|
38
|
+
);
|
|
33
39
|
const response =
|
|
34
|
-
rewriteToDraftRoute(request, draft) ??
|
|
35
|
-
NextResponse.next({ request: { headers
|
|
36
|
-
return applyDraft(response, draft);
|
|
40
|
+
rewriteToDraftRoute(request, draft, visitor, assignments) ??
|
|
41
|
+
NextResponse.next({ request: { headers } });
|
|
42
|
+
return applyVisitor(applyDraft(response, draft), visitor);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
// src/app/%5Fdraft/[[...path]]/page.tsx: the dynamic draft route (a literal `_draft` folder never routes)
|
|
40
46
|
export const dynamic = "force-dynamic";
|
|
41
47
|
export default async function DraftPage({ params, searchParams }) {
|
|
42
|
-
await bindDraft(cms, { searchParams: await searchParams
|
|
48
|
+
await bindDraft(cms, { searchParams: await searchParams });
|
|
43
49
|
const client = contentClient(cms);
|
|
44
50
|
// … resolve and render the page as on the published route …
|
|
45
51
|
return <>{page}<DraftPreviewIndicator /></>;
|
|
46
52
|
}
|
|
47
53
|
```
|
|
48
54
|
|
|
49
|
-
Published pages stay
|
|
55
|
+
Published pages stay cached (ISR, below): the proxy sends only preview requests to the draft route (any `?__draft=`, `off` included, or the draft cookie; never the assignments route), sets the draft cookie, forwards the accepted version on `x-deco-version` (removing any copy sent from outside), and marks the response exactly `Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary: Cookie` and `X-Robots-Tag: noindex, nofollow`. A draft request checks for the latest release once (`cms.checkRelease()`) and forwards the check's id on `x-deco-release-check` (removing any copy sent from outside): the proxy's host check and read of the draft, `bindDraft`'s host check and the page's `contentClient` all wait for that one check, so one preview request fetches `latest.json` once (a page in another process than the proxy makes one check of its own). It reads a link's draft before forwarding it: a draft that can't be read anymore renders the release, expires the cookie and tells the pages on `x-deco-draft-unavailable`, so `DraftPreviewIndicator` shows a calm notice instead of an error page. A layout that reads content goes with its route, so the cached pages read no request: under the draft route (`app/%5Fdraft/layout.tsx`) it calls `bindDraft(cms)` and `contentClient(cms)`, which read the forwarded headers; under the assignments route (`app/%5Fassignments/[assignments]/layout.tsx`) it reads through `await assignedClient(cms, (await params).assignments)`. `bindDraft` reads `headers()`, and a cached page that reads a request header renders on every request instead, so never call it in the root layout or on the assignments route.
|
|
56
|
+
|
|
57
|
+
A draft link can carry a published release's commit SHA instead of a draft (Studio's "Preview this version"), and either one can carry a preview time (`__at`) and tests and campaigns switched on or off (`__test`, `__campaign`), which `cms.readVersion` reads as the client's `date`, `experiments` and `campaigns`. All of it passes the same host check and renders on the draft route: a visitor on the live site never picks a version. A preview may switch on a test at 0%.
|
|
58
|
+
|
|
59
|
+
### The visitor, the assignments and the client
|
|
60
|
+
|
|
61
|
+
The proxy decides once per request. `decideVisitor(request, draft)` reads the visitor with `readVisitor` from `@decocms/blocks/experiments` (the `deco_segment` cookie, or a new visitor); `applyVisitor` sends its cookie on a first visit only, never from a preview. `decideAssignments(proxyCms, draft, visitor)` then decides each test and campaign on or off: `cms.client({ visitor, version, date, experiments, campaigns })` with the visitor and the Studio link's version, moment and switched tests and campaigns, resolving the saved `experiments` and `campaigns` blocks. `visitorRequestHeaders` and `assignmentsRequestHeaders` forward them on `x-deco-visitor` and `x-deco-assignments` (`draftRequestHeaders` always removes copies sent from outside).
|
|
62
|
+
|
|
63
|
+
On the page, `bindDraft` binds the version and what the proxy forwarded (on the current release it binds the visitor and the assignments only when the release saves an `experiments` or `campaigns` block, and a test at 0% stays off whatever the header says), and `contentClient(cms)` builds `cms.client({ version, experiments, campaigns })` from them: the page decides nothing again. The pages trust those headers: behind the proxy they hold what it decided, and a request that reaches a page without it (a `matcher` that leaves the route out, or a site without the proxy) is read the same way, with whatever `x-deco-version` and `x-deco-assignments` it arrives with. Which routes go through the proxy, and how the others are protected, is the site's call. A cached page reads no request: the CMS's pages render on [the assignments route](#the-assignments-route), with the tests and campaigns the proxy decided in the path.
|
|
64
|
+
|
|
65
|
+
The binding caches nothing. A site that caches pages in front of Next (a CDN keyed on a request header) keys on the request's assignments, never on the visitor: `assignmentsKey(assignments)` from `@decocms/nextjs/proxy`, with what `decideAssignments` gave, is the version, the tests on and the campaigns running (`""` when there are none). Visitors on the same side of every test share one entry, and a campaign starting or ending changes the key. To let Next's own cache do it, see [the assignments route](#the-assignments-route).
|
|
66
|
+
|
|
67
|
+
### The assignments route
|
|
68
|
+
|
|
69
|
+
The proxy decides, the page renders that. Next caches a page by its path (ISR), so the proxy puts its decision in the path: it rewrites each CMS page to `/_assignments/<assignments>/<path>`, and the page renders exactly the tests and campaigns that segment names. Next then keeps one entry per path and set of assignments and serves it to every visitor with the same ones; the page reads no request, so Next can cache it. CMS pages are ISR with `revalidate = 600` (10 minutes): Next serves a stored page for 10 minutes; the first request after that still gets the stored copy while Next renders the page again in the background for the requests after it. So a published change shows within about 10 minutes on a page people open, and a page nobody opened for longer shows its old copy once more. That delay (for example, a banner switching on late) is accepted. Never `force-static` on Next.js: a page that never expires would keep what it rendered first.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
// src/proxy.ts
|
|
73
|
+
import { isAssignmentsRoutePath, isDraftRoutePath, rewriteToAssignmentsRoute /* … */ } from "@decocms/nextjs/proxy";
|
|
74
|
+
|
|
75
|
+
export async function proxy(request: NextRequest) {
|
|
76
|
+
const { pathname } = request.nextUrl;
|
|
77
|
+
if (isDraftRoutePath(pathname) || isAssignmentsRoutePath(pathname)) return new NextResponse(null, { status: 404 });
|
|
78
|
+
const draft = await decideDraft(proxyCms, request, preview);
|
|
79
|
+
const visitor = decideVisitor(request, draft);
|
|
80
|
+
const assigned = isCmsPage(pathname) ? await rewriteToAssignmentsRoute(proxyCms, request, draft, visitor) : null;
|
|
81
|
+
if (assigned) return applyVisitor(applyDraft(assigned, draft), visitor);
|
|
82
|
+
const assignments = await decideAssignments(proxyCms, draft, visitor);
|
|
83
|
+
const headers = assignmentsRequestHeaders(visitorRequestHeaders(draftRequestHeaders(request, draft), visitor), assignments);
|
|
84
|
+
const response = rewriteToDraftRoute(request, draft, visitor, assignments) ?? NextResponse.next({ request: { headers } });
|
|
85
|
+
return applyVisitor(applyDraft(response, draft), visitor);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Next's own files never go through the proxy: rewritten, every script and style would be a 404.
|
|
89
|
+
export const config = { matcher: ["/((?!_next/|favicon.ico).*)"] };
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// src/app/%5Fassignments/[assignments]/[[...path]]/page.tsx: every CMS page (a literal `_assignments` folder never routes)
|
|
94
|
+
import { assignedClient } from "@decocms/nextjs/server";
|
|
95
|
+
|
|
96
|
+
export const revalidate = 600; // 10 minutes; never force-static
|
|
97
|
+
export async function generateStaticParams() {
|
|
98
|
+
return []; // no path at build time: each renders on its first request, then is cached
|
|
99
|
+
}
|
|
100
|
+
export default async function Page({ params }) {
|
|
101
|
+
const { assignments, path } = await params;
|
|
102
|
+
const client = await assignedClient(cms, assignments);
|
|
103
|
+
if (!client) notFound();
|
|
104
|
+
// … match the path and resolve the page through `client`, as on any page …
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- **What the segment holds**: every test of the saved `experiments` block on or off for the visitor and every campaign of the saved `campaigns` block running or not now (`readAssignments` on the current release), as `assignmentsSegment` writes it: `-` for none, otherwise base64url, one spelling per set of assignments. Visitors on the same side of every test get the same path; the visitor's id is never in it, and the rewritten request carries neither the visitor nor a draft header. A campaign starting or ending moves every visitor to a new path.
|
|
109
|
+
- **The page renders what the path names**: `await assignedClient(cms, segment)` builds `cms.client({ experiments, campaigns })` with every test and campaign as the segment names it and no visitor, a client fully decided in advance, so every visitor on that side renders the same page. The page never checks a campaign's dates or rolls a test itself. A test now at 0% stays off whatever the segment names. A page Next renders again later (revalidation) shows the same tests and campaigns under the same path.
|
|
110
|
+
- **The route trusts the path its proxy writes**: the proxy answers a direct hit on `/_assignments/…` with a 404, as it does for `/_draft/…`, so behind the proxy only its own rewrite reaches the route. A request that reaches the page some other way (a proxy `matcher` that leaves the route out, or a site without the proxy) renders the tests and campaigns its path names: `assignedClient` only keeps off a test at 0% and a test or a campaign the content doesn't have, and returns `null` (a 404) for a segment the proxy never writes. Which routes go through the proxy, and how the others are protected, is the site's call. Leaving off rather than answering 404 matters because Next caches a 404 under the path too: a path the proxy wrote before the page's server saw newly published content still renders.
|
|
111
|
+
- **The URL asked for stays private**: when the content saves tests or campaigns, the rewrite marks the response `Cache-Control: private, no-cache`. Next keeps one cached page per path and set of assignments, but `/shoes` itself answers by the visitor's cookie and the moment, so a CDN in front must not keep Next's ISR `s-maxage` under it.
|
|
112
|
+
- **A cached page reads no request**: `headers()`, `cookies()` or `searchParams` anywhere in the route (its layouts included, so no `bindDraft` there) make Next render it on every request instead of caching it, and a client component calling `useSearchParams()` renders on the client up to its nearest `<Suspense>`. `force-static` used to hide both by handing the page empty values; with ISR the page takes what it needs from the path, as the assignments do.
|
|
113
|
+
- **Drafts and previews are never rewritten here**: a drafted request goes to the dynamic draft route, so nothing from a preview lands in Next's cache.
|
|
114
|
+
- **Which paths**: only the CMS's pages (`isCmsPage` above is the site's own: routes of the app's own, such as `/blog` here, render as before). `draftProxy(proxyCms, { assignments: (request) => isCmsPage(request.nextUrl.pathname) })` does the same for a site with no proxy of its own.
|
|
115
|
+
|
|
116
|
+
`examples/nextjs-binding-smoke` in the repository runs it: two visitors on opposite sides of a test get different paths, two on the same side get one cached entry (`x-nextjs-cache: HIT`), the route is ISR at 600 seconds, and a typed `/_assignments/…` path is a 404.
|
|
50
117
|
|
|
51
118
|
### Draft hosts
|
|
52
119
|
|
|
53
|
-
|
|
120
|
+
A draft link (`?__draft=`) previews on the hosts the CMS allows, which `cms.readVersion` checks: the addresses Studio writes into the current release's metadata (`previewHosts` in `latest.json`, read with `createCMS`'s `site`), plus `createCMS({ preview: { hosts } })`. The site lists nothing else. With no list, every host previews: a site with no `latest.json` on the CDN yet, or one published before Studio wrote `previewHosts` (its next publish writes the list), and content with no `site` unless `preview.hosts` lists hosts (such as `["localhost"]`). If `latest.json` can't be read (a network error, a 5xx, a malformed file), previews stay closed, besides `preview.hosts`, until a read succeeds. The host is the one the visitor asked for (`x-forwarded-host`, else `host`); the proxy and `bindDraft` ask the same CMS. On a host that may not preview, the page renders the release; `?__draft=off` still ends a preview anywhere.
|
|
54
121
|
|
|
55
122
|
## The layout
|
|
56
123
|
|
|
@@ -79,7 +146,7 @@ It renders a `<meta>` that React places in the `<head>`, so the page's own marku
|
|
|
79
146
|
|
|
80
147
|
## Names and Studio's origins
|
|
81
148
|
|
|
82
|
-
For a site that needs them in its own code (a route that sets its own headers, a CSP it writes itself), `@decocms/nextjs` exports what the proxy, the pages and `withDeco` use: `DRAFT_PARAM` (`__draft`), `DRAFT_COOKIE` (`__deco_draft`), `
|
|
149
|
+
For a site that needs them in its own code (a route that sets its own headers, a CSP it writes itself), `@decocms/nextjs` exports what the proxy, the pages and `withDeco` use: `DRAFT_PARAM` (`__draft`), `DRAFT_COOKIE` (`__deco_draft`), `VERSION_HEADER` (`x-deco-version`), `VISITOR_HEADER` (`x-deco-visitor`), `ASSIGNMENTS_HEADER` (`x-deco-assignments`), `draftResponseHeaders(version)`, `STUDIO_ORIGINS`, `STUDIO_FRAME_ANCESTORS` and `isStudioOrigin(origin)`. They have the same values as `@decocms/tanstack`'s; each binding keeps its own copy and never imports the other.
|
|
83
150
|
|
|
84
151
|
## next.config
|
|
85
152
|
|
|
@@ -88,7 +155,7 @@ const { withDeco } = require("@decocms/nextjs/config");
|
|
|
88
155
|
module.exports = withDeco({ images: { loader: "custom", loaderFile: "./src/image-loader.ts" } });
|
|
89
156
|
```
|
|
90
157
|
|
|
91
|
-
`withDeco` puts v7's two draft header rules before your own (a `?__draft=` query, or the draft cookie: no-
|
|
158
|
+
`withDeco` puts v7's two draft header rules before your own (a `?__draft=` query, or the draft cookie: `max-age=0, no-cache, must-revalidate`, `Vary: Cookie`, noindex) and adds Studio's origins to `allowedDevOrigins`. Each can be turned off: `withDeco(config, { draftHeaders: false, studioOrigins: false })`. Next sets `Cache-Control` and `Vary` itself on dynamic pages, so only `X-Robots-Tag` is sure to arrive from these rules; the proxy's `applyDraft` sets all three on the responses it makes.
|
|
92
159
|
|
|
93
160
|
## Request metric
|
|
94
161
|
|
|
@@ -104,7 +171,7 @@ Next.js already measures every request it serves: one root span per request (`Ba
|
|
|
104
171
|
| `http.route` | Next's route template: `/[[...path]]`, `/blog`, `/_draft/[[...path]]`. An RSC request counts under its page's route. `unmatched` when Next names none (an Edge route under `next start`) |
|
|
105
172
|
| `http.request.method` | `GET`, `POST`, …; any other is `_OTHER` |
|
|
106
173
|
| `status_class` | `2xx` … `5xx`; a request that threw is `5xx` |
|
|
107
|
-
| `cache` | `DRAFT` for a draft render (the proxy forwarded `x-deco-
|
|
174
|
+
| `cache` | `DRAFT` for a draft render (the proxy forwarded `x-deco-version`, or the request was rewritten onto `/_draft`), otherwise `BYPASS`: Next decides its own cache (`x-nextjs-cache`) on the response, which the span never sees |
|
|
108
175
|
|
|
109
176
|
- `@opentelemetry/api` is an optional peer: `register` uses the same copy Next does (yours when installed, else Next's compiled one) and does nothing if neither loads.
|
|
110
177
|
- **Your own OpenTelemetry setup** (`@vercel/otel`, `NodeSDK`): only one tracer provider can be registered, so `register` leaves yours alone and logs how to combine them. Add the processor to yours instead: `registerOTel({ serviceName, spanProcessors: ["auto", serverRequestSpanProcessor()] })`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DraftPreviewIndicator.d.ts","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DraftPreviewIndicator.d.ts","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,MAAM,WAAW,0BAA0B;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAAE,0BAA+B,GAAG,SAAS,CAI9F"}
|
|
@@ -2,18 +2,19 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* The "Preview mode" badge for the request's draft: a Server Component that
|
|
4
4
|
* renders `DraftBadge` (`@decocms/blocks/preview`) only when `bindDraft`
|
|
5
|
-
* bound a draft, so a published page emits nothing
|
|
5
|
+
* bound a draft, so a published page emits nothing; when the link named a
|
|
6
|
+
* draft that can't be read anymore, the badge's calm notice instead.
|
|
6
7
|
*
|
|
7
8
|
* Render it inside the page, after `bindDraft` was awaited, never from a
|
|
8
9
|
* layout: a layout renders alongside the page that fills it, so it may run
|
|
9
10
|
* before the page bound its draft.
|
|
10
11
|
*/
|
|
11
12
|
import { DraftBadge } from "@decocms/blocks/preview";
|
|
12
|
-
import { activeDraftPointer } from "./server/slot.js";
|
|
13
|
+
import { activeDraftPointer, draftUnavailable } from "./server/slot.js";
|
|
13
14
|
export function DraftPreviewIndicator({ exitHref } = {}) {
|
|
14
15
|
const pointer = activeDraftPointer();
|
|
15
16
|
if (!pointer)
|
|
16
|
-
return null;
|
|
17
|
+
return draftUnavailable() ? _jsx(DraftBadge, { pointer: null, unavailable: true }) : null;
|
|
17
18
|
return _jsx(DraftBadge, { pointer: pointer, exitHref: exitHref });
|
|
18
19
|
}
|
|
19
20
|
//# sourceMappingURL=DraftPreviewIndicator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DraftPreviewIndicator.js","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"DraftPreviewIndicator.js","sourceRoot":"","sources":["../src/DraftPreviewIndicator.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAOxE,MAAM,UAAU,qBAAqB,CAAC,EAAE,QAAQ,EAAE,GAA+B,EAAE;IACjF,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,IAAI,CAAC,OAAO;QAAE,OAAO,gBAAgB,EAAE,CAAC,CAAC,CAAC,KAAC,UAAU,IAAC,OAAO,EAAE,IAAI,EAAE,WAAW,SAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3F,OAAO,KAAC,UAAU,IAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAI,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request's assignments: what its content gives it, decided once, in the
|
|
3
|
+
* proxy, and handed to the page. The version it reads (a Studio link's draft
|
|
4
|
+
* or release SHA, `null` for the current release), each test of the saved
|
|
5
|
+
* `experiments` block on or off for its visitor, and each campaign of the
|
|
6
|
+
* saved `campaigns` block running or not at its date, with a preview's
|
|
7
|
+
* switched tests and campaigns on top. The page builds
|
|
8
|
+
* `cms.client({ version, experiments, campaigns })` from them, a client that
|
|
9
|
+
* decides nothing again.
|
|
10
|
+
*
|
|
11
|
+
* Two requests with the same assignments render the same content, so they
|
|
12
|
+
* share a cache entry: `assignmentsKey` for a cache keyed on a string,
|
|
13
|
+
* `assignmentsSegment` for Next's own path-keyed cache. The visitor id itself
|
|
14
|
+
* is never in either, or every visitor would miss. The binding caches nothing
|
|
15
|
+
* itself.
|
|
16
|
+
*
|
|
17
|
+
* The same reading as `@decocms/tanstack/worker`'s.
|
|
18
|
+
*/
|
|
19
|
+
import type { ClientOptions, CMS } from "@decocms/blocks";
|
|
20
|
+
import type { Assignments } from "@decocms/blocks/experiments";
|
|
21
|
+
export interface RequestAssignments {
|
|
22
|
+
/** The version read, `null` for the current release. */
|
|
23
|
+
version: string | null;
|
|
24
|
+
/** Each test of the saved `experiments` block: on or off. */
|
|
25
|
+
experiments: Readonly<Assignments>;
|
|
26
|
+
/** Each campaign of the saved `campaigns` block: running or not. */
|
|
27
|
+
campaigns: Readonly<Assignments>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The request header the proxy hands the pages the assignments it decided on
|
|
31
|
+
* (`assignmentsSegment`), for `bindDraft` and `contentClient`. The proxy
|
|
32
|
+
* removes any copy that arrives from outside.
|
|
33
|
+
*/
|
|
34
|
+
export declare const ASSIGNMENTS_HEADER = "x-deco-assignments";
|
|
35
|
+
/** What a page's path and the decisions header carry: the tests and campaigns, never the version or the visitor. */
|
|
36
|
+
export type PathAssignments = Pick<RequestAssignments, "experiments" | "campaigns">;
|
|
37
|
+
/**
|
|
38
|
+
* The assignments of one request: `cms.client(options)` (the version, the
|
|
39
|
+
* visitor, the date and a preview's switched tests and campaigns, as
|
|
40
|
+
* `cms.readVersion` reads them) resolving `experiments` and `campaigns`.
|
|
41
|
+
* Missing blocks (or content that doesn't load) give none, never an error.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readAssignments(cms: Pick<CMS, "client">, options: ClientOptions): Promise<RequestAssignments>;
|
|
44
|
+
/**
|
|
45
|
+
* `readAssignments`, and whether its page can differ by them: `true` when the
|
|
46
|
+
* content saves tests or campaigns (or couldn't be read, to stay safe), so a
|
|
47
|
+
* response keyed on them must never be shared by its URL alone.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readAssignmentsAndScope(cms: Pick<CMS, "client">, options: ClientOptions): Promise<{
|
|
50
|
+
assignments: RequestAssignments;
|
|
51
|
+
varies: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* The assignments as one stable string for a cache key:
|
|
55
|
+
* `v=<version>|x=<tests on>|c=<campaigns running>`, names sorted and
|
|
56
|
+
* URL-encoded, empty parts left out; `""` for none. The names that are off
|
|
57
|
+
* aren't in it: within one content revision they are the rest.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assignmentsKey(assignments: RequestAssignments): string;
|
|
60
|
+
/**
|
|
61
|
+
* The route a page renders on under the assignments its proxy made:
|
|
62
|
+
* `/_assignments/<assignments>/<path>`, mounted as `app/%5Fassignments/[assignments]/[[...path]]`
|
|
63
|
+
* (a literal `_assignments` folder is private in Next and never routes). The proxy
|
|
64
|
+
* rewrites to it (`rewriteToAssignmentsRoute`) and answers a direct hit with
|
|
65
|
+
* its 404, so a visitor can't pick the assignments by typing a path.
|
|
66
|
+
*/
|
|
67
|
+
export declare const ASSIGNMENTS_ROUTE_PREFIX = "/_assignments";
|
|
68
|
+
/**
|
|
69
|
+
* The tests and campaigns as one path segment, every name with its state
|
|
70
|
+
* (`x=+on,-off|c=+running,-not`, base64url-encoded so Next passes it to the
|
|
71
|
+
* page unchanged), for Next's own path-keyed cache (ISR, the full route
|
|
72
|
+
* cache) and for the decisions header (`ASSIGNMENTS_HEADER`): `-` for none.
|
|
73
|
+
* The same assignments always give the same segment; the visitor is never in
|
|
74
|
+
* it.
|
|
75
|
+
*/
|
|
76
|
+
export declare function assignmentsSegment(assignments: PathAssignments): string;
|
|
77
|
+
/**
|
|
78
|
+
* The tests and campaigns a segment carries, or `null` when it isn't one
|
|
79
|
+
* `assignmentsSegment` writes (malformed, or the same assignments spelled
|
|
80
|
+
* another way), so each set of assignments has exactly one path.
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseAssignmentsSegment(segment: string): PathAssignments | null;
|
|
83
|
+
/** `/_assignments/<segment><pathname>` (the bare prefix and segment for `/`). */
|
|
84
|
+
export declare function assignmentsRoutePath(pathname: string, assignments: PathAssignments): string;
|
|
85
|
+
/** Whether `pathname` is on the assignments route: a site's proxy answers a direct hit there with its 404. */
|
|
86
|
+
export declare function isAssignmentsRoutePath(pathname: string): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The tests a path or header names, as a page renders them on the current
|
|
89
|
+
* release: a test at 0% stays off whatever it says, so a forged path or
|
|
90
|
+
* header can't switch on a test nobody is in (a preview, on its own version,
|
|
91
|
+
* may). A name the content doesn't have stays out anyway (`cms.client`
|
|
92
|
+
* keeps only the block's names). Read from memory, unresolved.
|
|
93
|
+
*/
|
|
94
|
+
export declare function withoutDarkTests(cms: Pick<CMS, "forRelease">, assignments: PathAssignments): Promise<PathAssignments>;
|
|
95
|
+
//# sourceMappingURL=assignments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assignments.d.ts","sourceRoot":"","sources":["../src/assignments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,6DAA6D;IAC7D,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACnC,oEAAoE;IACpE,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,oHAAoH;AACpH,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,kBAAkB,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC;AA4CpF;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EACxB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAE7B;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EACxB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,WAAW,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC,CAqB/D;AASD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,kBAAkB,GAAG,MAAM,CAStE;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AA8BxD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,eAAe,GAAG,MAAM,CAOvE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAwB/E;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,MAAM,CAG3F;AAED,8GAA8G;AAC9G,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIhE;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAC5B,WAAW,EAAE,eAAe,GAC3B,OAAO,CAAC,eAAe,CAAC,CAmB1B"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request header the proxy hands the pages the assignments it decided on
|
|
3
|
+
* (`assignmentsSegment`), for `bindDraft` and `contentClient`. The proxy
|
|
4
|
+
* removes any copy that arrives from outside.
|
|
5
|
+
*/
|
|
6
|
+
export const ASSIGNMENTS_HEADER = "x-deco-assignments";
|
|
7
|
+
const EXPERIMENTS = { block: "experiments", type: "split-traffic" };
|
|
8
|
+
const CAMPAIGNS = { block: "campaigns", type: "calendar" };
|
|
9
|
+
/** Plain code-unit order, the same on every server and locale. */
|
|
10
|
+
const byCodeUnit = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function record() {
|
|
15
|
+
return Object.create(null);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Whether the content saves a block of `type`: listing never fails on a
|
|
19
|
+
* missing block, so a site without tests or campaigns reports no error.
|
|
20
|
+
*/
|
|
21
|
+
async function saves(client, type) {
|
|
22
|
+
const [list] = await client.list(type);
|
|
23
|
+
return (list?.length ?? 0) > 0;
|
|
24
|
+
}
|
|
25
|
+
/** The saved `block` resolved, each name `true` or `false`; `null` when the content saves none. */
|
|
26
|
+
async function decided(client, spec) {
|
|
27
|
+
try {
|
|
28
|
+
if (!(await saves(client, spec.type)))
|
|
29
|
+
return null;
|
|
30
|
+
const [value] = await client.resolve(spec.block);
|
|
31
|
+
const out = record();
|
|
32
|
+
if (!isRecord(value))
|
|
33
|
+
return out;
|
|
34
|
+
for (const name of Object.keys(value).sort(byCodeUnit))
|
|
35
|
+
out[name] = value[name] === true;
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return record();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The assignments of one request: `cms.client(options)` (the version, the
|
|
44
|
+
* visitor, the date and a preview's switched tests and campaigns, as
|
|
45
|
+
* `cms.readVersion` reads them) resolving `experiments` and `campaigns`.
|
|
46
|
+
* Missing blocks (or content that doesn't load) give none, never an error.
|
|
47
|
+
*/
|
|
48
|
+
export async function readAssignments(cms, options) {
|
|
49
|
+
return (await readAssignmentsAndScope(cms, options)).assignments;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `readAssignments`, and whether its page can differ by them: `true` when the
|
|
53
|
+
* content saves tests or campaigns (or couldn't be read, to stay safe), so a
|
|
54
|
+
* response keyed on them must never be shared by its URL alone.
|
|
55
|
+
*/
|
|
56
|
+
export async function readAssignmentsAndScope(cms, options) {
|
|
57
|
+
const version = typeof options.version === "string" ? options.version : null;
|
|
58
|
+
const none = { version, experiments: record(), campaigns: record() };
|
|
59
|
+
let client;
|
|
60
|
+
try {
|
|
61
|
+
client = cms.client(options);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return { assignments: none, varies: true };
|
|
65
|
+
}
|
|
66
|
+
const [experiments, campaigns] = await Promise.all([
|
|
67
|
+
decided(client, EXPERIMENTS),
|
|
68
|
+
decided(client, CAMPAIGNS),
|
|
69
|
+
]);
|
|
70
|
+
return {
|
|
71
|
+
assignments: {
|
|
72
|
+
version,
|
|
73
|
+
experiments: experiments ?? none.experiments,
|
|
74
|
+
campaigns: campaigns ?? none.campaigns,
|
|
75
|
+
},
|
|
76
|
+
varies: experiments !== null || campaigns !== null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** The names that are on, sorted. */
|
|
80
|
+
function onNames(assignments) {
|
|
81
|
+
return Object.keys(assignments)
|
|
82
|
+
.filter((name) => assignments[name] === true)
|
|
83
|
+
.sort(byCodeUnit);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The assignments as one stable string for a cache key:
|
|
87
|
+
* `v=<version>|x=<tests on>|c=<campaigns running>`, names sorted and
|
|
88
|
+
* URL-encoded, empty parts left out; `""` for none. The names that are off
|
|
89
|
+
* aren't in it: within one content revision they are the rest.
|
|
90
|
+
*/
|
|
91
|
+
export function assignmentsKey(assignments) {
|
|
92
|
+
const list = (names) => names.map(encodeURIComponent).join(",");
|
|
93
|
+
const parts = [];
|
|
94
|
+
if (assignments.version)
|
|
95
|
+
parts.push(`v=${encodeURIComponent(assignments.version)}`);
|
|
96
|
+
const tests = onNames(assignments.experiments);
|
|
97
|
+
const campaigns = onNames(assignments.campaigns);
|
|
98
|
+
if (tests.length)
|
|
99
|
+
parts.push(`x=${list(tests)}`);
|
|
100
|
+
if (campaigns.length)
|
|
101
|
+
parts.push(`c=${list(campaigns)}`);
|
|
102
|
+
return parts.join("|");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The route a page renders on under the assignments its proxy made:
|
|
106
|
+
* `/_assignments/<assignments>/<path>`, mounted as `app/%5Fassignments/[assignments]/[[...path]]`
|
|
107
|
+
* (a literal `_assignments` folder is private in Next and never routes). The proxy
|
|
108
|
+
* rewrites to it (`rewriteToAssignmentsRoute`) and answers a direct hit with
|
|
109
|
+
* its 404, so a visitor can't pick the assignments by typing a path.
|
|
110
|
+
*/
|
|
111
|
+
export const ASSIGNMENTS_ROUTE_PREFIX = "/_assignments";
|
|
112
|
+
/** The segment for content with no test and no campaign. */
|
|
113
|
+
const NONE = "-";
|
|
114
|
+
function toBase64Url(text) {
|
|
115
|
+
let binary = "";
|
|
116
|
+
for (const byte of new TextEncoder().encode(text))
|
|
117
|
+
binary += String.fromCharCode(byte);
|
|
118
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
119
|
+
}
|
|
120
|
+
function fromBase64Url(segment) {
|
|
121
|
+
if (!/^[A-Za-z0-9_-]+$/.test(segment))
|
|
122
|
+
return null;
|
|
123
|
+
try {
|
|
124
|
+
const binary = atob(segment.replace(/-/g, "+").replace(/_/g, "/"));
|
|
125
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
126
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** `+name` on, `-name` off, names URL-encoded and sorted. */
|
|
133
|
+
function encodeStates(states) {
|
|
134
|
+
return Object.keys(states)
|
|
135
|
+
.sort(byCodeUnit)
|
|
136
|
+
.map((name) => `${states[name] === true ? "+" : "-"}${encodeURIComponent(name)}`)
|
|
137
|
+
.join(",");
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The tests and campaigns as one path segment, every name with its state
|
|
141
|
+
* (`x=+on,-off|c=+running,-not`, base64url-encoded so Next passes it to the
|
|
142
|
+
* page unchanged), for Next's own path-keyed cache (ISR, the full route
|
|
143
|
+
* cache) and for the decisions header (`ASSIGNMENTS_HEADER`): `-` for none.
|
|
144
|
+
* The same assignments always give the same segment; the visitor is never in
|
|
145
|
+
* it.
|
|
146
|
+
*/
|
|
147
|
+
export function assignmentsSegment(assignments) {
|
|
148
|
+
const parts = [];
|
|
149
|
+
const tests = encodeStates(assignments.experiments);
|
|
150
|
+
const campaigns = encodeStates(assignments.campaigns);
|
|
151
|
+
if (tests)
|
|
152
|
+
parts.push(`x=${tests}`);
|
|
153
|
+
if (campaigns)
|
|
154
|
+
parts.push(`c=${campaigns}`);
|
|
155
|
+
return parts.length === 0 ? NONE : toBase64Url(parts.join("|"));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The tests and campaigns a segment carries, or `null` when it isn't one
|
|
159
|
+
* `assignmentsSegment` writes (malformed, or the same assignments spelled
|
|
160
|
+
* another way), so each set of assignments has exactly one path.
|
|
161
|
+
*/
|
|
162
|
+
export function parseAssignmentsSegment(segment) {
|
|
163
|
+
if (segment === NONE)
|
|
164
|
+
return { experiments: record(), campaigns: record() };
|
|
165
|
+
const key = fromBase64Url(segment);
|
|
166
|
+
if (key === null)
|
|
167
|
+
return null;
|
|
168
|
+
const parsed = { experiments: record(), campaigns: record() };
|
|
169
|
+
const seen = new Set();
|
|
170
|
+
try {
|
|
171
|
+
for (const part of key.split("|")) {
|
|
172
|
+
const eq = part.indexOf("=");
|
|
173
|
+
const tag = part.slice(0, eq);
|
|
174
|
+
const list = part.slice(eq + 1);
|
|
175
|
+
if (eq <= 0 || list === "" || seen.has(tag) || (tag !== "x" && tag !== "c"))
|
|
176
|
+
return null;
|
|
177
|
+
seen.add(tag);
|
|
178
|
+
const target = tag === "x" ? parsed.experiments : parsed.campaigns;
|
|
179
|
+
for (const entry of list.split(",")) {
|
|
180
|
+
const state = entry[0];
|
|
181
|
+
if (state !== "+" && state !== "-")
|
|
182
|
+
return null;
|
|
183
|
+
target[decodeURIComponent(entry.slice(1))] = state === "+";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return assignmentsSegment(parsed) === segment ? parsed : null;
|
|
191
|
+
}
|
|
192
|
+
/** `/_assignments/<segment><pathname>` (the bare prefix and segment for `/`). */
|
|
193
|
+
export function assignmentsRoutePath(pathname, assignments) {
|
|
194
|
+
const base = `${ASSIGNMENTS_ROUTE_PREFIX}/${assignmentsSegment(assignments)}`;
|
|
195
|
+
return pathname === "/" || pathname === "" ? base : `${base}${pathname}`;
|
|
196
|
+
}
|
|
197
|
+
/** Whether `pathname` is on the assignments route: a site's proxy answers a direct hit there with its 404. */
|
|
198
|
+
export function isAssignmentsRoutePath(pathname) {
|
|
199
|
+
return (pathname === ASSIGNMENTS_ROUTE_PREFIX || pathname.startsWith(`${ASSIGNMENTS_ROUTE_PREFIX}/`));
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The tests a path or header names, as a page renders them on the current
|
|
203
|
+
* release: a test at 0% stays off whatever it says, so a forged path or
|
|
204
|
+
* header can't switch on a test nobody is in (a preview, on its own version,
|
|
205
|
+
* may). A name the content doesn't have stays out anyway (`cms.client`
|
|
206
|
+
* keeps only the block's names). Read from memory, unresolved.
|
|
207
|
+
*/
|
|
208
|
+
export async function withoutDarkTests(cms, assignments) {
|
|
209
|
+
const on = Object.keys(assignments.experiments).filter((name) => assignments.experiments[name] === true);
|
|
210
|
+
if (on.length === 0)
|
|
211
|
+
return assignments;
|
|
212
|
+
let saved = {};
|
|
213
|
+
try {
|
|
214
|
+
const [block] = await cms.forRelease().resolve(EXPERIMENTS.block, { run: false });
|
|
215
|
+
if (isRecord(block) && block.__resolveType === EXPERIMENTS.type)
|
|
216
|
+
saved = block;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// No experiments block: every test the client is given stays out.
|
|
220
|
+
}
|
|
221
|
+
const experiments = record();
|
|
222
|
+
for (const name of Object.keys(assignments.experiments)) {
|
|
223
|
+
const pct = Object.hasOwn(saved, name) ? saved[name] : undefined;
|
|
224
|
+
const dark = typeof pct !== "number" || !Number.isFinite(pct) || Math.round(pct) <= 0;
|
|
225
|
+
experiments[name] = assignments.experiments[name] === true && !dark;
|
|
226
|
+
}
|
|
227
|
+
return { experiments, campaigns: assignments.campaigns };
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=assignments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assignments.js","sourceRoot":"","sources":["../src/assignments.ts"],"names":[],"mappings":"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"}
|
package/dist/draft.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The draft names and headers of a Deco Next.js site: the `?__draft=` link,
|
|
3
3
|
* the cookie that keeps a preview going, the request header the proxy hands
|
|
4
|
-
* the pages, and the response headers of a page rendered from a draft. The
|
|
4
|
+
* the pages the version on, and the response headers of a page rendered from a draft. The
|
|
5
5
|
* proxy reads and writes them (`./drafts.ts`, `./proxy`); the pages read the
|
|
6
6
|
* header (`./server`); `./config/index.cjs` repeats them (`config.test.ts`
|
|
7
7
|
* holds them equal). This package's own copy: it never imports
|
|
@@ -20,18 +20,44 @@ export declare const DRAFT_PARAM = "__draft";
|
|
|
20
20
|
export declare const DRAFT_COOKIE = "__deco_draft";
|
|
21
21
|
/**
|
|
22
22
|
* The request header the proxy sets on the request it hands the pages once it
|
|
23
|
-
* accepted
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* accepted a Studio link: the version (a draft pointer or a release's commit
|
|
24
|
+
* SHA), already checked, without the preview's moment and switched tests
|
|
25
|
+
* (those are decided in the proxy: `ASSIGNMENTS_HEADER`). The proxy removes
|
|
26
|
+
* any copy that arrives from outside, and `bindDraft` repeats the host check,
|
|
27
|
+
* so a client can't send one itself.
|
|
26
28
|
*/
|
|
27
|
-
export declare const
|
|
29
|
+
export declare const VERSION_HEADER = "x-deco-version";
|
|
30
|
+
/**
|
|
31
|
+
* The request header the proxy sets on a draft request it hands the pages:
|
|
32
|
+
* the id of the release check it made for it (`cms.checkRelease()`), so
|
|
33
|
+
* `bindDraft`'s host check and the page's draft reads wait for that check
|
|
34
|
+
* instead of making their own (one `latest.json` read per preview request).
|
|
35
|
+
* The proxy removes any copy that arrives from outside; an id the CMS didn't
|
|
36
|
+
* make (a proxy in another process) is a check of its own anyway.
|
|
37
|
+
*/
|
|
38
|
+
export declare const RELEASE_CHECK_HEADER = "x-deco-release-check";
|
|
28
39
|
/** The URL prefix a drafted request is rewritten onto (`app/%5Fdraft/[[...path]]`). */
|
|
29
40
|
export declare const DRAFT_ROUTE_PREFIX = "/_draft";
|
|
30
41
|
/**
|
|
31
|
-
* The
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
42
|
+
* The request header the proxy sets when the link named a draft that can't be
|
|
43
|
+
* read anymore (deleted, say): the pages render the release and
|
|
44
|
+
* `DraftPreviewIndicator` says so calmly. The proxy removes any copy that
|
|
45
|
+
* arrives from outside.
|
|
46
|
+
*/
|
|
47
|
+
export declare const DRAFT_UNAVAILABLE_HEADER = "x-deco-draft-unavailable";
|
|
48
|
+
/**
|
|
49
|
+
* The `Cache-Control` of every response to a preview request (a request with
|
|
50
|
+
* `?__draft=`, whatever its value, or the draft cookie): always revalidated,
|
|
51
|
+
* never answered from a cache (PO 2026-10-23).
|
|
52
|
+
*/
|
|
53
|
+
export declare const DRAFT_CACHE_CONTROL = "max-age=0, no-cache, must-revalidate";
|
|
54
|
+
/**
|
|
55
|
+
* The response headers of a preview: never answered from a browser's or a
|
|
56
|
+
* CDN's cache (`Cache-Control: max-age=0, no-cache, must-revalidate`, `Vary:
|
|
57
|
+
* Cookie`) and never indexed (`X-Robots-Tag: noindex, nofollow`). `{}` when
|
|
58
|
+
* `preview` is `null`, `false` or empty, so the result of the draft decision
|
|
59
|
+
* applies as is: pass the version, or whether the request carries a draft
|
|
60
|
+
* signal at all (`?__draft=off` and a refused or unavailable draft too).
|
|
35
61
|
*/
|
|
36
|
-
export declare function draftResponseHeaders(
|
|
62
|
+
export declare function draftResponseHeaders(preview: string | boolean | null | undefined): Record<string, string>;
|
|
37
63
|
//# sourceMappingURL=draft.d.ts.map
|
package/dist/draft.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C
|
|
1
|
+
{"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../src/draft.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;GAIG;AACH,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,6BAA6B,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAE1E;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,GAC3C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB"}
|