@forumone/throughline-publishing 0.0.1 → 0.2.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +2 -42
  4. package/dist/checks/alt-text.d.ts +11 -0
  5. package/dist/checks/alt-text.d.ts.map +1 -0
  6. package/dist/checks/alt-text.js +53 -0
  7. package/dist/checks/alt-text.js.map +1 -0
  8. package/dist/checks/heading-hierarchy.d.ts +11 -0
  9. package/dist/checks/heading-hierarchy.d.ts.map +1 -0
  10. package/dist/checks/heading-hierarchy.js +32 -0
  11. package/dist/checks/heading-hierarchy.js.map +1 -0
  12. package/dist/checks/index.d.ts +6 -0
  13. package/dist/checks/index.d.ts.map +1 -0
  14. package/dist/checks/index.js +13 -0
  15. package/dist/checks/index.js.map +1 -0
  16. package/dist/checks/link-labels.d.ts +9 -0
  17. package/dist/checks/link-labels.d.ts.map +1 -0
  18. package/dist/checks/link-labels.js +44 -0
  19. package/dist/checks/link-labels.js.map +1 -0
  20. package/dist/hooks/block-status-writes.d.ts +12 -0
  21. package/dist/hooks/block-status-writes.d.ts.map +1 -0
  22. package/dist/hooks/block-status-writes.js +36 -0
  23. package/dist/hooks/block-status-writes.js.map +1 -0
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/options.d.ts +65 -0
  29. package/dist/options.d.ts.map +1 -0
  30. package/dist/options.js +57 -0
  31. package/dist/options.js.map +1 -0
  32. package/dist/pipeline/index.d.ts +15 -0
  33. package/dist/pipeline/index.d.ts.map +1 -0
  34. package/dist/pipeline/index.js +62 -0
  35. package/dist/pipeline/index.js.map +1 -0
  36. package/dist/pipeline/steps/accessibility.d.ts +9 -0
  37. package/dist/pipeline/steps/accessibility.d.ts.map +1 -0
  38. package/dist/pipeline/steps/accessibility.js +32 -0
  39. package/dist/pipeline/steps/accessibility.js.map +1 -0
  40. package/dist/pipeline/steps/approval.d.ts +11 -0
  41. package/dist/pipeline/steps/approval.d.ts.map +1 -0
  42. package/dist/pipeline/steps/approval.js +33 -0
  43. package/dist/pipeline/steps/approval.js.map +1 -0
  44. package/dist/pipeline/steps/composition.d.ts +14 -0
  45. package/dist/pipeline/steps/composition.d.ts.map +1 -0
  46. package/dist/pipeline/steps/composition.js +63 -0
  47. package/dist/pipeline/steps/composition.js.map +1 -0
  48. package/dist/pipeline/steps/embargo.d.ts +12 -0
  49. package/dist/pipeline/steps/embargo.d.ts.map +1 -0
  50. package/dist/pipeline/steps/embargo.js +41 -0
  51. package/dist/pipeline/steps/embargo.js.map +1 -0
  52. package/dist/pipeline/steps/execute.d.ts +12 -0
  53. package/dist/pipeline/steps/execute.d.ts.map +1 -0
  54. package/dist/pipeline/steps/execute.js +38 -0
  55. package/dist/pipeline/steps/execute.js.map +1 -0
  56. package/dist/pipeline/steps/exist.d.ts +9 -0
  57. package/dist/pipeline/steps/exist.d.ts.map +1 -0
  58. package/dist/pipeline/steps/exist.js +31 -0
  59. package/dist/pipeline/steps/exist.js.map +1 -0
  60. package/dist/pipeline/steps/required-fields.d.ts +9 -0
  61. package/dist/pipeline/steps/required-fields.d.ts.map +1 -0
  62. package/dist/pipeline/steps/required-fields.js +63 -0
  63. package/dist/pipeline/steps/required-fields.js.map +1 -0
  64. package/dist/pipeline/types.d.ts +45 -0
  65. package/dist/pipeline/types.d.ts.map +1 -0
  66. package/dist/pipeline/types.js +2 -0
  67. package/dist/pipeline/types.js.map +1 -0
  68. package/dist/plugin.d.ts +4 -0
  69. package/dist/plugin.d.ts.map +1 -0
  70. package/dist/plugin.js +87 -0
  71. package/dist/plugin.js.map +1 -0
  72. package/dist/tools/get-publish-status.d.ts +9 -0
  73. package/dist/tools/get-publish-status.d.ts.map +1 -0
  74. package/dist/tools/get-publish-status.js +55 -0
  75. package/dist/tools/get-publish-status.js.map +1 -0
  76. package/dist/tools/index.d.ts +6 -0
  77. package/dist/tools/index.d.ts.map +1 -0
  78. package/dist/tools/index.js +6 -0
  79. package/dist/tools/index.js.map +1 -0
  80. package/dist/tools/publish.d.ts +11 -0
  81. package/dist/tools/publish.d.ts.map +1 -0
  82. package/dist/tools/publish.js +71 -0
  83. package/dist/tools/publish.js.map +1 -0
  84. package/dist/tools/rollback.d.ts +11 -0
  85. package/dist/tools/rollback.d.ts.map +1 -0
  86. package/dist/tools/rollback.js +78 -0
  87. package/dist/tools/rollback.js.map +1 -0
  88. package/dist/tools/schedule-publish.d.ts +11 -0
  89. package/dist/tools/schedule-publish.d.ts.map +1 -0
  90. package/dist/tools/schedule-publish.js +94 -0
  91. package/dist/tools/schedule-publish.js.map +1 -0
  92. package/dist/tools/unpublish.d.ts +11 -0
  93. package/dist/tools/unpublish.d.ts.map +1 -0
  94. package/dist/tools/unpublish.js +61 -0
  95. package/dist/tools/unpublish.js.map +1 -0
  96. package/package.json +66 -7
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @forumone/throughline-publishing
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`123d2ea`](https://github.com/forumone/throughline/commit/123d2ea0172d9495b9e2c8e8c6039e623f5fba66) Thanks [@briangraves](https://github.com/briangraves)! - Initial release. Policy-gated publishing server with a seven-step pipeline (exist, composition, accessibility, required-fields, embargo, approval, execute), five MCP tools (publish, unpublish, schedule_publish, get_publish_status, rollback), and a Payload `beforeChange` hook on every publishable collection that rejects direct `_status` writes from anywhere other than the pipeline's execute step. Built-in accessibility checks cover alt text, heading hierarchy, and link labels; clients add custom checks via the `accessibilityChecks` option.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forum One Communications Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,5 @@
1
1
  # @forumone/throughline-publishing
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ Policy-gated publishing server for Throughline. The trust boundary that decides what's allowed to ship.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@forumone/throughline-publishing`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
5
+ _Authoring guide and full API reference land in a later commit._
@@ -0,0 +1,11 @@
1
+ import type { AccessibilityCheck } from '../options.js';
2
+ /**
3
+ * Walks the document tree looking for image-shaped objects (anything with
4
+ * a `url` or `filename` property whose `mimeType`, if present, starts with
5
+ * `image/`). Flags any whose `alt` is missing or empty.
6
+ *
7
+ * Heuristic, not exhaustive — clients with non-standard image shapes
8
+ * register their own check via `accessibilityChecks`.
9
+ */
10
+ export declare const altTextCheck: AccessibilityCheck;
11
+ //# sourceMappingURL=alt-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alt-text.d.ts","sourceRoot":"","sources":["../../src/checks/alt-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,eAAe,CAAA;AAE3E;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,kBAgB1B,CAAA"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Walks the document tree looking for image-shaped objects (anything with
3
+ * a `url` or `filename` property whose `mimeType`, if present, starts with
4
+ * `image/`). Flags any whose `alt` is missing or empty.
5
+ *
6
+ * Heuristic, not exhaustive — clients with non-standard image shapes
7
+ * register their own check via `accessibilityChecks`.
8
+ */
9
+ export const altTextCheck = {
10
+ name: 'alt-text',
11
+ run(doc) {
12
+ const issues = [];
13
+ walkForImages(doc, (image, path) => {
14
+ const alt = image['alt'];
15
+ if (typeof alt !== 'string' || alt.trim() === '') {
16
+ issues.push({
17
+ field: path,
18
+ message: `Image at "${path}" is missing alt text`,
19
+ severity: 'error',
20
+ });
21
+ }
22
+ });
23
+ return issues;
24
+ },
25
+ };
26
+ function walkForImages(value, visit, path = '') {
27
+ if (!value || typeof value !== 'object')
28
+ return;
29
+ if (Array.isArray(value)) {
30
+ for (let i = 0; i < value.length; i++) {
31
+ walkForImages(value[i], visit, `${path}[${i}]`);
32
+ }
33
+ return;
34
+ }
35
+ const obj = value;
36
+ if (looksLikeImage(obj)) {
37
+ visit(obj, path || '(root)');
38
+ }
39
+ for (const key of Object.keys(obj)) {
40
+ walkForImages(obj[key], visit, path ? `${path}.${key}` : key);
41
+ }
42
+ }
43
+ function looksLikeImage(obj) {
44
+ // Require an explicit image marker: a `filename` (Payload media uploads
45
+ // always have one) or an `image/*` mimeType. A bare `{ url }` is too
46
+ // ambiguous — it's just as likely to be a link, so we leave that to the
47
+ // link-labels check.
48
+ const mime = obj['mimeType'];
49
+ if (typeof mime === 'string')
50
+ return mime.startsWith('image/');
51
+ return typeof obj['filename'] === 'string';
52
+ }
53
+ //# sourceMappingURL=alt-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alt-text.js","sourceRoot":"","sources":["../../src/checks/alt-text.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAuB;IAC9C,IAAI,EAAE,UAAU;IAChB,GAAG,CAAC,GAAG;QACL,MAAM,MAAM,GAAyB,EAAE,CAAA;QACvC,aAAa,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACjC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAA;YACxB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACjD,MAAM,CAAC,IAAI,CAAC;oBACV,KAAK,EAAE,IAAI;oBACX,OAAO,EAAE,aAAa,IAAI,uBAAuB;oBACjD,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAC,CAAA;QACF,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA;AAED,SAAS,aAAa,CACpB,KAAc,EACd,KAA6D,EAC7D,IAAI,GAAG,EAAE;IAET,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAM;IAE/C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;QACjD,CAAC;QACD,OAAM;IACR,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAA;IAC5C,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,QAAQ,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAA4B;IAClD,wEAAwE;IACxE,qEAAqE;IACrE,wEAAwE;IACxE,qBAAqB;IACrB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAA;IAC5B,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC9D,OAAO,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAA;AAC5C,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { AccessibilityCheck } from '../options.js';
2
+ /**
3
+ * Structural heading-hierarchy check. Without rendering the page we can't
4
+ * fully audit heading levels, but we can flag the most common structural
5
+ * mistake: multiple Hero blocks (each becomes an h1 in the reference DS).
6
+ *
7
+ * Block types are matched case-insensitively against `'hero'` since the
8
+ * `blockType` field is by convention lower-cased in Payload.
9
+ */
10
+ export declare const headingHierarchyCheck: AccessibilityCheck;
11
+ //# sourceMappingURL=heading-hierarchy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-hierarchy.d.ts","sourceRoot":"","sources":["../../src/checks/heading-hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,eAAe,CAAA;AAE3E;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,EAAE,kBAuBnC,CAAA"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Structural heading-hierarchy check. Without rendering the page we can't
3
+ * fully audit heading levels, but we can flag the most common structural
4
+ * mistake: multiple Hero blocks (each becomes an h1 in the reference DS).
5
+ *
6
+ * Block types are matched case-insensitively against `'hero'` since the
7
+ * `blockType` field is by convention lower-cased in Payload.
8
+ */
9
+ export const headingHierarchyCheck = {
10
+ name: 'heading-hierarchy',
11
+ run(doc, collection) {
12
+ const issues = [];
13
+ const layoutValue = doc[collection.layoutField];
14
+ if (!Array.isArray(layoutValue))
15
+ return issues;
16
+ const heroCount = layoutValue.filter((block) => {
17
+ if (!block || typeof block !== 'object')
18
+ return false;
19
+ const blockType = block['blockType'];
20
+ return typeof blockType === 'string' && blockType.toLowerCase() === 'hero';
21
+ }).length;
22
+ if (heroCount > 1) {
23
+ issues.push({
24
+ field: collection.layoutField,
25
+ message: `Page has ${heroCount} Hero blocks but should have exactly one (each Hero becomes an h1)`,
26
+ severity: 'error',
27
+ });
28
+ }
29
+ return issues;
30
+ },
31
+ };
32
+ //# sourceMappingURL=heading-hierarchy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-hierarchy.js","sourceRoot":"","sources":["../../src/checks/heading-hierarchy.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAuB;IACvD,IAAI,EAAE,mBAAmB;IACzB,GAAG,CAAC,GAAG,EAAE,UAAU;QACjB,MAAM,MAAM,GAAyB,EAAE,CAAA;QACvC,MAAM,WAAW,GAAG,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;QAC/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO,MAAM,CAAA;QAE9C,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAA;YACrD,MAAM,SAAS,GAAI,KAAiC,CAAC,WAAW,CAAC,CAAA;YACjE,OAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;QAC5E,CAAC,CAAC,CAAC,MAAM,CAAA;QAET,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,UAAU,CAAC,WAAW;gBAC7B,OAAO,EAAE,YAAY,SAAS,oEAAoE;gBAClG,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAA;QACJ,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA"}
@@ -0,0 +1,6 @@
1
+ export { altTextCheck } from './alt-text.js';
2
+ export { headingHierarchyCheck } from './heading-hierarchy.js';
3
+ export { linkLabelsCheck } from './link-labels.js';
4
+ /** Built-in accessibility checks the pipeline runs before any user-supplied ones. */
5
+ export declare const BUILT_IN_ACCESSIBILITY_CHECKS: readonly [import("../options.js").AccessibilityCheck, import("../options.js").AccessibilityCheck, import("../options.js").AccessibilityCheck];
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAElD,qFAAqF;AACrF,eAAO,MAAM,6BAA6B,+IAIhC,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { altTextCheck } from './alt-text.js';
2
+ import { headingHierarchyCheck } from './heading-hierarchy.js';
3
+ import { linkLabelsCheck } from './link-labels.js';
4
+ export { altTextCheck } from './alt-text.js';
5
+ export { headingHierarchyCheck } from './heading-hierarchy.js';
6
+ export { linkLabelsCheck } from './link-labels.js';
7
+ /** Built-in accessibility checks the pipeline runs before any user-supplied ones. */
8
+ export const BUILT_IN_ACCESSIBILITY_CHECKS = [
9
+ altTextCheck,
10
+ headingHierarchyCheck,
11
+ linkLabelsCheck,
12
+ ];
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAElD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAElD,qFAAqF;AACrF,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,YAAY;IACZ,qBAAqB;IACrB,eAAe;CACP,CAAA"}
@@ -0,0 +1,9 @@
1
+ import type { AccessibilityCheck } from '../options.js';
2
+ /**
3
+ * Walks the document tree for objects with both `url` and `label` fields and
4
+ * flags any where the URL is non-empty but the label is missing or empty.
5
+ * "Click here"-style labels are not flagged (those need a content review,
6
+ * not a link-label check).
7
+ */
8
+ export declare const linkLabelsCheck: AccessibilityCheck;
9
+ //# sourceMappingURL=link-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-labels.d.ts","sourceRoot":"","sources":["../../src/checks/link-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,eAAe,CAAA;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,kBAmB7B,CAAA"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Walks the document tree for objects with both `url` and `label` fields and
3
+ * flags any where the URL is non-empty but the label is missing or empty.
4
+ * "Click here"-style labels are not flagged (those need a content review,
5
+ * not a link-label check).
6
+ */
7
+ export const linkLabelsCheck = {
8
+ name: 'link-labels',
9
+ run(doc) {
10
+ const issues = [];
11
+ walkForLinks(doc, (link, path) => {
12
+ const url = link['url'];
13
+ const label = link['label'];
14
+ const hasUrl = typeof url === 'string' && url.trim() !== '';
15
+ const hasLabel = typeof label === 'string' && label.trim() !== '';
16
+ if (hasUrl && !hasLabel) {
17
+ issues.push({
18
+ field: path,
19
+ message: `Link at "${path}" has a URL but no label`,
20
+ severity: 'error',
21
+ });
22
+ }
23
+ });
24
+ return issues;
25
+ },
26
+ };
27
+ function walkForLinks(value, visit, path = '') {
28
+ if (!value || typeof value !== 'object')
29
+ return;
30
+ if (Array.isArray(value)) {
31
+ for (let i = 0; i < value.length; i++) {
32
+ walkForLinks(value[i], visit, `${path}[${i}]`);
33
+ }
34
+ return;
35
+ }
36
+ const obj = value;
37
+ if ('url' in obj && 'label' in obj) {
38
+ visit(obj, path || '(root)');
39
+ }
40
+ for (const key of Object.keys(obj)) {
41
+ walkForLinks(obj[key], visit, path ? `${path}.${key}` : key);
42
+ }
43
+ }
44
+ //# sourceMappingURL=link-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-labels.js","sourceRoot":"","sources":["../../src/checks/link-labels.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAuB;IACjD,IAAI,EAAE,aAAa;IACnB,GAAG,CAAC,GAAG;QACL,MAAM,MAAM,GAAyB,EAAE,CAAA;QACvC,YAAY,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;YAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAA;YAC3B,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;YAC3D,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;YACjE,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC;oBACV,KAAK,EAAE,IAAI;oBACX,OAAO,EAAE,YAAY,IAAI,0BAA0B;oBACnD,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAA;YACJ,CAAC;QACH,CAAC,CAAC,CAAA;QACF,OAAO,MAAM,CAAA;IACf,CAAC;CACF,CAAA;AAED,SAAS,YAAY,CACnB,KAAc,EACd,KAA4D,EAC5D,IAAI,GAAG,EAAE;IAET,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAM;IAE/C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;QAChD,CAAC;QACD,OAAM;IACR,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAA;IAC5C,IAAI,KAAK,IAAI,GAAG,IAAI,OAAO,IAAI,GAAG,EAAE,CAAC;QACnC,KAAK,CAAC,GAAG,EAAE,IAAI,IAAI,QAAQ,CAAC,CAAA;IAC9B,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC9D,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { CollectionBeforeChangeHook } from 'payload';
2
+ /**
3
+ * The trust boundary. This `beforeChange` hook rejects every update that
4
+ * touches `_status` unless the request explicitly carries the
5
+ * `bypassPublishingServer` context flag (set by the pipeline's `executeStep`).
6
+ *
7
+ * Without this hook, the Payload MCP could let Claude flip a document to
8
+ * `published` directly, sidestepping every check the publishing server
9
+ * exists to enforce. With it, the only sanctioned path is the pipeline.
10
+ */
11
+ export declare function createBlockStatusWritesHook(): CollectionBeforeChangeHook;
12
+ //# sourceMappingURL=block-status-writes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-status-writes.d.ts","sourceRoot":"","sources":["../../src/hooks/block-status-writes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAA;AAEzD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,IAAI,0BAA0B,CAqBxE"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The trust boundary. This `beforeChange` hook rejects every update that
3
+ * touches `_status` unless the request explicitly carries the
4
+ * `bypassPublishingServer` context flag (set by the pipeline's `executeStep`).
5
+ *
6
+ * Without this hook, the Payload MCP could let Claude flip a document to
7
+ * `published` directly, sidestepping every check the publishing server
8
+ * exists to enforce. With it, the only sanctioned path is the pipeline.
9
+ */
10
+ export function createBlockStatusWritesHook() {
11
+ return ({ data, originalDoc, operation, context, req }) => {
12
+ if (operation !== 'update')
13
+ return data;
14
+ if (!hasStatusChange(data))
15
+ return data;
16
+ // No-op writes (status set to its current value) are harmless.
17
+ if (originalDoc &&
18
+ typeof originalDoc === 'object' &&
19
+ data['_status'] ===
20
+ originalDoc['_status']) {
21
+ return data;
22
+ }
23
+ if (isBypassed(context) || isBypassed(req?.context))
24
+ return data;
25
+ throw new Error('Direct writes to `_status` are not allowed. Use the publishing server (publish / unpublish / rollback) so the policy pipeline runs.');
26
+ };
27
+ }
28
+ function hasStatusChange(data) {
29
+ return (typeof data === 'object' && data !== null && '_status' in data);
30
+ }
31
+ function isBypassed(context) {
32
+ if (!context || typeof context !== 'object')
33
+ return false;
34
+ return context['bypassPublishingServer'] === true;
35
+ }
36
+ //# sourceMappingURL=block-status-writes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-status-writes.js","sourceRoot":"","sources":["../../src/hooks/block-status-writes.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;QACxD,IAAI,SAAS,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAA;QACvC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAEvC,+DAA+D;QAC/D,IACE,WAAW;YACX,OAAO,WAAW,KAAK,QAAQ;YAC9B,IAAgC,CAAC,SAAS,CAAC;gBACzC,WAAuC,CAAC,SAAS,CAAC,EACrD,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;YAAE,OAAO,IAAI,CAAA;QAEhE,MAAM,IAAI,KAAK,CACb,qIAAqI,CACtI,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,IAAa;IACpC,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,SAAS,IAAI,IAAI,CAC/D,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAC,OAAgB;IAClC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACzD,OAAQ,OAAmC,CAAC,wBAAwB,CAAC,KAAK,IAAI,CAAA;AAChF,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { publishingPlugin } from './plugin.js';
2
+ export type { AccessibilityCheck, AccessibilityIssue, ActiveApproval, ApprovalResolver, PublishableCollection, PublishingPluginOptions, ResolvedCollection, } from './options.js';
3
+ export type { PipelineContext, PipelineResult, PipelineStep, PipelineStepResult, } from './pipeline/index.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9C,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,cAAc,CAAA;AAErB,YAAY,EACV,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,MAAM,qBAAqB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { publishingPlugin } from './plugin.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,65 @@
1
+ import type { Inngest } from 'inngest';
2
+ import type { BaseCorePluginOptions } from '@forumone/throughline-plugin-contract';
3
+ export interface PublishableCollection {
4
+ /** Slug of the collection that can be published through this server. */
5
+ slug: string;
6
+ /** Field name on the document holding composition blocks. Default: `'layout'`. */
7
+ layoutField?: string;
8
+ /** Field name for the SEO group used by required-field checks. Default: `'seo'`. */
9
+ seoField?: string;
10
+ /** Field name for the policy group (`requiresApproval`, `embargoedUntil`). Default: `'policy'`. */
11
+ policyField?: string;
12
+ /** Field name for the slug. Default: `'slug'`. */
13
+ slugField?: string;
14
+ /** Field name for the publish timestamp. Default: `'publishedAt'`. */
15
+ publishedAtField?: string;
16
+ /** Field name for a future scheduled publish time. Default: `'scheduledPublishAt'`. */
17
+ scheduledPublishField?: string;
18
+ /** Per-collection required fields beyond the built-in SEO/slug checks. */
19
+ requiredFields?: Array<{
20
+ path: string;
21
+ message: string;
22
+ }>;
23
+ }
24
+ export interface AccessibilityIssue {
25
+ field?: string | undefined;
26
+ message: string;
27
+ severity: 'error' | 'warning';
28
+ }
29
+ export interface AccessibilityCheck {
30
+ name: string;
31
+ run: (doc: Record<string, unknown>, collection: ResolvedCollection) => Promise<AccessibilityIssue[]> | AccessibilityIssue[];
32
+ }
33
+ export interface ActiveApproval {
34
+ id: string;
35
+ grantedAt: string;
36
+ grantedBy: string;
37
+ version: string;
38
+ }
39
+ export interface ApprovalResolver {
40
+ /** Returns the active granted approval for this document version, or `null`. */
41
+ getActiveApproval: (collection: string, id: string, version: string) => Promise<ActiveApproval | null>;
42
+ }
43
+ export interface PublishingPluginOptions extends BaseCorePluginOptions {
44
+ /** Required: collections that can be published through this server. */
45
+ collections: PublishableCollection[];
46
+ /** Optional: extra accessibility checks beyond the built-ins. */
47
+ accessibilityChecks?: AccessibilityCheck[];
48
+ /** Optional: resolver consulted when a document's policy requires approval. */
49
+ approvalResolver?: ApprovalResolver;
50
+ /** Required: Inngest client used to fire publishing events. */
51
+ inngest: Inngest;
52
+ }
53
+ export type ResolvedCollection = Required<Omit<PublishableCollection, 'requiredFields'>> & Pick<PublishableCollection, 'requiredFields'>;
54
+ /**
55
+ * Validates plugin options at load time. Throws with targeted messages on
56
+ * obvious misconfiguration so deploys fail fast rather than the first publish
57
+ * call returning 503.
58
+ */
59
+ export declare function validateOptions(options: PublishingPluginOptions): PublishingPluginOptions;
60
+ /**
61
+ * Returns the collection config with all field-name defaults filled in.
62
+ * Throws when the slug isn't registered as publishable.
63
+ */
64
+ export declare function resolveCollection(options: PublishingPluginOptions, slug: string): ResolvedCollection;
65
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAA;AAElF,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mGAAmG;IACnG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,uFAAuF;IACvF,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC1D;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,CACH,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,UAAU,EAAE,kBAAkB,KAC3B,OAAO,CAAC,kBAAkB,EAAE,CAAC,GAAG,kBAAkB,EAAE,CAAA;CAC1D;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,iBAAiB,EAAE,CACjB,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;CACpC;AAED,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,uEAAuE;IACvE,WAAW,EAAE,qBAAqB,EAAE,CAAA;IACpC,iEAAiE;IACjE,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC1C,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,+DAA+D;IAC/D,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAC,GACtF,IAAI,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAA;AAe/C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,uBAAuB,GAAG,uBAAuB,CAiBzF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,MAAM,GACX,kBAAkB,CAiBpB"}
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ const PublishableCollectionSchema = z.object({
3
+ slug: z.string().min(1),
4
+ layoutField: z.string().optional(),
5
+ seoField: z.string().optional(),
6
+ policyField: z.string().optional(),
7
+ slugField: z.string().optional(),
8
+ publishedAtField: z.string().optional(),
9
+ scheduledPublishField: z.string().optional(),
10
+ requiredFields: z
11
+ .array(z.object({ path: z.string().min(1), message: z.string().min(1) }))
12
+ .optional(),
13
+ });
14
+ /**
15
+ * Validates plugin options at load time. Throws with targeted messages on
16
+ * obvious misconfiguration so deploys fail fast rather than the first publish
17
+ * call returning 503.
18
+ */
19
+ export function validateOptions(options) {
20
+ if (!options.collections || options.collections.length === 0) {
21
+ throw new Error('publishingPlugin requires at least one collection in options.collections');
22
+ }
23
+ if (!options.inngest) {
24
+ throw new Error('publishingPlugin requires an Inngest client in options.inngest');
25
+ }
26
+ for (const collection of options.collections) {
27
+ const result = PublishableCollectionSchema.safeParse(collection);
28
+ if (!result.success) {
29
+ const issues = result.error.issues
30
+ .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`)
31
+ .join('\n');
32
+ throw new Error(`Invalid collection config:\n${issues}`);
33
+ }
34
+ }
35
+ return options;
36
+ }
37
+ /**
38
+ * Returns the collection config with all field-name defaults filled in.
39
+ * Throws when the slug isn't registered as publishable.
40
+ */
41
+ export function resolveCollection(options, slug) {
42
+ const config = options.collections.find((c) => c.slug === slug);
43
+ if (!config) {
44
+ throw new Error(`Collection "${slug}" is not registered as publishable. Add it to publishingPlugin's collections option.`);
45
+ }
46
+ return {
47
+ slug: config.slug,
48
+ layoutField: config.layoutField ?? 'layout',
49
+ seoField: config.seoField ?? 'seo',
50
+ policyField: config.policyField ?? 'policy',
51
+ slugField: config.slugField ?? 'slug',
52
+ publishedAtField: config.publishedAtField ?? 'publishedAt',
53
+ scheduledPublishField: config.scheduledPublishField ?? 'scheduledPublishAt',
54
+ ...(config.requiredFields ? { requiredFields: config.requiredFields } : {}),
55
+ };
56
+ }
57
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { PipelineContext, PipelineResult } from './types.js';
2
+ /**
3
+ * Runs the full publish pipeline. Stops at the first failing step and
4
+ * returns its diagnostic; otherwise records success with the publish
5
+ * timestamp the execute step set.
6
+ */
7
+ export declare function runPublishPipeline(context: PipelineContext): Promise<PipelineResult>;
8
+ /**
9
+ * Runs every preflight step (everything but `execute`). Used by
10
+ * `get_publish_status` to answer "would this publish succeed?" without
11
+ * mutating anything.
12
+ */
13
+ export declare function runPreflightPipeline(context: PipelineContext): Promise<PipelineResult>;
14
+ export type { PipelineContext, PipelineResult, PipelineStep, PipelineStepResult } from './types.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pipeline/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAgB,MAAM,YAAY,CAAA;AAqB/E;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAe1F;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,62 @@
1
+ import { existStep } from './steps/exist.js';
2
+ import { compositionStep } from './steps/composition.js';
3
+ import { accessibilityStep } from './steps/accessibility.js';
4
+ import { requiredFieldsStep } from './steps/required-fields.js';
5
+ import { embargoStep } from './steps/embargo.js';
6
+ import { approvalStep } from './steps/approval.js';
7
+ import { executeStep } from './steps/execute.js';
8
+ const PREFLIGHT_STEPS = [
9
+ { name: 'exist', step: existStep },
10
+ { name: 'composition', step: compositionStep },
11
+ { name: 'accessibility', step: accessibilityStep },
12
+ { name: 'required-fields', step: requiredFieldsStep },
13
+ { name: 'embargo', step: embargoStep },
14
+ { name: 'approval', step: approvalStep },
15
+ ];
16
+ const ORDERED_STEPS = [
17
+ ...PREFLIGHT_STEPS,
18
+ { name: 'execute', step: executeStep },
19
+ ];
20
+ /**
21
+ * Runs the full publish pipeline. Stops at the first failing step and
22
+ * returns its diagnostic; otherwise records success with the publish
23
+ * timestamp the execute step set.
24
+ */
25
+ export async function runPublishPipeline(context) {
26
+ for (const { name, step } of ORDERED_STEPS) {
27
+ const result = await step(context);
28
+ if (!result.pass) {
29
+ return {
30
+ success: false,
31
+ failedAt: name,
32
+ ...(result.reason ? { reason: result.reason } : {}),
33
+ ...(result.code ? { code: result.code } : {}),
34
+ ...(result.issues ? { issues: result.issues } : {}),
35
+ ...(result.suggestion ? { suggestion: result.suggestion } : {}),
36
+ };
37
+ }
38
+ }
39
+ return { success: true, publishedAt: new Date().toISOString() };
40
+ }
41
+ /**
42
+ * Runs every preflight step (everything but `execute`). Used by
43
+ * `get_publish_status` to answer "would this publish succeed?" without
44
+ * mutating anything.
45
+ */
46
+ export async function runPreflightPipeline(context) {
47
+ for (const { name, step } of PREFLIGHT_STEPS) {
48
+ const result = await step(context);
49
+ if (!result.pass) {
50
+ return {
51
+ success: false,
52
+ failedAt: name,
53
+ ...(result.reason ? { reason: result.reason } : {}),
54
+ ...(result.code ? { code: result.code } : {}),
55
+ ...(result.issues ? { issues: result.issues } : {}),
56
+ ...(result.suggestion ? { suggestion: result.suggestion } : {}),
57
+ };
58
+ }
59
+ }
60
+ return { success: true };
61
+ }
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pipeline/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { PipelineStep } from '../types.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 declare const accessibilityStep: PipelineStep;
9
+ //# sourceMappingURL=accessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../../src/pipeline/steps/accessibility.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,aAAa,CAAA;AAG9D;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,YA2B/B,CAAA"}