payload-live-preview 1.4.1 → 1.6.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 +136 -2
- package/dist/adapters/astro/index.d.ts +32 -4
- package/dist/adapters/astro/index.js +1 -1
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.d.ts +1 -1
- package/dist/adapters/astro/middleware-entry.js +1 -1
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.js +1 -1
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.js +1 -1
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.js +1 -1
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +247 -2
- package/dist/core.d.ts +247 -2
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/doctor-cli.d.ts +34 -0
- package/dist/doctor-cli.js +3 -0
- package/dist/doctor-cli.js.map +1 -0
- package/dist/doctor.d.ts +37 -0
- package/dist/doctor.js +2 -0
- package/dist/doctor.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -2
- package/dist/index.d.ts +247 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{middleware-CkSeLZ-9.d.ts → middleware-DD4Hwomt.d.ts} +6 -1
- package/dist/probe-tMHdim5y.d.ts +190 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -141,7 +141,31 @@ import RichText from 'payload-live-preview/astro/RichText.astro';
|
|
|
141
141
|
<RichText value={page.body} field="body" class="prose" />
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
**Injection modes.**
|
|
144
|
+
**Injection modes.** Three, and for a statically built site the default is rarely the best one.
|
|
145
|
+
|
|
146
|
+
`mode: 'inline'` (the default) bakes the runtime into every page at build time. Simple, and it works without a server — but every ordinary visitor downloads and parses ~21 KB gzip for a feature only an editor uses.
|
|
147
|
+
|
|
148
|
+
`mode: 'loader'` is the same deal without that cost. The page carries a few hundred bytes that check the preview context and fetch the runtime as a content-hashed, SRI-verified asset **only inside a preview**. Measured on the Astro fixture in this repository:
|
|
149
|
+
|
|
150
|
+
| | `index.html` |
|
|
151
|
+
| ---------------- | --------------- |
|
|
152
|
+
| `mode: 'inline'` | 70 314 bytes |
|
|
153
|
+
| `mode: 'loader'` | **3 151 bytes** |
|
|
154
|
+
|
|
155
|
+
That is per page, so a hundred-page site saves it a hundred times. The asset is published once at `/_payload-live-preview/runtime.<hash>.js`, cached across every page, and — because the configuration stays in the inline bootstrap — byte identical for every site on this version. It therefore cannot carry a deployment secret, and a redeploy that did not change the runtime does not invalidate the cached copy. `astro dev` serves the same path from memory, so development and production behave the same.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
livePreview({
|
|
159
|
+
mode: 'loader',
|
|
160
|
+
allowedOrigins: [import.meta.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN],
|
|
161
|
+
}),
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The trade is one extra request the first time an editor opens a preview.
|
|
165
|
+
|
|
166
|
+
Under a strict CSP, the asset itself is unremarkable: a same-origin script with an `integrity` attribute, needing no `'unsafe-inline'`. The bootstrap does need covering, though — a static build has no request, so there is no per-request nonce to attach and Astro emits it as a plain inline `<script>`. Its content is deterministic for a given package version and configuration, so the practical answer is a `'sha256-…'` source expression rather than `'unsafe-inline'`. `mode: 'middleware'` is the option that can carry a real nonce, because there a request exists to derive one from.
|
|
167
|
+
|
|
168
|
+
For SSR projects (`output: 'server'`), request-time injection can limit the bytes to requests carrying preview intent:
|
|
145
169
|
|
|
146
170
|
```ts
|
|
147
171
|
livePreview({
|
|
@@ -567,9 +591,119 @@ Together these span the whole spectrum: tier 1 proves the real thing works end t
|
|
|
567
591
|
|
|
568
592
|
Full details in [docs/security.md](docs/security.md). Report vulnerabilities per [SECURITY.md](SECURITY.md).
|
|
569
593
|
|
|
594
|
+
## Inspecting a running preview
|
|
595
|
+
|
|
596
|
+
When a preview misbehaves, `inspect()` returns a point-in-time snapshot of what
|
|
597
|
+
the runtime actually sees. It performs no I/O and transmits nothing.
|
|
598
|
+
|
|
599
|
+
Adapter users reach it on the global handle inside the preview iframe — that is
|
|
600
|
+
the point, because there is no client object to call a method on:
|
|
601
|
+
|
|
602
|
+
```js
|
|
603
|
+
// In the browser console, inside the preview iframe
|
|
604
|
+
__livePreview.inspect();
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Consumers driving the runtime themselves call it on the client:
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
const client = initLivePreview({ allowedOrigins: ['https://cms.example.com'] });
|
|
611
|
+
console.log(client.inspect());
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
The snapshot answers the questions that are otherwise guesswork:
|
|
615
|
+
|
|
616
|
+
| Reading | What it tells you |
|
|
617
|
+
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
618
|
+
| `bindings.orphanFields` | Field names that arrived but matched no element. A name here is a markup problem; a name in neither this nor `bindings.fieldNames` was never sent. |
|
|
619
|
+
| `scheduler.deferred` with `scheduler.visibilityGateActive` | Updates the visibility gate is holding until the element scrolls into view. On a page nobody scrolls, that is "never" — the symptom is a preview that stops updating below the fold. |
|
|
620
|
+
| `revisions.superseded` | Updates abandoned because a newer one arrived. Tracking `accepted` closely is normal for fast typing; it only matters when the _last_ update is among them. |
|
|
621
|
+
| `origins.locked` | The origin the runtime locked onto after its first accepted update. Every other origin is refused from then on. |
|
|
622
|
+
| `bindings.ownerScoped` with `bindings.owners` | Whether owner scoping is on, and which documents the page declares. Under scoping, an unowned binding receives nothing. |
|
|
623
|
+
| `protocol.negotiated` | The version both sides share, which caps the capabilities in `protocol.capabilities`. |
|
|
624
|
+
|
|
625
|
+
This is deliberately not gated to development builds. A snapshot discloses
|
|
626
|
+
nothing that is not already on the page — the trusted origins are inside the
|
|
627
|
+
injected script and the field names are `data-payload-field` attributes in the
|
|
628
|
+
DOM — and a preview that misbehaves only on the deployed site is exactly the
|
|
629
|
+
case where the information is worth having.
|
|
630
|
+
|
|
631
|
+
## Auditing a deployment: `pll doctor`
|
|
632
|
+
|
|
633
|
+
`inspect()` answers "what is this runtime doing right now" from inside the page.
|
|
634
|
+
`pll doctor` answers the question one step earlier: **what is this deployment
|
|
635
|
+
actually serving?**
|
|
636
|
+
|
|
637
|
+
```
|
|
638
|
+
npx pll doctor https://example.com/some-page --admin https://cms.example.com
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
It fetches the URL twice — once as an ordinary visitor, once with the headers
|
|
642
|
+
the admin's iframe sends — and reports the difference. That comparison is the
|
|
643
|
+
point: a config can say `allowedOrigins: [...]` while a proxy strips the
|
|
644
|
+
header, an adapter runs in a mode nobody remembers choosing, or a build emits
|
|
645
|
+
binding attributes on public pages.
|
|
646
|
+
|
|
647
|
+
What it checks:
|
|
648
|
+
|
|
649
|
+
| Code | Finding |
|
|
650
|
+
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
651
|
+
| `LP0701` | No inline runtime in the preview response. A warning, not an error: the audit cannot tell an adapter that missed the request from a consumer who starts `LivePreviewClient` themselves. Also reports, as information, when the runtime reaches anonymous visitors (correct for `inject: 'always'`). |
|
|
652
|
+
| `LP0702` | No `frame-ancestors` on the preview response, or a policy that does not admit `--admin`. A bare `'self'` counts as admitting it when admin and site share an origin. |
|
|
653
|
+
| `LP0703` | `X-Frame-Options` forbids framing — browsers honour it independently of CSP. `SAMEORIGIN` is accepted when `--admin` shares the page's origin; `DENY` never is. |
|
|
654
|
+
| `LP0704` | Binding attributes served to anonymous visitors. |
|
|
655
|
+
| `LP0705` | More bindings than the default `visibilityGateThreshold` writes eagerly. |
|
|
656
|
+
| `LP0706` | Bindings outside every owner marker, which receive nothing under `scopeBindingsByOwner`. |
|
|
657
|
+
| `LP0707` | The runtime is present with nothing to write into. |
|
|
658
|
+
|
|
659
|
+
Exit codes: `0` no error-level findings, `1` usage error or the URL could not be
|
|
660
|
+
fetched, `2` at least one error-level finding — so it drops into CI as a smoke
|
|
661
|
+
test against a deploy preview. `--json` emits the report as data.
|
|
662
|
+
|
|
663
|
+
The audit makes exactly the two requests it is told to make, sends no
|
|
664
|
+
credentials, and reports no telemetry. `analyzeProbe()` is exported from
|
|
665
|
+
`payload-live-preview/doctor` for callers who fetch the responses themselves.
|
|
666
|
+
|
|
667
|
+
## Diagnostic codes
|
|
668
|
+
|
|
669
|
+
Every message the runtime reports carries a stable code. Prose gets reworded; a
|
|
670
|
+
code does not — so a log filter, an alert rule, or a bug report that names
|
|
671
|
+
`LP0301` still means the same thing after the sentence around it changes.
|
|
672
|
+
|
|
673
|
+
| Code | Meaning | What to do |
|
|
674
|
+
| -------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
675
|
+
| `LP0101` | No trusted origin configured in production | Set `PAYLOAD_ADMIN_ORIGIN` or pass `allowedOrigins`. Nothing is accepted until you do. |
|
|
676
|
+
| `LP0102` | Origin trust rests on `document.referrer` | Any framing site is trusted. Set `allowedOrigins` and a `frame-ancestors` CSP. |
|
|
677
|
+
| `LP0201` | An update named a field with no binding on the page | Render the binding anchor unconditionally so edits to an initially-empty field have somewhere to land. |
|
|
678
|
+
| `LP0202` | Owner scoping is on and the update names no document | The message carries neither a global slug nor a collection slug plus id. |
|
|
679
|
+
| `LP0301` | The visibility gate held offscreen writes back | Raise `visibilityGateThreshold`, or accept that below-the-fold updates wait for a scroll. |
|
|
680
|
+
| `LP0401` | A value was refused as an unsafe attribute write | The attribute or the value is not safe to write; see the security model. |
|
|
681
|
+
| `LP0402` | A text element has structured children | Move `data-payload-field` to the value element, or add `data-payload-text`. |
|
|
682
|
+
| `LP0403` | A structural container has no array template | Add `data-payload-array-template`. |
|
|
683
|
+
| `LP0501` | A message was rejected before the update pipeline | Reason is one of origin, shape, type, token. Visible with `debug: true`. |
|
|
684
|
+
| `LP0502` | A preview token was rejected | Also reported when your `validateToken` throws — a throwing validator fails closed. |
|
|
685
|
+
| `LP0601` | A consumer event handler threw | Your `on(...)` handler; the runtime continued. |
|
|
686
|
+
| `LP0602` | A consumer transform threw | The original value was kept. |
|
|
687
|
+
| `LP0603` | A renderer threw while writing | That one write was abandoned. |
|
|
688
|
+
| `LP0605` | Runtime startup failed | |
|
|
689
|
+
| `LP0606` | Sending the ready handshake failed | |
|
|
690
|
+
|
|
691
|
+
Codes on the `error` event can be branched on directly:
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
import { DIAGNOSTIC_CODES } from 'payload-live-preview';
|
|
695
|
+
|
|
696
|
+
client.events.on('error', (e) => {
|
|
697
|
+
if (e.code === DIAGNOSTIC_CODES.TransformThrew) reportToSentry(e.error);
|
|
698
|
+
});
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
Codes are never reused for a different meaning and never renumbered. `LP0604`
|
|
702
|
+
is reserved and unassigned.
|
|
703
|
+
|
|
570
704
|
## Troubleshooting
|
|
571
705
|
|
|
572
|
-
- **Nothing updates** —
|
|
706
|
+
- **Nothing updates** — call `__livePreview.inspect()` in the preview iframe's console first; it names the cause in most cases (see [Inspecting a running preview](#inspecting-a-running-preview)). `debug: true` adds verbose diagnostics on top. The most common causes: the admin origin is not in `allowedOrigins`; the page is not actually loaded in an iframe; the binding element does not exist (see the empty-field gotcha above — orphan-update warnings are always enabled and deduplicated per field).
|
|
573
707
|
- **Relationship fields show IDs** — set `serverURL` (Payload 3.x sends unpopulated form values).
|
|
574
708
|
- **`Referrer-Policy: no-referrer`** on the admin breaks zero-config origin detection — set `allowedOrigins` explicitly.
|
|
575
709
|
- **Preview iframe refuses to load** — your host sets `X-Frame-Options` or a restrictive `frame-ancestors`. The adapters' CSP management overrides `frame-ancestors` on intent-matched responses, but `X-Frame-Options: DENY` from a proxy must be removed for authorized preview responses.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LivePreviewAstroOptions } from '../../middleware-
|
|
2
|
-
export { a as LivePreviewMiddleware, N as NONCE_LOCALS_KEY, c as createLivePreviewMiddleware } from '../../middleware-
|
|
1
|
+
import { L as LivePreviewAstroOptions } from '../../middleware-DD4Hwomt.js';
|
|
2
|
+
export { a as LivePreviewMiddleware, N as NONCE_LOCALS_KEY, c as createLivePreviewMiddleware } from '../../middleware-DD4Hwomt.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Astro integration for Payload Live Preview.
|
|
@@ -36,8 +36,32 @@ export { a as LivePreviewMiddleware, N as NONCE_LOCALS_KEY, c as createLivePrevi
|
|
|
36
36
|
type ScriptStage = 'head-inline' | 'page' | 'before-hydration' | 'page-ssr';
|
|
37
37
|
interface VitePluginLike {
|
|
38
38
|
readonly name: string;
|
|
39
|
-
readonly resolveId
|
|
40
|
-
readonly load
|
|
39
|
+
readonly resolveId?: (id: string) => string | undefined;
|
|
40
|
+
readonly load?: (id: string) => string | undefined;
|
|
41
|
+
readonly configureServer?: (server: ViteDevServerLike) => void;
|
|
42
|
+
readonly generateBundle?: (this: RollupEmitContext) => void;
|
|
43
|
+
}
|
|
44
|
+
/** The one Rollup facility this needs: publishing a file into the output. */
|
|
45
|
+
interface RollupEmitContext {
|
|
46
|
+
emitFile: (file: {
|
|
47
|
+
type: 'asset';
|
|
48
|
+
fileName: string;
|
|
49
|
+
source: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
}
|
|
52
|
+
/** Just enough of Vite's dev server to serve one file. */
|
|
53
|
+
interface ViteDevServerLike {
|
|
54
|
+
readonly middlewares: {
|
|
55
|
+
use: (handler: (req: DevRequest, res: DevResponse, next: () => void) => void) => void;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
interface DevRequest {
|
|
59
|
+
readonly url?: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
interface DevResponse {
|
|
62
|
+
statusCode: number;
|
|
63
|
+
setHeader: (name: string, value: string) => void;
|
|
64
|
+
end: (body?: string) => void;
|
|
41
65
|
}
|
|
42
66
|
interface AstroConfigSetupContext {
|
|
43
67
|
readonly injectScript: (stage: ScriptStage, content: string) => void;
|
|
@@ -50,6 +74,10 @@ interface AstroConfigSetupContext {
|
|
|
50
74
|
plugins?: VitePluginLike[];
|
|
51
75
|
};
|
|
52
76
|
}) => void;
|
|
77
|
+
/** Astro's configured `base`; absent on versions that do not expose it. */
|
|
78
|
+
readonly config?: {
|
|
79
|
+
readonly base?: string;
|
|
80
|
+
};
|
|
53
81
|
}
|
|
54
82
|
interface AstroIntegrationLike {
|
|
55
83
|
readonly name: string;
|