@actuate-media/cms-core 0.85.3 → 0.87.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 (39) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/api/openapi.d.ts.map +1 -1
  3. package/dist/api/openapi.js +24 -0
  4. package/dist/api/openapi.js.map +1 -1
  5. package/dist/api/route-helpers.d.ts.map +1 -1
  6. package/dist/api/route-helpers.js +21 -2
  7. package/dist/api/route-helpers.js.map +1 -1
  8. package/dist/api/routes/documents.d.ts.map +1 -1
  9. package/dist/api/routes/documents.js +27 -1
  10. package/dist/api/routes/documents.js.map +1 -1
  11. package/dist/api/routes/media.d.ts.map +1 -1
  12. package/dist/api/routes/media.js +3 -0
  13. package/dist/api/routes/media.js.map +1 -1
  14. package/dist/api/routes/sections.d.ts.map +1 -1
  15. package/dist/api/routes/sections.js +2 -1
  16. package/dist/api/routes/sections.js.map +1 -1
  17. package/dist/api/routes/webhooks.d.ts.map +1 -1
  18. package/dist/api/routes/webhooks.js +22 -1
  19. package/dist/api/routes/webhooks.js.map +1 -1
  20. package/dist/db/model-types.d.ts +3 -0
  21. package/dist/db/model-types.d.ts.map +1 -1
  22. package/dist/next/script-tags.d.ts.map +1 -1
  23. package/dist/next/script-tags.js +1 -1
  24. package/dist/next/script-tags.js.map +1 -1
  25. package/dist/posts/query.d.ts +6 -0
  26. package/dist/posts/query.d.ts.map +1 -1
  27. package/dist/posts/query.js +21 -0
  28. package/dist/posts/query.js.map +1 -1
  29. package/dist/script-tags/index.d.ts +1 -1
  30. package/dist/script-tags/index.d.ts.map +1 -1
  31. package/dist/script-tags/index.js +1 -1
  32. package/dist/script-tags/index.js.map +1 -1
  33. package/dist/script-tags/resolve.d.ts +27 -1
  34. package/dist/script-tags/resolve.d.ts.map +1 -1
  35. package/dist/script-tags/resolve.js +64 -2
  36. package/dist/script-tags/resolve.js.map +1 -1
  37. package/package.json +1 -1
  38. package/prisma/migrations/0005_script_tag_delivery_controls/migration.sql +8 -0
  39. package/prisma/schema.prisma +16 -10
@@ -1,4 +1,11 @@
1
1
  export const SCRIPT_TAGS_CACHE_TAG = 'actuate:script-tags';
2
+ export const SCRIPT_TAG_CONSENT_CATEGORIES = [
3
+ 'none',
4
+ 'analytics',
5
+ 'marketing',
6
+ 'functional',
7
+ 'preferences',
8
+ ];
2
9
  const EMPTY = { head: [], body_open: [], body_close: [] };
