@forumone/throughline-publishing 0.0.1 → 0.2.1
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 +13 -0
- package/LICENSE +21 -0
- package/README.md +2 -42
- package/dist/checks/alt-text.d.ts +11 -0
- package/dist/checks/alt-text.d.ts.map +1 -0
- package/dist/checks/alt-text.js +53 -0
- package/dist/checks/alt-text.js.map +1 -0
- package/dist/checks/heading-hierarchy.d.ts +11 -0
- package/dist/checks/heading-hierarchy.d.ts.map +1 -0
- package/dist/checks/heading-hierarchy.js +32 -0
- package/dist/checks/heading-hierarchy.js.map +1 -0
- package/dist/checks/index.d.ts +6 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +13 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/checks/link-labels.d.ts +9 -0
- package/dist/checks/link-labels.d.ts.map +1 -0
- package/dist/checks/link-labels.js +44 -0
- package/dist/checks/link-labels.js.map +1 -0
- package/dist/hooks/block-status-writes.d.ts +12 -0
- package/dist/hooks/block-status-writes.d.ts.map +1 -0
- package/dist/hooks/block-status-writes.js +36 -0
- package/dist/hooks/block-status-writes.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +65 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +57 -0
- package/dist/options.js.map +1 -0
- package/dist/pipeline/index.d.ts +15 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +62 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/steps/accessibility.d.ts +9 -0
- package/dist/pipeline/steps/accessibility.d.ts.map +1 -0
- package/dist/pipeline/steps/accessibility.js +32 -0
- package/dist/pipeline/steps/accessibility.js.map +1 -0
- package/dist/pipeline/steps/approval.d.ts +21 -0
- package/dist/pipeline/steps/approval.d.ts.map +1 -0
- package/dist/pipeline/steps/approval.js +48 -0
- package/dist/pipeline/steps/approval.js.map +1 -0
- package/dist/pipeline/steps/composition.d.ts +14 -0
- package/dist/pipeline/steps/composition.d.ts.map +1 -0
- package/dist/pipeline/steps/composition.js +63 -0
- package/dist/pipeline/steps/composition.js.map +1 -0
- package/dist/pipeline/steps/embargo.d.ts +12 -0
- package/dist/pipeline/steps/embargo.d.ts.map +1 -0
- package/dist/pipeline/steps/embargo.js +41 -0
- package/dist/pipeline/steps/embargo.js.map +1 -0
- package/dist/pipeline/steps/execute.d.ts +12 -0
- package/dist/pipeline/steps/execute.d.ts.map +1 -0
- package/dist/pipeline/steps/execute.js +38 -0
- package/dist/pipeline/steps/execute.js.map +1 -0
- package/dist/pipeline/steps/exist.d.ts +9 -0
- package/dist/pipeline/steps/exist.d.ts.map +1 -0
- package/dist/pipeline/steps/exist.js +31 -0
- package/dist/pipeline/steps/exist.js.map +1 -0
- package/dist/pipeline/steps/required-fields.d.ts +9 -0
- package/dist/pipeline/steps/required-fields.d.ts.map +1 -0
- package/dist/pipeline/steps/required-fields.js +63 -0
- package/dist/pipeline/steps/required-fields.js.map +1 -0
- package/dist/pipeline/types.d.ts +45 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/pipeline/types.js.map +1 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +87 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tools/get-publish-status.d.ts +9 -0
- package/dist/tools/get-publish-status.d.ts.map +1 -0
- package/dist/tools/get-publish-status.js +55 -0
- package/dist/tools/get-publish-status.js.map +1 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/publish.d.ts +11 -0
- package/dist/tools/publish.d.ts.map +1 -0
- package/dist/tools/publish.js +71 -0
- package/dist/tools/publish.js.map +1 -0
- package/dist/tools/rollback.d.ts +11 -0
- package/dist/tools/rollback.d.ts.map +1 -0
- package/dist/tools/rollback.js +78 -0
- package/dist/tools/rollback.js.map +1 -0
- package/dist/tools/schedule-publish.d.ts +11 -0
- package/dist/tools/schedule-publish.d.ts.map +1 -0
- package/dist/tools/schedule-publish.js +94 -0
- package/dist/tools/schedule-publish.js.map +1 -0
- package/dist/tools/unpublish.d.ts +11 -0
- package/dist/tools/unpublish.d.ts.map +1 -0
- package/dist/tools/unpublish.js +61 -0
- package/dist/tools/unpublish.js.map +1 -0
- package/package.json +66 -7
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BUILT_IN_ACCESSIBILITY_CHECKS } from '../../checks/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs every built-in accessibility check followed by user-supplied ones.
|
|
4
|
+
* Errors block publish; warnings are surfaced on the result but don't fail
|
|
5
|
+
* the step. (Warning-only behavior is currently unused by the built-in
|
|
6
|
+
* checks but reserved for richer per-client checks.)
|
|
7
|
+
*/
|
|
8
|
+
export const accessibilityStep = async (ctx) => {
|
|
9
|
+
const checks = [
|
|
10
|
+
...BUILT_IN_ACCESSIBILITY_CHECKS,
|
|
11
|
+
...(ctx.options.accessibilityChecks ?? []),
|
|
12
|
+
];
|
|
13
|
+
const allIssues = [];
|
|
14
|
+
for (const check of checks) {
|
|
15
|
+
const issues = await check.run(ctx.document, ctx.collection);
|
|
16
|
+
for (const issue of issues) {
|
|
17
|
+
allIssues.push({ ...issue, rule: check.name });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const errors = allIssues.filter((i) => i.severity === 'error');
|
|
21
|
+
if (errors.length > 0) {
|
|
22
|
+
return {
|
|
23
|
+
pass: false,
|
|
24
|
+
code: 'accessibility-errors',
|
|
25
|
+
reason: `${errors.length} accessibility issue${errors.length === 1 ? '' : 's'}`,
|
|
26
|
+
issues: errors,
|
|
27
|
+
suggestion: 'Every image needs alt text. Every link needs a label. Heading levels must not skip. Fix these before publishing.',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { pass: true };
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=accessibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../../src/pipeline/steps/accessibility.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAA;AAErE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IAC3D,MAAM,MAAM,GAAG;QACb,GAAG,6BAA6B;QAChC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,mBAAmB,IAAI,EAAE,CAAC;KAC3C,CAAA;IAED,MAAM,SAAS,GAAoB,EAAE,CAAA;IACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAA;QAC5D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;QAChD,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAA;IAC9D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,sBAAsB;YAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,uBAAuB,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;YAC/E,MAAM,EAAE,MAAM;YACd,UAAU,EACR,kHAAkH;SACrH,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PipelineStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Symbol the approvals plugin attaches its resolver under. Publishing's
|
|
4
|
+
* approval step looks here when no resolver is supplied via options, which
|
|
5
|
+
* lets clients add the approvals plugin without re-wiring publishing's
|
|
6
|
+
* config. Keep in sync with the matching constant in the approvals package.
|
|
7
|
+
*/
|
|
8
|
+
export declare const APPROVALS_RESOLVER_SYMBOL: unique symbol;
|
|
9
|
+
/**
|
|
10
|
+
* Gates publish on a granted approval when the document's policy demands
|
|
11
|
+
* one. Fails closed: if `policy.requiresApproval` is true but no resolver
|
|
12
|
+
* is available (neither in options nor attached via the approvals plugin),
|
|
13
|
+
* the step blocks the publish.
|
|
14
|
+
*
|
|
15
|
+
* Resolution order:
|
|
16
|
+
* 1. `options.approvalResolver` (explicit wiring)
|
|
17
|
+
* 2. The resolver attached on the Payload instance via
|
|
18
|
+
* `APPROVALS_RESOLVER_SYMBOL` by the approvals plugin
|
|
19
|
+
*/
|
|
20
|
+
export declare const approvalStep: PipelineStep;
|
|
21
|
+
//# sourceMappingURL=approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/approval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,eAErC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAE,YAoC1B,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol the approvals plugin attaches its resolver under. Publishing's
|
|
3
|
+
* approval step looks here when no resolver is supplied via options, which
|
|
4
|
+
* lets clients add the approvals plugin without re-wiring publishing's
|
|
5
|
+
* config. Keep in sync with the matching constant in the approvals package.
|
|
6
|
+
*/
|
|
7
|
+
export const APPROVALS_RESOLVER_SYMBOL = Symbol.for('@forumone/throughline/approvals-resolver');
|
|
8
|
+
/**
|
|
9
|
+
* Gates publish on a granted approval when the document's policy demands
|
|
10
|
+
* one. Fails closed: if `policy.requiresApproval` is true but no resolver
|
|
11
|
+
* is available (neither in options nor attached via the approvals plugin),
|
|
12
|
+
* the step blocks the publish.
|
|
13
|
+
*
|
|
14
|
+
* Resolution order:
|
|
15
|
+
* 1. `options.approvalResolver` (explicit wiring)
|
|
16
|
+
* 2. The resolver attached on the Payload instance via
|
|
17
|
+
* `APPROVALS_RESOLVER_SYMBOL` by the approvals plugin
|
|
18
|
+
*/
|
|
19
|
+
export const approvalStep = async (ctx) => {
|
|
20
|
+
const policy = ctx.document[ctx.collection.policyField];
|
|
21
|
+
if (!policy?.['requiresApproval'])
|
|
22
|
+
return { pass: true };
|
|
23
|
+
const resolver = ctx.options.approvalResolver ?? lookupResolverOnPayload(ctx.payload);
|
|
24
|
+
if (!resolver) {
|
|
25
|
+
return {
|
|
26
|
+
pass: false,
|
|
27
|
+
code: 'approval-resolver-missing',
|
|
28
|
+
reason: 'Document requires approval but no approval resolver is configured',
|
|
29
|
+
suggestion: 'Register approvalsPlugin in your Payload config (it attaches the resolver automatically) or pass an explicit `approvalResolver` to publishingPlugin.',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const versionId = String(ctx.document['updatedAt'] ?? ctx.documentId);
|
|
33
|
+
const approval = await resolver.getActiveApproval(ctx.collection.slug, ctx.documentId, versionId);
|
|
34
|
+
if (!approval) {
|
|
35
|
+
return {
|
|
36
|
+
pass: false,
|
|
37
|
+
code: 'approval-required',
|
|
38
|
+
reason: 'This document requires approval and no granted approval exists for the current version',
|
|
39
|
+
suggestion: 'Use the Approvals Server to request approval. Once granted, publish will succeed.',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return { pass: true };
|
|
43
|
+
};
|
|
44
|
+
function lookupResolverOnPayload(payload) {
|
|
45
|
+
const value = payload[APPROVALS_RESOLVER_SYMBOL];
|
|
46
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval.js","sourceRoot":"","sources":["../../../src/pipeline/steps/approval.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,GAAG,CACjD,0CAA0C,CAC3C,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IACtD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAEzC,CAAA;IACb,IAAI,CAAC,MAAM,EAAE,CAAC,kBAAkB,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAExD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,IAAI,uBAAuB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACrF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,2BAA2B;YACjC,MAAM,EAAE,mEAAmE;YAC3E,UAAU,EACR,sJAAsJ;SACzJ,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,CAAA;IACrE,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,iBAAiB,CAC/C,GAAG,CAAC,UAAU,CAAC,IAAI,EACnB,GAAG,CAAC,UAAU,EACd,SAAS,CACV,CAAA;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,mBAAmB;YACzB,MAAM,EACJ,wFAAwF;YAC1F,UAAU,EACR,mFAAmF;SACtF,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA;AAED,SAAS,uBAAuB,CAAC,OAAe;IAC9C,MAAM,KAAK,GAAI,OAAmC,CAAC,yBAAyB,CAAC,CAAA;IAC7E,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAE,KAA0B,CAAC,CAAC,CAAC,SAAS,CAAA;AAC9F,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PipelineStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Symbol the Component Server's plugin (C5) attaches its composition
|
|
4
|
+
* validator to. Keep in sync with the components package.
|
|
5
|
+
*/
|
|
6
|
+
export declare const COMPONENTS_VALIDATOR_SYMBOL: unique symbol;
|
|
7
|
+
/**
|
|
8
|
+
* Validates a document's layout blocks via the Component Server's validator
|
|
9
|
+
* (attached to the Payload instance by `componentsPlugin`'s `onInit`). Empty
|
|
10
|
+
* layouts pass automatically. Composition warnings don't block publish; only
|
|
11
|
+
* errors do.
|
|
12
|
+
*/
|
|
13
|
+
export declare const compositionStep: PipelineStep;
|
|
14
|
+
//# sourceMappingURL=composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/composition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,2BAA2B,eAEvC,CAAA;AAqBD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,YA2D7B,CAAA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol the Component Server's plugin (C5) attaches its composition
|
|
3
|
+
* validator to. Keep in sync with the components package.
|
|
4
|
+
*/
|
|
5
|
+
export const COMPONENTS_VALIDATOR_SYMBOL = Symbol.for('@forumone/throughline/components-validator');
|
|
6
|
+
/**
|
|
7
|
+
* Validates a document's layout blocks via the Component Server's validator
|
|
8
|
+
* (attached to the Payload instance by `componentsPlugin`'s `onInit`). Empty
|
|
9
|
+
* layouts pass automatically. Composition warnings don't block publish; only
|
|
10
|
+
* errors do.
|
|
11
|
+
*/
|
|
12
|
+
export const compositionStep = async (ctx) => {
|
|
13
|
+
const layoutValue = ctx.document[ctx.collection.layoutField];
|
|
14
|
+
if (!Array.isArray(layoutValue) || layoutValue.length === 0) {
|
|
15
|
+
return { pass: true };
|
|
16
|
+
}
|
|
17
|
+
const blocks = layoutValue
|
|
18
|
+
.map((block) => {
|
|
19
|
+
const type = block?.blockType;
|
|
20
|
+
const variant = block?.variant;
|
|
21
|
+
if (typeof type !== 'string')
|
|
22
|
+
return null;
|
|
23
|
+
return typeof variant === 'string'
|
|
24
|
+
? { type, variant }
|
|
25
|
+
: { type };
|
|
26
|
+
})
|
|
27
|
+
.filter((b) => b !== null);
|
|
28
|
+
if (blocks.length === 0)
|
|
29
|
+
return { pass: true };
|
|
30
|
+
const validator = ctx.payload[COMPONENTS_VALIDATOR_SYMBOL];
|
|
31
|
+
if (!validator) {
|
|
32
|
+
return {
|
|
33
|
+
pass: false,
|
|
34
|
+
code: 'components-server-missing',
|
|
35
|
+
reason: 'Composition step requires the components plugin to be registered before publishingPlugin',
|
|
36
|
+
suggestion: 'Register `componentsPlugin` in your Payload config before `publishingPlugin`. Both must share the same Payload instance.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const result = await validator({ blocks });
|
|
40
|
+
const errors = result.issues
|
|
41
|
+
.filter((issue) => issue.severity === 'error')
|
|
42
|
+
.map((issue) => {
|
|
43
|
+
const out = {
|
|
44
|
+
severity: 'error',
|
|
45
|
+
message: issue.message,
|
|
46
|
+
rule: issue.rule,
|
|
47
|
+
};
|
|
48
|
+
if (issue.blockIndex !== undefined)
|
|
49
|
+
out.field = `${ctx.collection.layoutField}[${issue.blockIndex}]`;
|
|
50
|
+
return out;
|
|
51
|
+
});
|
|
52
|
+
if (errors.length > 0) {
|
|
53
|
+
return {
|
|
54
|
+
pass: false,
|
|
55
|
+
code: 'composition-errors',
|
|
56
|
+
reason: `${errors.length} composition error${errors.length === 1 ? '' : 's'}`,
|
|
57
|
+
issues: errors,
|
|
58
|
+
suggestion: 'Fix the composition errors. Common causes: duplicate Heroes, forbidden adjacent blocks, or unknown component types.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { pass: true };
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.js","sourceRoot":"","sources":["../../../src/pipeline/steps/composition.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CACnD,4CAA4C,CAC7C,CAAA;AAqBD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IACzD,MAAM,WAAW,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;IAC5D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACvB,CAAC;IAED,MAAM,MAAM,GAAI,WAA6B;SAC1C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,IAAI,GAAG,KAAK,EAAE,SAAS,CAAA;QAC7B,MAAM,OAAO,GAAG,KAAK,EAAE,OAAO,CAAA;QAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QACzC,OAAO,OAAO,OAAO,KAAK,QAAQ;YAChC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;YACnB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAA2C,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;IAErE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAE9C,MAAM,SAAS,GAAI,GAAG,CAAC,OAA8C,CACnE,2BAA2B,CACM,CAAA;IAEnC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,2BAA2B;YACjC,MAAM,EACJ,0FAA0F;YAC5F,UAAU,EACR,0HAA0H;SAC7H,CAAA;IACH,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAoB,MAAM,CAAC,MAAM;SAC1C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC;SAC7C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,GAAG,GAAkB;YACzB,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,IAAI,EAAE,KAAK,CAAC,IAAI;SACjB,CAAA;QACD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;YAAE,GAAG,CAAC,KAAK,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,WAAW,IAAI,KAAK,CAAC,UAAU,GAAG,CAAA;QACpG,OAAO,GAAG,CAAA;IACZ,CAAC,CAAC,CAAA;IAEJ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,oBAAoB;YAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,qBAAqB,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;YAC7E,MAAM,EAAE,MAAM;YACd,UAAU,EACR,qHAAqH;SACxH,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PipelineStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Enforces the document's policy:
|
|
4
|
+
* - `embargoedUntil`: blocks publish until that time has passed
|
|
5
|
+
* - `expiresAt`: blocks publish if the document has already expired
|
|
6
|
+
*
|
|
7
|
+
* Both fields live under the configured `policyField` on the document.
|
|
8
|
+
* Missing or null values are treated as "no policy applies" — the step
|
|
9
|
+
* passes.
|
|
10
|
+
*/
|
|
11
|
+
export declare const embargoStep: PipelineStep;
|
|
12
|
+
//# sourceMappingURL=embargo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embargo.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/embargo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,YA6BzB,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforces the document's policy:
|
|
3
|
+
* - `embargoedUntil`: blocks publish until that time has passed
|
|
4
|
+
* - `expiresAt`: blocks publish if the document has already expired
|
|
5
|
+
*
|
|
6
|
+
* Both fields live under the configured `policyField` on the document.
|
|
7
|
+
* Missing or null values are treated as "no policy applies" — the step
|
|
8
|
+
* passes.
|
|
9
|
+
*/
|
|
10
|
+
export const embargoStep = async (ctx) => {
|
|
11
|
+
const policy = ctx.document[ctx.collection.policyField];
|
|
12
|
+
if (!policy)
|
|
13
|
+
return { pass: true };
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
const embargoedUntil = parseDate(policy['embargoedUntil']);
|
|
16
|
+
if (embargoedUntil !== null && embargoedUntil > now) {
|
|
17
|
+
return {
|
|
18
|
+
pass: false,
|
|
19
|
+
code: 'embargoed',
|
|
20
|
+
reason: `Embargoed until ${new Date(embargoedUntil).toISOString()}`,
|
|
21
|
+
suggestion: 'Wait until the embargo expires, update the embargoedUntil date, or schedule publish for after the embargo.',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const expiresAt = parseDate(policy['expiresAt']);
|
|
25
|
+
if (expiresAt !== null && expiresAt < now) {
|
|
26
|
+
return {
|
|
27
|
+
pass: false,
|
|
28
|
+
code: 'expired',
|
|
29
|
+
reason: `Content expired on ${new Date(expiresAt).toISOString()}`,
|
|
30
|
+
suggestion: 'Update the expiresAt date or unpublish this content.',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { pass: true };
|
|
34
|
+
};
|
|
35
|
+
function parseDate(value) {
|
|
36
|
+
if (typeof value !== 'string')
|
|
37
|
+
return null;
|
|
38
|
+
const ms = Date.parse(value);
|
|
39
|
+
return Number.isNaN(ms) ? null : ms;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=embargo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embargo.js","sourceRoot":"","sources":["../../../src/pipeline/steps/embargo.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IACrD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAEzC,CAAA;IACb,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAElC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACtB,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAC1D,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,GAAG,GAAG,EAAE,CAAC;QACpD,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,mBAAmB,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,EAAE;YACnE,UAAU,EACR,4GAA4G;SAC/G,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAA;IAChD,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,GAAG,GAAG,EAAE,CAAC;QAC1C,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,sBAAsB,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE;YACjE,UAAU,EAAE,sDAAsD;SACnE,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC1C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAC5B,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;AACrC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PipelineStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Performs the actual publish: writes `_status: 'published'` and updates
|
|
4
|
+
* the publishedAt field, then fires `content/page.published` with metadata
|
|
5
|
+
* subscribers can react to (revalidation, integrations, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Sets `context.bypassPublishingServer = true` on the Payload update so the
|
|
8
|
+
* status-write blocking hook recognizes this as a sanctioned write. Any
|
|
9
|
+
* other path that tries to flip `_status` will be rejected by the hook.
|
|
10
|
+
*/
|
|
11
|
+
export declare const executeStep: PipelineStep;
|
|
12
|
+
//# sourceMappingURL=execute.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/execute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,YA+BzB,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs the actual publish: writes `_status: 'published'` and updates
|
|
3
|
+
* the publishedAt field, then fires `content/page.published` with metadata
|
|
4
|
+
* subscribers can react to (revalidation, integrations, etc.).
|
|
5
|
+
*
|
|
6
|
+
* Sets `context.bypassPublishingServer = true` on the Payload update so the
|
|
7
|
+
* status-write blocking hook recognizes this as a sanctioned write. Any
|
|
8
|
+
* other path that tries to flip `_status` will be rejected by the hook.
|
|
9
|
+
*/
|
|
10
|
+
export const executeStep = async (ctx) => {
|
|
11
|
+
const now = new Date().toISOString();
|
|
12
|
+
const previousPublishedAt = typeof ctx.document[ctx.collection.publishedAtField] === 'string'
|
|
13
|
+
? ctx.document[ctx.collection.publishedAtField]
|
|
14
|
+
: null;
|
|
15
|
+
const wasFirstPublish = previousPublishedAt === null;
|
|
16
|
+
await ctx.payload.update({
|
|
17
|
+
collection: ctx.collection.slug,
|
|
18
|
+
id: ctx.documentId,
|
|
19
|
+
data: {
|
|
20
|
+
_status: 'published',
|
|
21
|
+
[ctx.collection.publishedAtField]: now,
|
|
22
|
+
},
|
|
23
|
+
context: { bypassPublishingServer: true },
|
|
24
|
+
});
|
|
25
|
+
await ctx.inngest.send({
|
|
26
|
+
name: 'content/page.published',
|
|
27
|
+
data: {
|
|
28
|
+
collection: ctx.collection.slug,
|
|
29
|
+
id: ctx.documentId,
|
|
30
|
+
slug: String(ctx.document[ctx.collection.slugField] ?? ctx.documentId),
|
|
31
|
+
publishedBy: ctx.actor.user?.id ?? 'system',
|
|
32
|
+
previousPublishedAt,
|
|
33
|
+
isFirstPublish: wasFirstPublish,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return { pass: true };
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=execute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execute.js","sourceRoot":"","sources":["../../../src/pipeline/steps/execute.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IACrD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACpC,MAAM,mBAAmB,GACvB,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,KAAK,QAAQ;QAC/D,CAAC,CAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAY;QAC3D,CAAC,CAAC,IAAI,CAAA;IACV,MAAM,eAAe,GAAG,mBAAmB,KAAK,IAAI,CAAA;IAEpD,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;QACvB,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI;QAC/B,EAAE,EAAE,GAAG,CAAC,UAAU;QAClB,IAAI,EAAE;YACJ,OAAO,EAAE,WAAW;YACpB,CAAC,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,GAAG;SACvC;QACD,OAAO,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAE;KAC1C,CAAC,CAAA;IAEF,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;QACrB,IAAI,EAAE,wBAAwB;QAC9B,IAAI,EAAE;YACJ,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI;YAC/B,EAAE,EAAE,GAAG,CAAC,UAAU;YAClB,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC;YACtE,WAAW,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,QAAQ;YAC3C,mBAAmB;YACnB,cAAc,EAAE,eAAe;SAChC;KACF,CAAC,CAAA;IAEF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PipelineStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies the document exists and isn't already published with no
|
|
4
|
+
* outstanding draft changes. The publish operation should be idempotent
|
|
5
|
+
* but not silent — re-publishing a clean document returns a clear "already
|
|
6
|
+
* published" response so callers can distinguish that from success.
|
|
7
|
+
*/
|
|
8
|
+
export declare const existStep: PipelineStep;
|
|
9
|
+
//# sourceMappingURL=exist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exist.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/exist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,YAgBvB,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies the document exists and isn't already published with no
|
|
3
|
+
* outstanding draft changes. The publish operation should be idempotent
|
|
4
|
+
* but not silent — re-publishing a clean document returns a clear "already
|
|
5
|
+
* published" response so callers can distinguish that from success.
|
|
6
|
+
*/
|
|
7
|
+
export const existStep = async (ctx) => {
|
|
8
|
+
if (!ctx.document || Object.keys(ctx.document).length === 0) {
|
|
9
|
+
return { pass: false, code: 'not-found', reason: 'Document not found' };
|
|
10
|
+
}
|
|
11
|
+
const isPublished = ctx.document['_status'] === 'published';
|
|
12
|
+
if (isPublished && !hasUnpublishedChanges(ctx.document, ctx.collection.publishedAtField)) {
|
|
13
|
+
return {
|
|
14
|
+
pass: false,
|
|
15
|
+
code: 'already-published',
|
|
16
|
+
reason: 'Document is already published with no unpublished changes',
|
|
17
|
+
suggestion: 'Make a draft change first, then publish.',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return { pass: true };
|
|
21
|
+
};
|
|
22
|
+
function hasUnpublishedChanges(doc, publishedAtField) {
|
|
23
|
+
const updatedRaw = doc['updatedAt'];
|
|
24
|
+
const publishedRaw = doc[publishedAtField];
|
|
25
|
+
const updatedAt = typeof updatedRaw === 'string' ? Date.parse(updatedRaw) : 0;
|
|
26
|
+
const publishedAt = typeof publishedRaw === 'string' ? Date.parse(publishedRaw) : 0;
|
|
27
|
+
if (Number.isNaN(updatedAt) || Number.isNaN(publishedAt))
|
|
28
|
+
return true;
|
|
29
|
+
return updatedAt > publishedAt;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=exist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exist.js","sourceRoot":"","sources":["../../../src/pipeline/steps/exist.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IACnD,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAA;IACzE,CAAC;IAED,MAAM,WAAW,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,WAAW,CAAA;IAC3D,IAAI,WAAW,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACzF,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,mBAAmB;YACzB,MAAM,EAAE,2DAA2D;YACnE,UAAU,EAAE,0CAA0C;SACvD,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA;AAED,SAAS,qBAAqB,CAC5B,GAA4B,EAC5B,gBAAwB;IAExB,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,CAAC,CAAA;IACnC,MAAM,YAAY,GAAG,GAAG,CAAC,gBAAgB,CAAC,CAAA;IAC1C,MAAM,SAAS,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7E,MAAM,WAAW,GAAG,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACnF,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAA;IACrE,OAAO,SAAS,GAAG,WAAW,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PipelineStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verifies SEO title/description, slug, and any per-collection required
|
|
4
|
+
* fields are populated. SEO requirements are non-negotiable: a published
|
|
5
|
+
* page without title and description hurts share previews, search results,
|
|
6
|
+
* and accessibility cards.
|
|
7
|
+
*/
|
|
8
|
+
export declare const requiredFieldsStep: PipelineStep;
|
|
9
|
+
//# sourceMappingURL=required-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"required-fields.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/required-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAA;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,YAiDhC,CAAA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies SEO title/description, slug, and any per-collection required
|
|
3
|
+
* fields are populated. SEO requirements are non-negotiable: a published
|
|
4
|
+
* page without title and description hurts share previews, search results,
|
|
5
|
+
* and accessibility cards.
|
|
6
|
+
*/
|
|
7
|
+
export const requiredFieldsStep = async (ctx) => {
|
|
8
|
+
const issues = [];
|
|
9
|
+
const seo = ctx.document[ctx.collection.seoField];
|
|
10
|
+
if (!nonEmptyString(seo?.['title'])) {
|
|
11
|
+
issues.push({
|
|
12
|
+
field: `${ctx.collection.seoField}.title`,
|
|
13
|
+
message: 'SEO title is required',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (!nonEmptyString(seo?.['description'])) {
|
|
18
|
+
issues.push({
|
|
19
|
+
field: `${ctx.collection.seoField}.description`,
|
|
20
|
+
message: 'SEO description is required',
|
|
21
|
+
severity: 'error',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (!nonEmptyString(ctx.document[ctx.collection.slugField])) {
|
|
25
|
+
issues.push({
|
|
26
|
+
field: ctx.collection.slugField,
|
|
27
|
+
message: 'Slug is required',
|
|
28
|
+
severity: 'error',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
for (const required of ctx.collection.requiredFields ?? []) {
|
|
32
|
+
const value = getPath(ctx.document, required.path);
|
|
33
|
+
if (value === null || value === undefined || value === '') {
|
|
34
|
+
issues.push({
|
|
35
|
+
field: required.path,
|
|
36
|
+
message: required.message,
|
|
37
|
+
severity: 'error',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (issues.length > 0) {
|
|
42
|
+
return {
|
|
43
|
+
pass: false,
|
|
44
|
+
code: 'required-fields-missing',
|
|
45
|
+
reason: `${issues.length} required field${issues.length === 1 ? '' : 's'} missing`,
|
|
46
|
+
issues,
|
|
47
|
+
suggestion: 'Fill in the missing fields and try again.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { pass: true };
|
|
51
|
+
};
|
|
52
|
+
function nonEmptyString(value) {
|
|
53
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
54
|
+
}
|
|
55
|
+
function getPath(obj, path) {
|
|
56
|
+
return path.split('.').reduce((acc, segment) => {
|
|
57
|
+
if (acc && typeof acc === 'object') {
|
|
58
|
+
return acc[segment];
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}, obj);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=required-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"required-fields.js","sourceRoot":"","sources":["../../../src/pipeline/steps/required-fields.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAiB,KAAK,EAAE,GAAG,EAAE,EAAE;IAC5D,MAAM,MAAM,GAAoB,EAAE,CAAA;IAClC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAwC,CAAA;IAExF,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,QAAQ;YACzC,OAAO,EAAE,uBAAuB;YAChC,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,cAAc;YAC/C,OAAO,EAAE,6BAA6B;YACtC,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC;YACV,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,SAAS;YAC/B,OAAO,EAAE,kBAAkB;YAC3B,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,UAAU,CAAC,cAAc,IAAI,EAAE,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;QAClD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,QAAQ,CAAC,IAAI;gBACpB,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,IAAI,EAAE,KAAK;YACX,IAAI,EAAE,yBAAyB;YAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,kBAAkB,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,UAAU;YAClF,MAAM;YACN,UAAU,EAAE,2CAA2C;SACxD,CAAA;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AACvB,CAAC,CAAA;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;AACzD,CAAC;AAED,SAAS,OAAO,CAAC,GAA4B,EAAE,IAAY;IACzD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAU,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;QACtD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YACnC,OAAQ,GAA+B,CAAC,OAAO,CAAC,CAAA;QAClD,CAAC;QACD,OAAO,SAAS,CAAA;IAClB,CAAC,EAAE,GAAG,CAAC,CAAA;AACT,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Inngest } from 'inngest';
|
|
2
|
+
import type { Payload } from 'payload';
|
|
3
|
+
import type { AuthenticatedUser } from '@forumone/throughline-plugin-contract';
|
|
4
|
+
import type { AccessibilityIssue, PublishingPluginOptions, ResolvedCollection } from '../options.js';
|
|
5
|
+
export interface PipelineActor {
|
|
6
|
+
user: AuthenticatedUser | null;
|
|
7
|
+
apiKeyName: string;
|
|
8
|
+
sessionId?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface PipelineMeta {
|
|
11
|
+
userPrompt?: string | undefined;
|
|
12
|
+
reasoning?: string | undefined;
|
|
13
|
+
changesSummary?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface PipelineContext {
|
|
16
|
+
payload: Payload;
|
|
17
|
+
inngest: Inngest;
|
|
18
|
+
options: PublishingPluginOptions;
|
|
19
|
+
collection: ResolvedCollection;
|
|
20
|
+
document: Record<string, unknown>;
|
|
21
|
+
documentId: string;
|
|
22
|
+
actor: PipelineActor;
|
|
23
|
+
meta?: PipelineMeta | undefined;
|
|
24
|
+
}
|
|
25
|
+
export type PipelineIssue = AccessibilityIssue & {
|
|
26
|
+
rule?: string;
|
|
27
|
+
};
|
|
28
|
+
export interface PipelineStepResult {
|
|
29
|
+
pass: boolean;
|
|
30
|
+
reason?: string;
|
|
31
|
+
code?: string;
|
|
32
|
+
issues?: PipelineIssue[];
|
|
33
|
+
suggestion?: string;
|
|
34
|
+
}
|
|
35
|
+
export type PipelineStep = (context: PipelineContext) => Promise<PipelineStepResult>;
|
|
36
|
+
export interface PipelineResult {
|
|
37
|
+
success: boolean;
|
|
38
|
+
failedAt?: string;
|
|
39
|
+
reason?: string;
|
|
40
|
+
code?: string;
|
|
41
|
+
issues?: PipelineIssue[];
|
|
42
|
+
suggestion?: string;
|
|
43
|
+
publishedAt?: string;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,eAAe,CAAA;AAEtB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,uBAAuB,CAAA;IAChC,UAAU,EAAE,kBAAkB,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,aAAa,CAAA;IACpB,IAAI,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;CAChC;AAED,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,aAAa,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;AAEpF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,aAAa,EAAE,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/pipeline/types.ts"],"names":[],"mappings":""}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAA;AAQvE,OAAO,EAAE,KAAK,uBAAuB,EAAmB,MAAM,cAAc,CAAA;AAgB5E,eAAO,MAAM,gBAAgB,EAAE,UAAU,CAAC,uBAAuB,CA2F9D,CAAA"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { getPluginRegistry } from '@forumone/throughline-plugin-contract';
|
|
2
|
+
import { createMcpHandler, createNamedLogger, defaultLogger, getAuditWriter, } from '@forumone/throughline-core';
|
|
3
|
+
import { validateOptions } from './options.js';
|
|
4
|
+
import { createBlockStatusWritesHook } from './hooks/block-status-writes.js';
|
|
5
|
+
import { createGetPublishStatusTool, createPublishTool, createRollbackTool, createSchedulePublishTool, createUnpublishTool, } from './tools/index.js';
|
|
6
|
+
const PLUGIN_ID = '@forumone/throughline-publishing';
|
|
7
|
+
const PLUGIN_VERSION = '0.1.0';
|
|
8
|
+
const MCP_HANDLER_SYMBOL = Symbol.for('@forumone/throughline/publishing-mcp-handler');
|
|
9
|
+
export const publishingPlugin = (rawOptions) => (incomingConfig) => {
|
|
10
|
+
if (rawOptions.enabled === false)
|
|
11
|
+
return incomingConfig;
|
|
12
|
+
const options = validateOptions(rawOptions);
|
|
13
|
+
// Payload prepends `config.routes.api` (default `/api`); paths here must
|
|
14
|
+
// not start with /api or they'll be doubled.
|
|
15
|
+
const routePrefix = options.routePrefix ?? '/publishing';
|
|
16
|
+
const logger = createNamedLogger('publishing', options.logger ?? defaultLogger);
|
|
17
|
+
const publishableSlugs = new Set(options.collections.map((c) => c.slug));
|
|
18
|
+
// Inject the block-status-writes hook into every publishable collection.
|
|
19
|
+
const modifiedCollections = (incomingConfig.collections ?? []).map((collection) => {
|
|
20
|
+
if (!publishableSlugs.has(collection.slug))
|
|
21
|
+
return collection;
|
|
22
|
+
return {
|
|
23
|
+
...collection,
|
|
24
|
+
hooks: {
|
|
25
|
+
...(collection.hooks ?? {}),
|
|
26
|
+
beforeChange: [
|
|
27
|
+
...(collection.hooks?.beforeChange ?? []),
|
|
28
|
+
createBlockStatusWritesHook(),
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
...incomingConfig,
|
|
35
|
+
collections: modifiedCollections,
|
|
36
|
+
endpoints: [
|
|
37
|
+
...(incomingConfig.endpoints ?? []),
|
|
38
|
+
{
|
|
39
|
+
path: `${routePrefix}/mcp`,
|
|
40
|
+
method: 'post',
|
|
41
|
+
handler: async (req) => {
|
|
42
|
+
const handler = req.payload[MCP_HANDLER_SYMBOL];
|
|
43
|
+
if (!handler) {
|
|
44
|
+
return new Response(JSON.stringify({ error: 'Publishing MCP not initialized' }), { status: 503, headers: { 'content-type': 'application/json' } });
|
|
45
|
+
}
|
|
46
|
+
return handler(req);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
onInit: async (payload) => {
|
|
51
|
+
if (incomingConfig.onInit) {
|
|
52
|
+
await incomingConfig.onInit(payload);
|
|
53
|
+
}
|
|
54
|
+
const registry = getPluginRegistry(payload);
|
|
55
|
+
registry.requireCapability('audit-log', PLUGIN_ID);
|
|
56
|
+
const auditWriter = getAuditWriter(payload);
|
|
57
|
+
const tools = [
|
|
58
|
+
createPublishTool({ payload, options, auditWriter }),
|
|
59
|
+
createUnpublishTool({ payload, options, auditWriter }),
|
|
60
|
+
createSchedulePublishTool({ payload, options, auditWriter }),
|
|
61
|
+
createGetPublishStatusTool({ payload, options }),
|
|
62
|
+
createRollbackTool({ payload, options, auditWriter }),
|
|
63
|
+
];
|
|
64
|
+
const handler = createMcpHandler({
|
|
65
|
+
payload,
|
|
66
|
+
serverName: 'publishing',
|
|
67
|
+
tools,
|
|
68
|
+
logger,
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(payload, MCP_HANDLER_SYMBOL, {
|
|
71
|
+
value: handler,
|
|
72
|
+
enumerable: false,
|
|
73
|
+
writable: false,
|
|
74
|
+
configurable: false,
|
|
75
|
+
});
|
|
76
|
+
registry.register({
|
|
77
|
+
id: PLUGIN_ID,
|
|
78
|
+
version: PLUGIN_VERSION,
|
|
79
|
+
capabilities: ['publishing', 'publish-pipeline'],
|
|
80
|
+
});
|
|
81
|
+
logger.info('Publishing server ready', {
|
|
82
|
+
collections: options.collections.map((c) => c.slug),
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,cAAc,GACf,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AAC5E,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EAClB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,kBAAkB,CAAA;AAEzB,MAAM,SAAS,GAAG,kCAAkC,CAAA;AACpD,MAAM,cAAc,GAAG,OAAO,CAAA;AAC9B,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAA;AAIrF,MAAM,CAAC,MAAM,gBAAgB,GAC3B,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,cAAc,EAAE,EAAE;IACjC,IAAI,UAAU,CAAC,OAAO,KAAK,KAAK;QAAE,OAAO,cAAc,CAAA;IAEvD,MAAM,OAAO,GAAG,eAAe,CAAC,UAAU,CAAC,CAAA;IAC3C,yEAAyE;IACzE,6CAA6C;IAC7C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,aAAa,CAAA;IACxD,MAAM,MAAM,GAAG,iBAAiB,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,CAAA;IAC/E,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAExE,yEAAyE;IACzE,MAAM,mBAAmB,GAAG,CAAC,cAAc,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QAChF,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,UAAU,CAAA;QAC7D,OAAO;YACL,GAAG,UAAU;YACb,KAAK,EAAE;gBACL,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC3B,YAAY,EAAE;oBACZ,GAAG,CAAC,UAAU,CAAC,KAAK,EAAE,YAAY,IAAI,EAAE,CAAC;oBACzC,2BAA2B,EAAE;iBAC9B;aACF;SACyB,CAAA;IAC9B,CAAC,CAAC,CAAA;IAEF,OAAO;QACL,GAAG,cAAc;QACjB,WAAW,EAAE,mBAAmB;QAChC,SAAS,EAAE;YACT,GAAG,CAAC,cAAc,CAAC,SAAS,IAAI,EAAE,CAAC;YACnC;gBACE,IAAI,EAAE,GAAG,WAAW,MAAM;gBAC1B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;oBACrB,MAAM,OAAO,GAAI,GAAG,CAAC,OAA8C,CACjE,kBAAkB,CACO,CAAA;oBAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;wBACb,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,EAC3D,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACjE,CAAA;oBACH,CAAC;oBACD,OAAO,OAAO,CAAC,GAAyB,CAAC,CAAA;gBAC3C,CAAC;aACF;SACF;QACD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACxB,IAAI,cAAc,CAAC,MAAM,EAAE,CAAC;gBAC1B,MAAM,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACtC,CAAC;YAED,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;YAC3C,QAAQ,CAAC,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;YAElD,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;YAE3C,MAAM,KAAK,GAAG;gBACZ,iBAAiB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;gBACpD,mBAAmB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;gBACtD,yBAAyB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;gBAC5D,0BAA0B,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;gBAChD,kBAAkB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;aACtD,CAAA;YAED,MAAM,OAAO,GAAG,gBAAgB,CAAC;gBAC/B,OAAO;gBACP,UAAU,EAAE,YAAY;gBACxB,KAAK;gBACL,MAAM;aACP,CAAC,CAAA;YAEF,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE;gBACjD,KAAK,EAAE,OAAO;gBACd,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,KAAK;gBACf,YAAY,EAAE,KAAK;aACpB,CAAC,CAAA;YAEF,QAAQ,CAAC,QAAQ,CAAC;gBAChB,EAAE,EAAE,SAAS;gBACb,OAAO,EAAE,cAAc;gBACvB,YAAY,EAAE,CAAC,YAAY,EAAE,kBAAkB,CAAC;aACjD,CAAC,CAAA;YAEF,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE;gBACrC,WAAW,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aACpD,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|