@forumone/throughline-publishing 0.4.0 → 0.5.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/CHANGELOG.md +14 -0
- package/README.md +4 -1
- package/dist/admin/PublishButton.d.ts.map +1 -1
- package/dist/admin/PublishButton.js +36 -3
- package/dist/admin/PublishButton.js.map +1 -1
- package/dist/admin/publishing-client.d.ts +46 -1
- package/dist/admin/publishing-client.d.ts.map +1 -1
- package/dist/admin/publishing-client.js +72 -1
- package/dist/admin/publishing-client.js.map +1 -1
- package/dist/pipeline/steps/execute.d.ts +9 -0
- package/dist/pipeline/steps/execute.d.ts.map +1 -1
- package/dist/pipeline/steps/execute.js +68 -12
- package/dist/pipeline/steps/execute.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @forumone/throughline-publishing
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 43c0636: Show a blocked publish on the fields that blocked it, and stop announcing the draft save
|
|
8
|
+
|
|
9
|
+
Every publish diagnostic arrived as a toast in the corner, which left an editor reading `layout.7.image` and counting blocks. The pipeline's issues have always named their fields; they are now dispatched into form state, so the field carries the message and the collapsed block row containing it carries an error count. An issue with no field — an embargo, a missing approval — stays in the toast, which still lists everything.
|
|
10
|
+
|
|
11
|
+
Only fields the form actually has are marked, and an issue that names something deeper (a populated relationship's image, a block index) marks the nearest field that owns it. Payload's reducer creates a field state entry for any path it is handed, so an invented path would become invented data on the next save.
|
|
12
|
+
|
|
13
|
+
A field the collection itself refuses is now a failed step rather than a thrown error: `failedAt: 'execute'`, `code: 'field-validation-failed'`, with Payload's field paths as `issues`. The publishing write is the first step that enforces `required` — a draft write deliberately does not — so an empty required field inside a block is caught there and nowhere earlier. `publishDocument` and the `publish` MCP tool now return that as a result instead of throwing.
|
|
14
|
+
|
|
15
|
+
The interim draft save the Publish button performs no longer shows its own success toast. It is a step inside publishing rather than something the editor asked for, and its notice used to land on top of the publish one. Payload's own Save Draft button is untouched.
|
|
16
|
+
|
|
3
17
|
## 0.4.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -52,7 +52,8 @@ What you get:
|
|
|
52
52
|
- **No API key in the editorial publish path.** The endpoint authenticates off the Payload session cookie.
|
|
53
53
|
- **The person is the actor.** The audit event records the logged-in editor, with `mcpTool` set to `admin:publish` so admin publishes are distinguishable from MCP ones.
|
|
54
54
|
- **Access control still applies.** The write runs with `overrideAccess: false` as that user. Bypassing the status hook is not bypassing permissions.
|
|
55
|
-
- **Real feedback.** A blocked publish renders the failing step, its issues, and its suggestion —
|
|
55
|
+
- **Real feedback, on the field that caused it.** A blocked publish renders the failing step, its issues, and its suggestion — and every issue naming a field is marked on that field, with an error count on the collapsed block row containing it. An issue with no field (an embargo, a missing approval) stays in the toast, which is where the full list still appears.
|
|
56
|
+
- **One notice per action.** The interim draft save the button performs does not announce itself; publishing says "published" once.
|
|
56
57
|
|
|
57
58
|
The Publish button is hidden on the create view: the pipeline's first step is `exist`, so there is nothing to evaluate until the draft is saved. Use Payload's Save Draft button, then publish from the edit view.
|
|
58
59
|
|
|
@@ -74,6 +75,8 @@ With `adminComponents: false` the admin has **no** working publish path until yo
|
|
|
74
75
|
|
|
75
76
|
A publish blocked by the pipeline returns **200** with `{ published: false, failedAt, reason, code, issues, suggestion }`. The pipeline ran correctly and the answer was no; that is not a transport error. Non-2xx is reserved for auth (401/403), bad input (400), and genuine failures (500).
|
|
76
77
|
|
|
78
|
+
A field the collection itself refuses is one of those blocks — `failedAt: 'execute'`, `code: 'field-validation-failed'`, with Payload's own field paths as `issues`. The publishing write is the first step that enforces `required`, because a draft write deliberately does not, so an empty required field inside a block is caught there and nowhere earlier.
|
|
79
|
+
|
|
77
80
|
## Publishing from host code
|
|
78
81
|
|
|
79
82
|
`runPublishPipeline` needs plugin options and an audit writer that only exist inside the plugin. Rather than export the raw pipeline, the plugin attaches a service to the Payload instance at `onInit` and exposes these helpers:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PublishButton.d.ts","sourceRoot":"","sources":["../../src/admin/PublishButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgC,MAAM,OAAO,CAAA;AAYpD,MAAM,WAAW,6BAA6B;IAC5C,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,6BAAkC,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"PublishButton.d.ts","sourceRoot":"","sources":["../../src/admin/PublishButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAgC,MAAM,OAAO,CAAA;AAYpD,MAAM,WAAW,6BAA6B;IAC5C,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAE,6BAAkC,GAAG,KAAK,CAAC,SAAS,CA2KxF"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useState } from 'react';
|
|
4
4
|
import { FormSubmit, toast, useConfig, useDocumentInfo, useForm, useFormModified, useTranslation, } from '@payloadcms/ui';
|
|
5
|
-
import { callPublishingEndpoint, describeBlock } from './publishing-client.js';
|
|
5
|
+
import { callPublishingEndpoint, describeBlock, fieldErrorsFromBlock } from './publishing-client.js';
|
|
6
6
|
/**
|
|
7
7
|
* Replaces Payload's Publish button on collections the publishing plugin
|
|
8
8
|
* governs.
|
|
@@ -22,7 +22,7 @@ export function PublishButton(props = {}) {
|
|
|
22
22
|
const routePrefix = props.routePrefix ?? '/publishing';
|
|
23
23
|
const { collectionSlug, hasPublishedDoc, hasPublishPermission, id, setHasPublishedDoc, setMostRecentVersionIsAutosaved, setUnpublishedVersionCount, unpublishedVersionCount, uploadStatus, } = useDocumentInfo();
|
|
24
24
|
const { config } = useConfig();
|
|
25
|
-
const { submit } = useForm();
|
|
25
|
+
const { dispatchFields, getFields, setIsValid, setSubmitted, submit } = useForm();
|
|
26
26
|
const modified = useFormModified();
|
|
27
27
|
const { t } = useTranslation();
|
|
28
28
|
const [publishing, setPublishing] = useState(false);
|
|
@@ -47,6 +47,12 @@ export function PublishButton(props = {}) {
|
|
|
47
47
|
method: 'PATCH',
|
|
48
48
|
overrides: { _status: 'draft' },
|
|
49
49
|
skipValidation: true,
|
|
50
|
+
// No toast for this write. It is a step inside publishing, not
|
|
51
|
+
// something the editor asked for, and its success toast used to
|
|
52
|
+
// land on top of the publish one — two overlapping notices for one
|
|
53
|
+
// action, the first of which announced the lesser outcome.
|
|
54
|
+
// Payload's own Save Draft button is untouched and still toasts.
|
|
55
|
+
disableSuccessStatus: true,
|
|
50
56
|
});
|
|
51
57
|
// `submit` returns void and toasts its own error on most failures,
|
|
52
58
|
// but falls through with a non-ok response when the body carries no
|
|
@@ -67,7 +73,30 @@ export function PublishButton(props = {}) {
|
|
|
67
73
|
return;
|
|
68
74
|
}
|
|
69
75
|
if (!result.body.published) {
|
|
70
|
-
|
|
76
|
+
/*
|
|
77
|
+
The pipeline's issues already name their fields; until now they only
|
|
78
|
+
reached a toast, which left the editor reading `layout.7.image` and
|
|
79
|
+
counting blocks. Dispatched into form state they render where the
|
|
80
|
+
problem is — on the field, and as an error count on the collapsed
|
|
81
|
+
block row containing it.
|
|
82
|
+
|
|
83
|
+
`setSubmitted` is what makes them visible: a field shows its error
|
|
84
|
+
only once the form has been submitted, and a publish with no pending
|
|
85
|
+
edits never submits the form at all. `setIsValid(false)` mirrors what
|
|
86
|
+
Payload does with the field errors from an ordinary save.
|
|
87
|
+
|
|
88
|
+
The toast still lists everything, because an issue with no field — an
|
|
89
|
+
embargo, a missing approval — has nowhere else to go.
|
|
90
|
+
*/
|
|
91
|
+
const fieldErrors = fieldErrorsFromBlock(result.body, Object.keys(getFields()));
|
|
92
|
+
if (fieldErrors.length > 0) {
|
|
93
|
+
dispatchFields({ type: 'ADD_SERVER_ERRORS', errors: fieldErrors });
|
|
94
|
+
setIsValid(false);
|
|
95
|
+
setSubmitted(true);
|
|
96
|
+
}
|
|
97
|
+
const { title, description } = describeBlock(result.body, {
|
|
98
|
+
markedFields: fieldErrors.length,
|
|
99
|
+
});
|
|
71
100
|
toast.error(title, {
|
|
72
101
|
...(description ? { description } : {}),
|
|
73
102
|
duration: 10_000,
|
|
@@ -105,13 +134,17 @@ export function PublishButton(props = {}) {
|
|
|
105
134
|
}, [
|
|
106
135
|
api,
|
|
107
136
|
collectionSlug,
|
|
137
|
+
dispatchFields,
|
|
138
|
+
getFields,
|
|
108
139
|
id,
|
|
109
140
|
modified,
|
|
110
141
|
publishing,
|
|
111
142
|
routePrefix,
|
|
112
143
|
serverURL,
|
|
113
144
|
setHasPublishedDoc,
|
|
145
|
+
setIsValid,
|
|
114
146
|
setMostRecentVersionIsAutosaved,
|
|
147
|
+
setSubmitted,
|
|
115
148
|
setUnpublishedVersionCount,
|
|
116
149
|
submit,
|
|
117
150
|
t,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PublishButton.js","sourceRoot":"","sources":["../../src/admin/PublishButton.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACpD,OAAO,EACL,UAAU,EACV,KAAK,EACL,SAAS,EACT,eAAe,EACf,OAAO,EACP,eAAe,EACf,cAAc,GACf,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"PublishButton.js","sourceRoot":"","sources":["../../src/admin/PublishButton.tsx"],"names":[],"mappings":"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"}
|
|
@@ -42,13 +42,58 @@ export type PublishingCallResult = {
|
|
|
42
42
|
* back as `ok: true` with a `published: false` body.
|
|
43
43
|
*/
|
|
44
44
|
export declare function callPublishingEndpoint(args: CallPublishingEndpointArgs): Promise<PublishingCallResult>;
|
|
45
|
+
/**
|
|
46
|
+
* A field error in the shape Payload's form reducer accepts. `path` is a
|
|
47
|
+
* *form state* path — dotted, with numeric segments for array and block rows —
|
|
48
|
+
* which is not the same dialect the pipeline's issues speak.
|
|
49
|
+
*
|
|
50
|
+
* Structurally `ValidationFieldError` from `payload`, redeclared so this file
|
|
51
|
+
* needs no import of its own and stays unit-testable without a form.
|
|
52
|
+
*/
|
|
53
|
+
export interface PublishingFieldError {
|
|
54
|
+
path: string;
|
|
55
|
+
message: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Rewrites a pipeline issue's field path into a form state path.
|
|
59
|
+
*
|
|
60
|
+
* The checks address array members with brackets — `layout[2].image` — because
|
|
61
|
+
* they walk a document. Payload's form state keys the same field
|
|
62
|
+
* `layout.2.image`. One is not a prefix of the other, so an unrewritten path
|
|
63
|
+
* matches no field and the error would land nowhere.
|
|
64
|
+
*/
|
|
65
|
+
export declare function toFormPath(field: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Turns a pipeline block into errors attached to the fields that caused it.
|
|
68
|
+
*
|
|
69
|
+
* `formPaths` is what the form actually has — `Object.keys(getFields())` at the
|
|
70
|
+
* call site. Only those paths are used, and that restriction is load-bearing
|
|
71
|
+
* rather than defensive: Payload's reducer *creates* a field state entry for
|
|
72
|
+
* any path it is handed, and an invented entry becomes invented data on the
|
|
73
|
+
* next save. `speakers.0.portrait` is a real issue path — the pipeline walks
|
|
74
|
+
* the document at depth, so a related person's portrait is in the tree — and
|
|
75
|
+
* writing it into form state would turn a relationship's list of ids into a
|
|
76
|
+
* list of objects.
|
|
77
|
+
*
|
|
78
|
+
* An issue whose exact path is not a field falls back to the nearest ancestor
|
|
79
|
+
* that is, so a problem inside a populated relationship marks the relationship
|
|
80
|
+
* and a block-level composition failure marks the block field. That is also
|
|
81
|
+
* what puts the error count on a *collapsed* block row: the reducer propagates
|
|
82
|
+
* every error path up to its parents.
|
|
83
|
+
*
|
|
84
|
+
* Anything that resolves to no field at all is left out and stays in the toast,
|
|
85
|
+
* which is the whole reason the toast keeps listing the issues.
|
|
86
|
+
*/
|
|
87
|
+
export declare function fieldErrorsFromBlock(body: PublishingResponse, formPaths: Iterable<string>): PublishingFieldError[];
|
|
45
88
|
/**
|
|
46
89
|
* Turns a pipeline block into something an editor can act on: which step
|
|
47
90
|
* said no, what it objected to, and what to do about it. This is the
|
|
48
91
|
* information the pipeline already returns — it just never had a way to
|
|
49
92
|
* reach the screen.
|
|
50
93
|
*/
|
|
51
|
-
export declare function describeBlock(body: PublishingResponse
|
|
94
|
+
export declare function describeBlock(body: PublishingResponse, options?: {
|
|
95
|
+
markedFields?: number;
|
|
96
|
+
}): {
|
|
52
97
|
title: string;
|
|
53
98
|
description: string;
|
|
54
99
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publishing-client.d.ts","sourceRoot":"","sources":["../../src/admin/publishing-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,SAAS,GAAG,WAAW,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,GACtC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAElC;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CA8B/B;
|
|
1
|
+
{"version":3,"file":"publishing-client.d.ts","sourceRoot":"","sources":["../../src/admin/publishing-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,SAAS,GAAG,WAAW,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,GACtC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAElC;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,0BAA0B,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CA8B/B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,kBAAkB,EACxB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,GAC1B,oBAAoB,EAAE,CAgBxB;AAeD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,kBAAkB,EACxB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC;IACD,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACpB,CA4BA"}
|
|
@@ -36,6 +36,71 @@ export async function callPublishingEndpoint(args) {
|
|
|
36
36
|
}
|
|
37
37
|
return { ok: true, body };
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Rewrites a pipeline issue's field path into a form state path.
|
|
41
|
+
*
|
|
42
|
+
* The checks address array members with brackets — `layout[2].image` — because
|
|
43
|
+
* they walk a document. Payload's form state keys the same field
|
|
44
|
+
* `layout.2.image`. One is not a prefix of the other, so an unrewritten path
|
|
45
|
+
* matches no field and the error would land nowhere.
|
|
46
|
+
*/
|
|
47
|
+
export function toFormPath(field) {
|
|
48
|
+
return field
|
|
49
|
+
.replace(/\[(\d+)\]/g, '.$1')
|
|
50
|
+
.replace(/\.{2,}/g, '.')
|
|
51
|
+
.replace(/^\.|\.$/g, '');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Turns a pipeline block into errors attached to the fields that caused it.
|
|
55
|
+
*
|
|
56
|
+
* `formPaths` is what the form actually has — `Object.keys(getFields())` at the
|
|
57
|
+
* call site. Only those paths are used, and that restriction is load-bearing
|
|
58
|
+
* rather than defensive: Payload's reducer *creates* a field state entry for
|
|
59
|
+
* any path it is handed, and an invented entry becomes invented data on the
|
|
60
|
+
* next save. `speakers.0.portrait` is a real issue path — the pipeline walks
|
|
61
|
+
* the document at depth, so a related person's portrait is in the tree — and
|
|
62
|
+
* writing it into form state would turn a relationship's list of ids into a
|
|
63
|
+
* list of objects.
|
|
64
|
+
*
|
|
65
|
+
* An issue whose exact path is not a field falls back to the nearest ancestor
|
|
66
|
+
* that is, so a problem inside a populated relationship marks the relationship
|
|
67
|
+
* and a block-level composition failure marks the block field. That is also
|
|
68
|
+
* what puts the error count on a *collapsed* block row: the reducer propagates
|
|
69
|
+
* every error path up to its parents.
|
|
70
|
+
*
|
|
71
|
+
* Anything that resolves to no field at all is left out and stays in the toast,
|
|
72
|
+
* which is the whole reason the toast keeps listing the issues.
|
|
73
|
+
*/
|
|
74
|
+
export function fieldErrorsFromBlock(body, formPaths) {
|
|
75
|
+
const paths = formPaths instanceof Set ? formPaths : new Set(formPaths);
|
|
76
|
+
// Grouped, because several issues can resolve to one field — three bad
|
|
77
|
+
// blocks all land on `layout` — and the reducer keeps one message per path.
|
|
78
|
+
const byPath = new Map();
|
|
79
|
+
for (const issue of body.issues ?? []) {
|
|
80
|
+
if (!issue.field)
|
|
81
|
+
continue;
|
|
82
|
+
const resolved = resolveFieldPath(toFormPath(issue.field), paths);
|
|
83
|
+
if (!resolved)
|
|
84
|
+
continue;
|
|
85
|
+
const messages = byPath.get(resolved) ?? [];
|
|
86
|
+
if (!messages.includes(issue.message))
|
|
87
|
+
messages.push(issue.message);
|
|
88
|
+
byPath.set(resolved, messages);
|
|
89
|
+
}
|
|
90
|
+
return [...byPath].map(([path, messages]) => ({ path, message: messages.join('; ') }));
|
|
91
|
+
}
|
|
92
|
+
/** The path itself if the form has it, else its nearest ancestor that it does. */
|
|
93
|
+
function resolveFieldPath(path, formPaths) {
|
|
94
|
+
if (path === '' || path === '(root)')
|
|
95
|
+
return undefined;
|
|
96
|
+
const segments = path.split('.');
|
|
97
|
+
for (let end = segments.length; end > 0; end--) {
|
|
98
|
+
const candidate = segments.slice(0, end).join('.');
|
|
99
|
+
if (formPaths.has(candidate))
|
|
100
|
+
return candidate;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
39
104
|
const MAX_LISTED_ISSUES = 5;
|
|
40
105
|
/**
|
|
41
106
|
* Turns a pipeline block into something an editor can act on: which step
|
|
@@ -43,7 +108,7 @@ const MAX_LISTED_ISSUES = 5;
|
|
|
43
108
|
* information the pipeline already returns — it just never had a way to
|
|
44
109
|
* reach the screen.
|
|
45
110
|
*/
|
|
46
|
-
export function describeBlock(body) {
|
|
111
|
+
export function describeBlock(body, options = {}) {
|
|
47
112
|
const title = body.reason ?? `Publish blocked at the ${body.failedAt ?? 'policy'} check.`;
|
|
48
113
|
const lines = [];
|
|
49
114
|
if (body.failedAt && body.reason) {
|
|
@@ -59,6 +124,12 @@ export function describeBlock(body) {
|
|
|
59
124
|
if (body.suggestion) {
|
|
60
125
|
lines.push(`Suggestion: ${body.suggestion}`);
|
|
61
126
|
}
|
|
127
|
+
// Said only when it is true. An editor who has read a toast listing five
|
|
128
|
+
// paths still has to find them, and the answer — they are marked on the
|
|
129
|
+
// fields — is not something the toast otherwise reveals.
|
|
130
|
+
if ((options.markedFields ?? 0) > 0) {
|
|
131
|
+
lines.push('Fields with a problem are highlighted in the form.');
|
|
132
|
+
}
|
|
62
133
|
return { title, description: lines.join('\n') };
|
|
63
134
|
}
|
|
64
135
|
//# sourceMappingURL=publishing-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publishing-client.js","sourceRoot":"","sources":["../../src/admin/publishing-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoCH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAgC;IAEhC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,MAAM,EAAE,CAAA;IAEjF,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;SACnE,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,wCAAwC,EAAE,CAAA;IACzE,CAAC;IAED,IAAI,IAAwB,CAAA;IAC5B,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAA;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,8BAA8B,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAA;IACjF,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,IAAI,CAAC,KAAK,IAAI,8BAA8B,QAAQ,CAAC,MAAM,GAAG;SACxE,CAAA;IACH,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AAC3B,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAE3B;;;;;GAKG;AACH,MAAM,UAAU,aAAa,
|
|
1
|
+
{"version":3,"file":"publishing-client.js","sourceRoot":"","sources":["../../src/admin/publishing-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,6 +30,15 @@ import type { PipelineStep } from '../types.js';
|
|
|
30
30
|
* The event is emitted after the write and cannot fail the step: once
|
|
31
31
|
* `_status` is `published` the publish has happened, and reporting failure
|
|
32
32
|
* would send an editor back to re-publish live content.
|
|
33
|
+
*
|
|
34
|
+
* A field Payload refuses is a failed *step*, not a thrown error. The write is
|
|
35
|
+
* the first thing in the whole pipeline that enforces `required` — a draft
|
|
36
|
+
* write does not, which is the point of drafts — so an empty required field
|
|
37
|
+
* inside a block reaches here and nowhere earlier. Thrown, it left the
|
|
38
|
+
* transport to explain itself: the admin got a bare message and the MCP tool
|
|
39
|
+
* got an exception, when Payload had already said exactly which paths were
|
|
40
|
+
* wrong. Returned as issues, it travels the same road as every other block —
|
|
41
|
+
* onto the fields in the admin, into the tool's result, into the audit row.
|
|
33
42
|
*/
|
|
34
43
|
export declare const executeStep: PipelineStep;
|
|
35
44
|
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/execute.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/execute.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAA;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,WAAW,EAAE,YAoDzB,CAAA"}
|
|
@@ -30,6 +30,15 @@ import { sendEventSafely } from '../../events.js';
|
|
|
30
30
|
* The event is emitted after the write and cannot fail the step: once
|
|
31
31
|
* `_status` is `published` the publish has happened, and reporting failure
|
|
32
32
|
* would send an editor back to re-publish live content.
|
|
33
|
+
*
|
|
34
|
+
* A field Payload refuses is a failed *step*, not a thrown error. The write is
|
|
35
|
+
* the first thing in the whole pipeline that enforces `required` — a draft
|
|
36
|
+
* write does not, which is the point of drafts — so an empty required field
|
|
37
|
+
* inside a block reaches here and nowhere earlier. Thrown, it left the
|
|
38
|
+
* transport to explain itself: the admin got a bare message and the MCP tool
|
|
39
|
+
* got an exception, when Payload had already said exactly which paths were
|
|
40
|
+
* wrong. Returned as issues, it travels the same road as every other block —
|
|
41
|
+
* onto the fields in the admin, into the tool's result, into the audit row.
|
|
33
42
|
*/
|
|
34
43
|
export const executeStep = async (ctx) => {
|
|
35
44
|
const now = new Date().toISOString();
|
|
@@ -37,18 +46,35 @@ export const executeStep = async (ctx) => {
|
|
|
37
46
|
? ctx.document[ctx.collection.publishedAtField]
|
|
38
47
|
: null;
|
|
39
48
|
const wasFirstPublish = previousPublishedAt === null;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
try {
|
|
50
|
+
await ctx.payload.update({
|
|
51
|
+
collection: ctx.collection.slug,
|
|
52
|
+
id: ctx.documentId,
|
|
53
|
+
data: {
|
|
54
|
+
_status: 'published',
|
|
55
|
+
...(wasFirstPublish ? { [ctx.collection.publishedAtField]: now } : {}),
|
|
56
|
+
},
|
|
57
|
+
...(ctx.actor.enforceAccessAs
|
|
58
|
+
? { user: ctx.actor.enforceAccessAs, overrideAccess: false }
|
|
59
|
+
: {}),
|
|
60
|
+
context: { bypassPublishingServer: true },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const issues = fieldIssues(error);
|
|
65
|
+
// Only a field rejection is an answer. A database or access failure is
|
|
66
|
+
// not, and rethrowing keeps it out of the diagnostics an editor reads as
|
|
67
|
+
// "fix these fields".
|
|
68
|
+
if (!issues)
|
|
69
|
+
throw error;
|
|
70
|
+
return {
|
|
71
|
+
pass: false,
|
|
72
|
+
code: 'field-validation-failed',
|
|
73
|
+
reason: `${issues.length} field${issues.length === 1 ? '' : 's'} the collection will not accept`,
|
|
74
|
+
issues,
|
|
75
|
+
suggestion: 'These are required by the collection rather than by a policy check, so they must be filled before this document can be published.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
52
78
|
// The write has landed. From here the publish has happened, so nothing
|
|
53
79
|
// below may turn it back into a failure.
|
|
54
80
|
const warning = await sendEventSafely(ctx.inngest, {
|
|
@@ -64,4 +90,34 @@ export const executeStep = async (ctx) => {
|
|
|
64
90
|
});
|
|
65
91
|
return { pass: true, ...(warning ? { warnings: [warning] } : {}) };
|
|
66
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Payload's `ValidationError` as issues, or `undefined` for anything else.
|
|
95
|
+
*
|
|
96
|
+
* Read structurally rather than with `instanceof`. The class would work in the
|
|
97
|
+
* ordinary install and fail in the one that matters — two copies of `payload`
|
|
98
|
+
* resolved under one tree make `instanceof` false for an error that is one in
|
|
99
|
+
* every other respect, and the failure mode is a silent rethrow of something
|
|
100
|
+
* an editor could have fixed. The shape is stable: `data.errors` is a list of
|
|
101
|
+
* `{ path, message }`, and the paths are already dotted form paths.
|
|
102
|
+
*/
|
|
103
|
+
function fieldIssues(error) {
|
|
104
|
+
if (!error || typeof error !== 'object')
|
|
105
|
+
return undefined;
|
|
106
|
+
const data = error.data;
|
|
107
|
+
if (!data || typeof data !== 'object')
|
|
108
|
+
return undefined;
|
|
109
|
+
const errors = data.errors;
|
|
110
|
+
if (!Array.isArray(errors) || errors.length === 0)
|
|
111
|
+
return undefined;
|
|
112
|
+
const issues = [];
|
|
113
|
+
for (const entry of errors) {
|
|
114
|
+
if (!entry || typeof entry !== 'object')
|
|
115
|
+
return undefined;
|
|
116
|
+
const { path, message } = entry;
|
|
117
|
+
if (typeof path !== 'string' || typeof message !== 'string')
|
|
118
|
+
return undefined;
|
|
119
|
+
issues.push({ field: path, message, severity: 'error' });
|
|
120
|
+
}
|
|
121
|
+
return issues;
|
|
122
|
+
}
|
|
67
123
|
//# sourceMappingURL=execute.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/pipeline/steps/execute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAGjD
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/pipeline/steps/execute.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forumone/throughline-publishing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Policy-gated publishing server for Throughline. Wraps Payload's update operation with composition, accessibility, required-field, embargo, and approval gating.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|