3
10
  export function normalizeScriptTagPath(pathParam) {
4
11
  const trimmed = pathParam.trim() || '/';
@@ -7,11 +14,64 @@ export function normalizeScriptTagPath(pathParam) {
7
14
  function hasScriptTagModel(db) {
8
15
  return typeof db.scriptTag?.findMany === 'function';
9
16
  }
17
+ /**
18
+ * Add `async` to external `<script src>` tags that don't already declare
19
+ * `async` or `defer`. Inline scripts are left untouched — `async` only has
20
+ * meaning for external scripts.
21
+ */
22
+ export function applyAsyncToScriptCode(code) {
23
+ return code.replace(/<script(\s[^>]*)?>/gi, (match, attrs) => {
24
+ const attrText = attrs ?? '';
25
+ if (!/\bsrc\s*=/i.test(attrText))
26
+ return match;
27
+ if (/\basync\b/i.test(attrText) || /\bdefer\b/i.test(attrText))
28
+ return match;
29
+ return `<script async${attrText}>`;
30
+ });
31
+ }
32
+ /**
33
+ * Wrap a snippet in a consent gate keyed to `@actuate-media/plugin-consent`
34
+ * storage (`am_cookie_consent`) and its `am-consent` event. The original
35
+ * snippet is injected only after the visitor grants full consent.
36
+ *
37
+ * Interim category model: the consent plugin currently stores a binary level
38
+ * (`all` | `essential`), so every non-`none` category requires `all`. The
39
+ * category is preserved as a `data-consent-category` marker for CMPs and for
40
+ * future granular gating.
41
+ */
42
+ export function wrapCodeInConsentGate(code, category) {
43
+ // \u003C-escape so the payload can never terminate the wrapper script early.
44
+ const payload = JSON.stringify(code).replace(/</g, '\\u003C');
45
+ return (`<script data-consent-category="${category}">` +
46
+ '(function(){' +
47
+ `var p=${payload};` +
48
+ // currentScript is only defined during synchronous execution — capture it
49
+ // now so deferred injection (after the am-consent event) keeps placement.
50
+ 'var s=document.currentScript;' +
51
+ 'function g(){try{return window.localStorage.getItem("am_cookie_consent")==="all"}catch(e){return false}}' +
52
+ 'var d=false;' +
53
+ 'function i(){if(d)return;d=true;' +
54
+ 'var f=document.createRange().createContextualFragment(p);' +
55
+ 'if(s&&s.parentNode){s.parentNode.insertBefore(f,s.nextSibling)}else{document.body.appendChild(f)}}' +
56
+ 'if(g()){i();return}' +
57
+ 'window.addEventListener("am-consent",function(e){if((e.detail&&e.detail.level)==="all")i()});' +
58
+ '})()' +
59
+ '</script>');
60
+ }
61
+ function deliverableCode(tag) {
62
+ let code = tag.code;
63
+ if (tag.loadAsync)
64
+ code = applyAsyncToScriptCode(code);
65
+ const category = tag.consentCategory ?? 'none';
66
+ if (category !== 'none')
67
+ code = wrapCodeInConsentGate(code, category);
68
+ return code;
69
+ }
10
70
  /**
11
71
  * Resolve enabled script tags for a public URL path, grouped by placement.
12
72
  * Shared by the public API route and Next.js site layouts.
13
73
  */
14
- export async function resolveScriptTagsForPath(db, pathParam) {
74
+ export async function resolveScriptTagsForPath(db, pathParam, options = {}) {
15
75
  if (!hasScriptTagModel(db))
16
76
  return EMPTY;
17
77
  const normalizedPath = normalizeScriptTagPath(pathParam);
@@ -21,6 +81,8 @@ export async function resolveScriptTagsForPath(db, pathParam) {
21
81
  });
22
82
  const matched = [];
23
83
  for (const tag of tags) {
84
+ if (options.preview && tag.disableInPreview !== false)
85
+ continue;
24
86
  if (tag.scope === 'site') {
25
87
  matched.push(tag);
26
88
  continue;
@@ -49,7 +111,7 @@ export async function resolveScriptTagsForPath(db, pathParam) {
49
111
  for (const tag of matched) {
50
112
  const bucket = grouped[tag.placement];
51
113
  if (bucket)
52
- bucket.push(tag.code);
114
+ bucket.push(deliverableCode(tag));
53
115
  }
54
116
  return grouped;
55
117
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/script-tags/resolve.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAA;AAQ1D,MAAM,KAAK,GAAuB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;AAE7E,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,CAAA;IACvC,OAAO,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAa;IACtC,OAAO,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,KAAK,UAAU,CAAA;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,EAAa,EACb,SAAiB;IAEjB,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAA;IAExC,MAAM,cAAc,GAAG,sBAAsB,CAAC,SAAS,CAAC,CAAA;IAExD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC;QACvC,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QACxB,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;KAC7B,CAAC,CAAA;IAEF,MAAM,OAAO,GAA+C,EAAE,CAAA;IAC9D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACjB,SAAQ;QACV,CAAC;QACD,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9D,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAA;gBAC3C,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;oBACrE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;oBACjB,MAAK;gBACP,CAAC;YACH,CAAC;YACD,SAAQ;QACV,CAAC;QACD,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3D,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAA;gBAC3C,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;oBAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;oBACjB,MAAK;gBACP,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAuB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;IAC/E,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAqC,CAAC,CAAA;QACjE,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/script-tags/resolve.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAA;AAE1D,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,MAAM;IACN,WAAW;IACX,WAAW;IACX,YAAY;IACZ,aAAa;CACL,CAAA;AAkBV,MAAM,KAAK,GAAuB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;AAE7E,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,CAAA;IACvC,OAAO,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,SAAS,iBAAiB,CAAC,EAAa;IACtC,OAAO,OAAO,EAAE,CAAC,SAAS,EAAE,QAAQ,KAAK,UAAU,CAAA;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,KAAyB,EAAE,EAAE;QAC/E,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE,CAAA;QAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAA;QAC9C,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAA;QAC5E,OAAO,gBAAgB,QAAQ,GAAG,CAAA;IACpC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,QAAgB;IAClE,6EAA6E;IAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IAC7D,OAAO,CACL,kCAAkC,QAAQ,IAAI;QAC9C,cAAc;QACd,SAAS,OAAO,GAAG;QACnB,0EAA0E;QAC1E,0EAA0E;QAC1E,+BAA+B;QAC/B,0GAA0G;QAC1G,cAAc;QACd,kCAAkC;QAClC,2DAA2D;QAC3D,oGAAoG;QACpG,qBAAqB;QACrB,+FAA+F;QAC/F,MAAM;QACN,WAAW,CACZ,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CAAC,GAIxB;IACC,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,CAAA;IACnB,IAAI,GAAG,CAAC,SAAS;QAAE,IAAI,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,IAAI,MAAM,CAAA;IAC9C,IAAI,QAAQ,KAAK,MAAM;QAAE,IAAI,GAAG,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACrE,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,EAAa,EACb,SAAiB,EACjB,UAAoC,EAAE;IAEtC,IAAI,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAA;IAExC,MAAM,cAAc,GAAG,sBAAsB,CAAC,SAAS,CAAC,CAAA;IAExD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC;QACvC,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QACxB,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;KAC7B,CAAC,CAAA;IAEF,MAAM,OAAO,GAKR,EAAE,CAAA;IACP,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC,gBAAgB,KAAK,KAAK;YAAE,SAAQ;QAC/D,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;YACjB,SAAQ;QACV,CAAC;QACD,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9D,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAA;gBAC3C,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;oBACrE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;oBACjB,MAAK;gBACP,CAAC;YACH,CAAC;YACD,SAAQ;QACV,CAAC;QACD,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3D,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAA;gBAC3C,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;oBAC5B,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;oBACjB,MAAK;gBACP,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAuB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;IAC/E,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAqC,CAAC,CAAA;QACjE,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/C,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actuate-media/cms-core",
3
- "version": "0.85.3",
3
+ "version": "0.87.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/actuate-media/actuatecms.git",
@@ -0,0 +1,8 @@
1
+ -- Design-parity delivery controls for script tags:
2
+ -- loadAsync — inject `async` into external <script src> tags at resolve time
3
+ -- disableInPreview — skip the tag when rendering inside CMS preview mode
4
+ -- consentCategory — cookie-consent gate (none | analytics | marketing | functional | preferences)
5
+ ALTER TABLE "actuate_script_tags"
6
+ ADD COLUMN "loadAsync" BOOLEAN NOT NULL DEFAULT false,
7
+ ADD COLUMN "disableInPreview" BOOLEAN NOT NULL DEFAULT true,
8
+ ADD COLUMN "consentCategory" TEXT NOT NULL DEFAULT 'none';
@@ -867,16 +867,22 @@ model Invite {
867
867
  }
868
868
 
869
869
  model ScriptTag {
870
- id String @id @default(cuid())
871
- name String
872
- code String @db.Text
873
- placement String
874
- scope String
875
- targetPaths String[]
876
- priority Int @default(100)
877
- enabled Boolean @default(true)
878
- createdAt DateTime @default(now())
879
- updatedAt DateTime @updatedAt
870
+ id String @id @default(cuid())
871
+ name String
872
+ code String @db.Text
873
+ placement String
874
+ scope String
875
+ targetPaths String[]
876
+ priority Int @default(100)
877
+ enabled Boolean @default(true)
878
+ /// Inject `async` into external <script src> tags so they don't block render.
879
+ loadAsync Boolean @default(false)
880
+ /// Skip this tag when the page is rendered inside CMS preview mode.
881
+ disableInPreview Boolean @default(true)
882
+ /// Cookie-consent category gate: none | analytics | marketing | functional | preferences.
883
+ consentCategory String @default("none")
884
+ createdAt DateTime @default(now())
885
+ updatedAt DateTime @updatedAt
880
886
 
881
887
  @@index([enabled])
882
888
  @@index([placement])