@bison-lab/payload-core 3.12.0 → 3.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as documentTitle, r as titleTemplate, t as SHARE_IMAGE_SIZE } from "./share-image-C4ILz4p2.mjs";
2
- import { A as THEME_IDENTITY_FALLBACK, C as THEME_APPEARANCE_FIELD, D as THEME_DOCUMENT_CONTROLS, E as THEME_CONTRAST_REPORT, F as THEME_SECTION_HEADING, I as headingSelectValue, M as THEME_PAIRING_FIELD, N as THEME_PUBLISH_FIELD, O as THEME_FONT_FIELD, P as THEME_SAVE_BUTTON, S as LOOK_FIELD, T as THEME_COLOR_SCALE_FIELD, _ as docFromConfig, a as pageEditorLooks, b as validateSourceIncluded, c as deleteLibraryColor, d as SYSTEM_COLOR_KEYS, f as THEME_APPEARANCE_SLUG, g as THEME_TYPOGRAPHY_SLUG, h as THEME_SLUG, i as PAGE_EDITOR_SYSTEM_KEYS, j as THEME_LIBRARY_FIELD, k as THEME_GREY_SCALE_FIELD, l as rewriteColorToken, m as THEME_IDENTITY_SLUG, n as colorTokenField, o as pageEditorTokens, p as THEME_COLORS_SLUG, r as lookField, s as themeLibraryFromDoc, t as resolveThemeIdentity, u as themeColorKeys, w as THEME_COLOR_FIELD, x as validateThemeHex, y as themeConfigFromDoc } from "./identity-DZOb3_Gk.mjs";
2
+ import { A as THEME_FONT_FIELD, C as validateThemeHex, D as THEME_COLOR_SCALE_FIELD, E as THEME_COLOR_FIELD, F as THEME_PUBLISH_FIELD, I as THEME_SAVE_BUTTON, L as THEME_SECTION_HEADING, M as THEME_IDENTITY_FALLBACK, N as THEME_LIBRARY_FIELD, O as THEME_CONTRAST_REPORT, P as THEME_PAIRING_FIELD, R as headingSelectValue, S as validateSourceIncluded, T as THEME_APPEARANCE_FIELD, _ as THEME_SLUG, a as pageEditorLooks, c as findColorTokens, d as rewriteColorToken, f as themeColorKeys, g as THEME_IDENTITY_SLUG, h as THEME_COLORS_SLUG, i as PAGE_EDITOR_SYSTEM_KEYS, j as THEME_GREY_SCALE_FIELD, k as THEME_DOCUMENT_CONTROLS, l as rewriteColorTokens, m as THEME_APPEARANCE_SLUG, n as colorTokenField, o as pageEditorTokens, p as SYSTEM_COLOR_KEYS, r as lookField, s as themeLibraryFromDoc, t as resolveThemeIdentity, u as deleteLibraryColor, v as THEME_TYPOGRAPHY_SLUG, w as LOOK_FIELD, x as themeConfigFromDoc, y as docFromConfig } from "./identity-LAzSeorC.mjs";
3
3
  import { seoPlugin as seoPlugin$1 } from "@payloadcms/plugin-seo";
4
4
  import { catalog } from "@bison-lab/fonts";
5
5
  import { DESTRUCTIVE_SCALE_HEX, SHADE_STEPS, presetHints } from "@bison-lab/tokens";
@@ -164,6 +164,145 @@ const APPEARANCE_CHOICES = {
164
164
  }
165
165
  };
166
166
  //#endregion
167
+ //#region src/theme/color-usages.ts
168
+ /** Theme-store REST path. LibraryField calls `/api/globals/theme` + this. */
169
+ const COLOR_USAGES_PATH = "/color-usages";
170
+ /**
171
+ * Walk every current document in the named collections and globals
172
+ * (drafts included). A color only on an unpublished page is still in use.
173
+ */
174
+ async function findColorUsages(payload, scopes, key) {
175
+ const usages = [];
176
+ for (const collection of scopes?.collections ?? []) for await (const doc of eachCollectionDoc(payload, collection)) for (const hit of findColorTokens(doc, key)) usages.push({
177
+ collection,
178
+ id: idOf(doc),
179
+ path: hit.path,
180
+ token: hit.token
181
+ });
182
+ for (const slug of scopes?.globals ?? []) {
183
+ const doc = await payload.findGlobal({
184
+ slug,
185
+ draft: true,
186
+ depth: 0,
187
+ overrideAccess: true
188
+ });
189
+ if (!doc) continue;
190
+ for (const hit of findColorTokens(doc, key)) usages.push({
191
+ global: slug,
192
+ path: hit.path,
193
+ token: hit.token
194
+ });
195
+ }
196
+ return usages;
197
+ }
198
+ async function rewriteColorUsages(payload, scopes, fromKey, toKey) {
199
+ for (const collection of scopes?.collections ?? []) for await (const doc of eachCollectionDoc(payload, collection)) {
200
+ if (findColorTokens(doc, fromKey).length === 0) continue;
201
+ const { id: _id, ...data } = rewriteColorTokens(doc, fromKey, toKey);
202
+ const id = idOf(doc);
203
+ if (id == null) continue;
204
+ await payload.update({
205
+ collection,
206
+ id,
207
+ data,
208
+ draft: true,
209
+ overrideAccess: true
210
+ });
211
+ }
212
+ for (const slug of scopes?.globals ?? []) {
213
+ const doc = await payload.findGlobal({
214
+ slug,
215
+ draft: true,
216
+ depth: 0,
217
+ overrideAccess: true
218
+ });
219
+ if (!doc || findColorTokens(doc, fromKey).length === 0) continue;
220
+ await payload.updateGlobal({
221
+ slug,
222
+ data: rewriteColorTokens(doc, fromKey, toKey),
223
+ draft: true,
224
+ overrideAccess: true
225
+ });
226
+ }
227
+ }
228
+ async function replacementKeys(payload, except) {
229
+ return pageEditorLooks(await payload.findGlobal({
230
+ slug: THEME_SLUG,
231
+ depth: 0,
232
+ overrideAccess: true
233
+ })).map((look) => look.value).filter((key) => key !== except);
234
+ }
235
+ async function* eachCollectionDoc(payload, collection) {
236
+ let page = 1;
237
+ for (;;) {
238
+ const result = await payload.find({
239
+ collection,
240
+ draft: true,
241
+ depth: 0,
242
+ limit: 100,
243
+ page,
244
+ overrideAccess: true
245
+ });
246
+ for (const doc of result.docs) yield doc;
247
+ const totalPages = result.totalPages ?? (result.docs.length < 100 ? page : page + 1);
248
+ if (page >= totalPages || result.docs.length === 0) break;
249
+ page += 1;
250
+ }
251
+ }
252
+ function idOf(doc) {
253
+ const id = doc.id;
254
+ return typeof id === "string" || typeof id === "number" ? id : void 0;
255
+ }
256
+ //#endregion
257
+ //#region src/theme/color-usage-endpoints.ts
258
+ function colorUsageEndpoints(access, scopes) {
259
+ return [{
260
+ path: COLOR_USAGES_PATH,
261
+ method: "get",
262
+ handler: async (req) => {
263
+ const denied = await denyUnlessUpdate(req, access.update);
264
+ if (denied) return denied;
265
+ const key = queryKey(req);
266
+ if (!key) return Response.json({ error: "key is required" }, { status: 400 });
267
+ const usages = await findColorUsages(req.payload, scopes, key);
268
+ return Response.json({ usages });
269
+ }
270
+ }, {
271
+ path: COLOR_USAGES_PATH,
272
+ method: "post",
273
+ handler: async (req) => {
274
+ const denied = await denyUnlessUpdate(req, access.update);
275
+ if (denied) return denied;
276
+ const body = await readJson(req);
277
+ const key = typeof body?.key === "string" ? body.key : "";
278
+ const replacement = typeof body?.replacement === "string" ? body.replacement : "";
279
+ if (!key || !replacement) return Response.json({ error: "key and replacement are required" }, { status: 400 });
280
+ if (!(await replacementKeys(req.payload, key)).includes(replacement)) return Response.json({ error: "Replacement must be a different live system or remaining custom key" }, { status: 400 });
281
+ await rewriteColorUsages(req.payload, scopes, key, replacement);
282
+ return Response.json({ ok: true });
283
+ }
284
+ }];
285
+ }
286
+ async function denyUnlessUpdate(req, update) {
287
+ if (await update({ req })) return null;
288
+ return Response.json({ error: "Forbidden" }, { status: 403 });
289
+ }
290
+ function queryKey(req) {
291
+ const fromQuery = req.query?.key;
292
+ if (typeof fromQuery === "string") return fromQuery;
293
+ if (Array.isArray(fromQuery) && typeof fromQuery[0] === "string") return fromQuery[0];
294
+ if (req.url) try {
295
+ return new URL(req.url, "http://local").searchParams.get("key") ?? "";
296
+ } catch {
297
+ return "";
298
+ }
299
+ return "";
300
+ }
301
+ async function readJson(req) {
302
+ if (typeof req.json === "function") return req.json();
303
+ return null;
304
+ }
305
+ //#endregion
167
306
  //#region src/theme/publish.ts
168
307
  /**
169
308
  * Publishing a child writes that slice only. Other responsibilities stay
@@ -570,7 +709,7 @@ function pageHooks(child) {
570
709
  function createTheme(options) {
571
710
  const access = requireAccess$1(options);
572
711
  const destructive = requireDestructive(options);
573
- const { seed, fonts = catalog, fontsBaseUrl = "/fonts", contrastTarget = 7, logo, identity, onPublish } = options;
712
+ const { seed, fonts = catalog, fontsBaseUrl = "/fonts", contrastTarget = 7, logo, identity, onPublish, colorUsages } = options;
574
713
  const adminCustom = {
575
714
  contrastTarget,
576
715
  fontsBaseUrl,
@@ -599,6 +738,7 @@ function createTheme(options) {
599
738
  ...lookFields,
600
739
  ...markFields
601
740
  ],
741
+ endpoints: colorUsageEndpoints(access, colorUsages),
602
742
  hooks: { afterChange: [async ({ doc }) => {
603
743
  if (!onPublish) return;
604
744
  const themeDoc = doc;
@@ -806,9 +946,20 @@ const CAPABILITIES = [
806
946
  function isRole(value) {
807
947
  return typeof value === "string" && ROLES.includes(value);
808
948
  }
949
+ /** Lowercase slug: `editor`, `site-editor`. Empty and punctuation are out. */
950
+ function isRoleSlug(value) {
951
+ return typeof value === "string" && /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value);
952
+ }
953
+ function roleLabel(role, label) {
954
+ const trimmed = typeof label === "string" ? label.trim() : "";
955
+ if (trimmed) return trimmed;
956
+ return isRole(role) ? ROLE_LABELS[role] : role;
957
+ }
809
958
  //#endregion
810
959
  //#region src/roles/matrix.ts
811
960
  const ROLES_SLUG = "roles";
961
+ const ROLES_GLOBAL_DESCRIPTION = "Who may do what. Drag to change rank.";
962
+ const ROLES_FIELD_DESCRIPTION = "Drag to change rank. Ticks are Content, Brand, Publish, and Users.";
812
963
  /**
813
964
  * Default rank and ticks. Brand is Designer only. The API tab is not a
814
965
  * column — it is locked to Developer in `isDeveloper`.
@@ -845,42 +996,64 @@ const DEFAULT_ROLE_MATRIX = [
845
996
  ];
846
997
  const DEVELOPER_DESCRIPTION = "Everything Admin can do, plus the document API tab.";
847
998
  const LAST_USERS_TICK_MESSAGE = "Keep Users ticked on at least one of Admin or Developer.";
999
+ const MISSING_SEED_ROLES_MESSAGE = "Roles must include Developer, Admin, Designer, and Author.";
1000
+ const DUPLICATE_ROLE_SLUG_MESSAGE = "Each role needs a unique slug.";
848
1001
  const CAPABILITY_LABELS = {
849
1002
  content: "Content",
850
1003
  brand: "Brand",
851
1004
  publish: "Publish",
852
1005
  users: "Users"
853
1006
  };
854
- function defaultRolesFieldValue() {
855
- return DEFAULT_ROLE_MATRIX.map((row) => ({
1007
+ function seedRoleRows(extras = []) {
1008
+ return [...DEFAULT_ROLE_MATRIX.map((row) => ({
1009
+ ...row,
1010
+ label: isRole(row.role) ? ROLE_LABELS[row.role] : row.role
1011
+ })), ...extras.map((extra) => ({ ...extra }))];
1012
+ }
1013
+ function defaultRolesFieldValue(extras = []) {
1014
+ return seedRoleRows(extras).map((row) => ({
856
1015
  id: row.role,
857
1016
  ...row
858
1017
  }));
859
1018
  }
1019
+ function readRoleRow(item) {
1020
+ const role = "role" in item ? item.role : void 0;
1021
+ if (typeof role !== "string" || role.trim() === "") return { error: DUPLICATE_ROLE_SLUG_MESSAGE };
1022
+ if (!isRoleSlug(role)) return { error: DUPLICATE_ROLE_SLUG_MESSAGE };
1023
+ return {
1024
+ id: role,
1025
+ role,
1026
+ label: roleLabel(role, "label" in item && typeof item.label === "string" ? item.label : void 0),
1027
+ content: Boolean("content" in item && item.content),
1028
+ brand: Boolean("brand" in item && item.brand),
1029
+ publish: Boolean("publish" in item && item.publish),
1030
+ users: Boolean("users" in item && item.users)
1031
+ };
1032
+ }
860
1033
  function parseRolesMatrix(value) {
861
1034
  if (!Array.isArray(value)) return {
862
1035
  ok: false,
863
- message: "Roles must be Developer, Admin, Designer, and Author."
1036
+ message: MISSING_SEED_ROLES_MESSAGE
864
1037
  };
865
1038
  const rows = [];
866
1039
  const seen = /* @__PURE__ */ new Set();
867
1040
  for (const item of value) {
868
1041
  if (!item || typeof item !== "object") continue;
869
- const role = "role" in item ? item.role : void 0;
870
- if (!isRole(role) || seen.has(role)) continue;
871
- seen.add(role);
872
- rows.push({
873
- id: role,
874
- role,
875
- content: Boolean("content" in item && item.content),
876
- brand: Boolean("brand" in item && item.brand),
877
- publish: Boolean("publish" in item && item.publish),
878
- users: Boolean("users" in item && item.users)
879
- });
1042
+ const parsed = readRoleRow(item);
1043
+ if ("error" in parsed) return {
1044
+ ok: false,
1045
+ message: parsed.error
1046
+ };
1047
+ if (seen.has(parsed.role)) return {
1048
+ ok: false,
1049
+ message: DUPLICATE_ROLE_SLUG_MESSAGE
1050
+ };
1051
+ seen.add(parsed.role);
1052
+ rows.push(parsed);
880
1053
  }
881
- if (rows.length !== ROLES.length || ROLES.some((role) => !seen.has(role))) return {
1054
+ if (ROLES.some((role) => !seen.has(role))) return {
882
1055
  ok: false,
883
- message: "Roles must be Developer, Admin, Designer, and Author."
1056
+ message: MISSING_SEED_ROLES_MESSAGE
884
1057
  };
885
1058
  return {
886
1059
  ok: true,
@@ -898,16 +1071,19 @@ function validateRolesMatrix(value) {
898
1071
  }
899
1072
  /**
900
1073
  * Developer is exclusive. Admin does not swallow Designer: both store.
1074
+ * Unknown slugs (dropped catalogue keys such as `approver`) fall away unless
1075
+ * they appear on the matrix.
901
1076
  */
902
- function normalizeStoredRoles(value) {
1077
+ function normalizeStoredRoles(value, matrix = DEFAULT_ROLE_MATRIX) {
903
1078
  if (!Array.isArray(value)) return [];
904
- const roles = [...new Set(value.filter(isRole))];
1079
+ const allowed = new Set(matrix.map((row) => row.role));
1080
+ const roles = [...new Set(value.filter((entry) => typeof entry === "string" && allowed.has(entry)))];
905
1081
  if (roles.includes("developer")) return ["developer"];
906
1082
  return roles;
907
1083
  }
908
1084
  function roleSelectOptions(matrix = DEFAULT_ROLE_MATRIX) {
909
1085
  return matrix.map((row) => ({
910
- label: ROLE_LABELS[row.role],
1086
+ label: roleLabel(row.role, row.label),
911
1087
  value: row.role
912
1088
  }));
913
1089
  }
@@ -918,9 +1094,22 @@ function roleDescription(role, matrix = DEFAULT_ROLE_MATRIX) {
918
1094
  if (!row) return "No capabilities";
919
1095
  return CAPABILITIES.filter((capability) => row[capability]).map((capability) => CAPABILITY_LABELS[capability]).join(", ") || "No capabilities";
920
1096
  }
1097
+ /**
1098
+ * Seed ticks and package labels come back; extra rows stay as they are.
1099
+ */
1100
+ function resetRolesMatrix(current) {
1101
+ const extras = [];
1102
+ if (Array.isArray(current)) for (const item of current) {
1103
+ if (!item || typeof item !== "object") continue;
1104
+ const parsed = readRoleRow(item);
1105
+ if ("error" in parsed || isRole(parsed.role)) continue;
1106
+ extras.push(parsed);
1107
+ }
1108
+ return [...defaultRolesFieldValue(), ...extras];
1109
+ }
921
1110
  //#endregion
922
1111
  //#region src/roles/access.ts
923
- function isAccessArgs(value) {
1112
+ function isAccessArgs$1(value) {
924
1113
  return typeof value === "object" && value !== null && "req" in value;
925
1114
  }
926
1115
  function storedRoles(user) {
@@ -943,9 +1132,10 @@ function hasCapability(user, capability, matrix = DEFAULT_ROLE_MATRIX) {
943
1132
  * empty, missing, or unreadable. Always override-access so a Designer
944
1133
  * evaluating Theme does not have to read Settings → Roles.
945
1134
  */
946
- async function getRolesMatrix(req = {}) {
1135
+ async function getRolesMatrix(req = {}, extras = []) {
1136
+ const fallback = seedRoleRows(extras);
947
1137
  const findGlobal = req.payload?.findGlobal;
948
- if (typeof findGlobal !== "function") return [...DEFAULT_ROLE_MATRIX];
1138
+ if (typeof findGlobal !== "function") return fallback;
949
1139
  try {
950
1140
  const doc = await findGlobal({
951
1141
  slug: ROLES_SLUG,
@@ -953,14 +1143,14 @@ async function getRolesMatrix(req = {}) {
953
1143
  req
954
1144
  });
955
1145
  const parsed = parseRolesMatrix(doc && typeof doc === "object" && "roles" in doc ? doc.roles : void 0);
956
- return parsed.ok ? parsed.rows : [...DEFAULT_ROLE_MATRIX];
1146
+ return parsed.ok ? parsed.rows : fallback;
957
1147
  } catch {
958
- return [...DEFAULT_ROLE_MATRIX];
1148
+ return fallback;
959
1149
  }
960
1150
  }
961
1151
  function capabilityPredicate(capability) {
962
1152
  function predicate(userOrArgs, matrix) {
963
- if (isAccessArgs(userOrArgs)) {
1153
+ if (isAccessArgs$1(userOrArgs)) {
964
1154
  const user = userOrArgs.req.user;
965
1155
  if (matrix) return hasCapability(user, capability, matrix);
966
1156
  return getRolesMatrix(userOrArgs.req).then((rows) => hasCapability(user, capability, rows));
@@ -976,10 +1166,10 @@ const canPublish = capabilityPredicate("publish");
976
1166
  const isAdmin = capabilityPredicate("users");
977
1167
  /** This role id currently has the Users tick. */
978
1168
  function isPrivilegedRole(role, matrix = DEFAULT_ROLE_MATRIX) {
979
- return isRole(role) && matrix.some((row) => row.role === role && row.users);
1169
+ return typeof role === "string" && matrix.some((row) => row.role === role && row.users);
980
1170
  }
981
1171
  function isAuthenticated(userOrArgs) {
982
- if (isAccessArgs(userOrArgs)) return Boolean(userOrArgs.req.user);
1172
+ if (isAccessArgs$1(userOrArgs)) return Boolean(userOrArgs.req.user);
983
1173
  return Boolean(userOrArgs);
984
1174
  }
985
1175
  const isAdminOrSelf = async ({ req }) => {
@@ -998,93 +1188,431 @@ function isDeveloperTab({ req }) {
998
1188
  //#endregion
999
1189
  //#region src/roles/fields.ts
1000
1190
  const ROLES_MATRIX_FIELD = "@bison-lab/payload-core/admin#RolesMatrixField";
1191
+ const ROLES_ROW_LABEL = "@bison-lab/payload-core/admin#RolesRowLabel";
1192
+ const ROLE_SLUG_FIELD = "@bison-lab/payload-core/admin#RoleSlugField";
1001
1193
  const ROLES_FIELD = "@bison-lab/payload-core/admin#RolesField";
1002
1194
  //#endregion
1003
1195
  //#region src/roles/global.ts
1196
+ const roleSlugValidate = (value, options) => {
1197
+ if (options.operation === "update" && isRole(options.previousValue)) return value === options.previousValue || "Each role needs a unique slug.";
1198
+ if (typeof value !== "string" || value === "") return DUPLICATE_ROLE_SLUG_MESSAGE;
1199
+ return isRoleSlug(value) || "Each role needs a unique slug.";
1200
+ };
1004
1201
  /**
1005
1202
  * Settings → Roles. Rank is the array order (Payload's drag handle). Ticks
1006
1203
  * are Content, Brand, Publish, Users. The API tab is not a column.
1007
1204
  */
1008
- function createRoles() {
1205
+ function createRoles({ extras = [] } = {}) {
1206
+ const rolesField = {
1207
+ name: "roles",
1208
+ type: "array",
1209
+ label: "Roles",
1210
+ labels: {
1211
+ singular: "Role",
1212
+ plural: "Roles"
1213
+ },
1214
+ minRows: 4,
1215
+ required: true,
1216
+ defaultValue: defaultRolesFieldValue(extras),
1217
+ validate: validateRolesMatrix,
1218
+ admin: {
1219
+ components: {
1220
+ Field: ROLES_MATRIX_FIELD,
1221
+ RowLabel: ROLES_ROW_LABEL
1222
+ },
1223
+ description: ROLES_FIELD_DESCRIPTION,
1224
+ initCollapsed: false
1225
+ },
1226
+ fields: [
1227
+ {
1228
+ name: "role",
1229
+ type: "text",
1230
+ label: "Slug",
1231
+ required: true,
1232
+ validate: roleSlugValidate,
1233
+ hooks: { beforeChange: [({ value, previousValue }) => isRole(previousValue) ? previousValue : value] },
1234
+ admin: { components: { Field: ROLE_SLUG_FIELD } }
1235
+ },
1236
+ {
1237
+ name: "label",
1238
+ type: "text",
1239
+ label: "Name",
1240
+ required: true
1241
+ },
1242
+ {
1243
+ name: "content",
1244
+ type: "checkbox",
1245
+ label: "Content"
1246
+ },
1247
+ {
1248
+ name: "brand",
1249
+ type: "checkbox",
1250
+ label: "Brand"
1251
+ },
1252
+ {
1253
+ name: "publish",
1254
+ type: "checkbox",
1255
+ label: "Publish"
1256
+ },
1257
+ {
1258
+ name: "users",
1259
+ type: "checkbox",
1260
+ label: "Users"
1261
+ }
1262
+ ]
1263
+ };
1009
1264
  return {
1010
1265
  slug: ROLES_SLUG,
1011
1266
  label: "Roles",
1012
1267
  admin: {
1013
1268
  group: "Settings",
1014
1269
  hidden: ({ user }) => !isAdmin(user),
1015
- description: "Who may do what. Drag to change rank. The API tab is locked to Developer."
1270
+ description: ROLES_GLOBAL_DESCRIPTION
1271
+ },
1272
+ access: {
1273
+ read: (args) => isAdmin(args),
1274
+ update: (args) => isAdmin(args)
1275
+ },
1276
+ fields: [rolesField]
1277
+ };
1278
+ }
1279
+ //#endregion
1280
+ //#region src/roles/seed.ts
1281
+ /**
1282
+ * Writes the default matrix (seed four plus any site extras). For a site's
1283
+ * migration `up()`. Never writes a user row — seeding `developer` on a
1284
+ * person stays a site concern.
1285
+ */
1286
+ async function seedRoles(payload, extras = []) {
1287
+ return payload.updateGlobal({
1288
+ slug: ROLES_SLUG,
1289
+ data: { roles: defaultRolesFieldValue(extras) }
1290
+ });
1291
+ }
1292
+ //#endregion
1293
+ //#region src/features/fields.ts
1294
+ const FEATURES_MATRIX_FIELD = "@bison-lab/payload-core/admin#FeaturesMatrixField";
1295
+ //#endregion
1296
+ //#region src/features/types.ts
1297
+ const FEATURES_SLUG = "features";
1298
+ const PACKAGE_FEATURE_SLUGS = [
1299
+ "pages",
1300
+ "media",
1301
+ "theme",
1302
+ "brand-assets",
1303
+ "users",
1304
+ "roles",
1305
+ "features"
1306
+ ];
1307
+ /**
1308
+ * Package catalogue. Locked rows cannot be turned off. Empty Global falls
1309
+ * back to the named capability (Content → Pages/Media, Brand → Theme /
1310
+ * Brand assets, Users → Users/Roles/Features).
1311
+ */
1312
+ const PACKAGE_FEATURES = [
1313
+ {
1314
+ slug: "pages",
1315
+ label: "Pages",
1316
+ fallback: "content",
1317
+ locked: false
1318
+ },
1319
+ {
1320
+ slug: "media",
1321
+ label: "Media",
1322
+ fallback: "content",
1323
+ locked: false
1324
+ },
1325
+ {
1326
+ slug: "theme",
1327
+ label: "Theme",
1328
+ fallback: "brand",
1329
+ locked: false
1330
+ },
1331
+ {
1332
+ slug: "brand-assets",
1333
+ label: "Brand assets",
1334
+ fallback: "brand",
1335
+ locked: false
1336
+ },
1337
+ {
1338
+ slug: "users",
1339
+ label: "Users",
1340
+ fallback: "users",
1341
+ locked: true
1342
+ },
1343
+ {
1344
+ slug: "roles",
1345
+ label: "Roles",
1346
+ fallback: "users",
1347
+ locked: true
1348
+ },
1349
+ {
1350
+ slug: "features",
1351
+ label: "Features",
1352
+ fallback: "users",
1353
+ locked: true
1354
+ }
1355
+ ];
1356
+ function isPackageFeatureSlug(value) {
1357
+ return typeof value === "string" && PACKAGE_FEATURE_SLUGS.includes(value);
1358
+ }
1359
+ function isFeatureSlug(value) {
1360
+ return typeof value === "string" && /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value);
1361
+ }
1362
+ function isLockedFeature(slug, locked) {
1363
+ if (locked === true) return true;
1364
+ return PACKAGE_FEATURES.find((feature) => feature.slug === slug)?.locked === true;
1365
+ }
1366
+ //#endregion
1367
+ //#region src/features/matrix.ts
1368
+ const MISSING_PACKAGE_FEATURES_MESSAGE = "Features must include Pages, Media, Theme, Brand assets, Users, Roles, and Features.";
1369
+ const LOCKED_FEATURE_MESSAGE = "Users, Roles, and Features cannot be turned off. Developer stays allowed on every row.";
1370
+ function featureCatalogue(extras = []) {
1371
+ return [...PACKAGE_FEATURES.map((feature) => ({ ...feature })), ...extras.map((extra) => ({
1372
+ slug: extra.slug,
1373
+ label: extra.label,
1374
+ fallback: null,
1375
+ locked: false
1376
+ }))];
1377
+ }
1378
+ function rolesForFeature(feature, matrix) {
1379
+ const allowed = new Set(["developer"]);
1380
+ if (feature.locked) {
1381
+ for (const row of matrix) allowed.add(row.role);
1382
+ return [...allowed];
1383
+ }
1384
+ if (feature.fallback) {
1385
+ for (const row of matrix) if (row[feature.fallback]) allowed.add(row.role);
1386
+ }
1387
+ return [...allowed];
1388
+ }
1389
+ function defaultFeaturesFieldValue(extras = [], matrix = DEFAULT_ROLE_MATRIX) {
1390
+ return featureCatalogue(extras).map((feature) => ({
1391
+ id: feature.slug,
1392
+ slug: feature.slug,
1393
+ label: feature.label,
1394
+ locked: feature.locked,
1395
+ roles: rolesForFeature(feature, matrix)
1396
+ }));
1397
+ }
1398
+ function parseFeaturesMatrix(value) {
1399
+ if (!Array.isArray(value) || value.length === 0) return {
1400
+ ok: false,
1401
+ message: MISSING_PACKAGE_FEATURES_MESSAGE
1402
+ };
1403
+ const rows = [];
1404
+ const seen = /* @__PURE__ */ new Set();
1405
+ for (const item of value) {
1406
+ if (!item || typeof item !== "object") continue;
1407
+ const slug = "slug" in item ? item.slug : void 0;
1408
+ if (!isFeatureSlug(slug) || seen.has(slug)) continue;
1409
+ seen.add(slug);
1410
+ const rawRoles = "roles" in item && Array.isArray(item.roles) ? item.roles : [];
1411
+ const roles = [];
1412
+ for (const role of rawRoles) if (typeof role === "string" && !roles.includes(role)) roles.push(role);
1413
+ const label = "label" in item && typeof item.label === "string" ? item.label : slug;
1414
+ rows.push({
1415
+ id: slug,
1416
+ slug,
1417
+ label,
1418
+ locked: isLockedFeature(slug, "locked" in item ? Boolean(item.locked) : void 0),
1419
+ roles
1420
+ });
1421
+ }
1422
+ if (PACKAGE_FEATURES.some((feature) => !seen.has(feature.slug))) return {
1423
+ ok: false,
1424
+ message: MISSING_PACKAGE_FEATURES_MESSAGE
1425
+ };
1426
+ return {
1427
+ ok: true,
1428
+ rows
1429
+ };
1430
+ }
1431
+ function enforceFeatureLocks(rows, matrix = DEFAULT_ROLE_MATRIX) {
1432
+ const roleSlugs = matrix.map((row) => row.role);
1433
+ return rows.map((row) => {
1434
+ const roles = new Set(row.roles ?? []);
1435
+ roles.add("developer");
1436
+ if (isLockedFeature(row.slug, row.locked)) for (const role of roleSlugs) roles.add(role);
1437
+ return {
1438
+ ...row,
1439
+ roles: [...roles]
1440
+ };
1441
+ });
1442
+ }
1443
+ function validateFeaturesMatrix(value, extras = [], matrix = DEFAULT_ROLE_MATRIX) {
1444
+ const parsed = parseFeaturesMatrix(value);
1445
+ if (!parsed.ok) return parsed.message;
1446
+ const allowed = new Set(featureCatalogue(extras).map((feature) => feature.slug));
1447
+ if (parsed.rows.some((row) => !allowed.has(row.slug))) return MISSING_PACKAGE_FEATURES_MESSAGE;
1448
+ for (const row of parsed.rows) {
1449
+ if (!row.roles?.includes("developer")) return LOCKED_FEATURE_MESSAGE;
1450
+ if (isLockedFeature(row.slug, row.locked) && matrix.some((role) => !row.roles?.includes(role.role))) return LOCKED_FEATURE_MESSAGE;
1451
+ }
1452
+ return true;
1453
+ }
1454
+ //#endregion
1455
+ //#region src/features/global.ts
1456
+ /**
1457
+ * Settings → Features. Rows are the catalogue (code). Columns are the
1458
+ * Roles Global. Locked rows and Developer cannot be turned off.
1459
+ */
1460
+ function createFeatures({ extras = [] } = {}) {
1461
+ const catalogue = featureCatalogue(extras);
1462
+ return {
1463
+ slug: FEATURES_SLUG,
1464
+ label: "Features",
1465
+ admin: {
1466
+ group: "Settings",
1467
+ hidden: ({ user }) => !isAdmin(user),
1468
+ description: "Who may use which collections and globals."
1016
1469
  },
1017
1470
  access: {
1018
1471
  read: (args) => isAdmin(args),
1019
1472
  update: (args) => isAdmin(args)
1020
1473
  },
1021
1474
  fields: [{
1022
- name: "roles",
1475
+ name: "features",
1023
1476
  type: "array",
1024
- label: "Roles",
1477
+ label: "Features",
1025
1478
  labels: {
1026
- singular: "Role",
1027
- plural: "Roles"
1479
+ singular: "Feature",
1480
+ plural: "Features"
1028
1481
  },
1029
- minRows: 4,
1030
- maxRows: 4,
1482
+ minRows: catalogue.length,
1483
+ maxRows: catalogue.length,
1031
1484
  required: true,
1032
- defaultValue: defaultRolesFieldValue(),
1033
- validate: validateRolesMatrix,
1485
+ defaultValue: defaultFeaturesFieldValue(extras),
1486
+ validate: async (value, { req }) => {
1487
+ return validateFeaturesMatrix(value, extras, await getRolesMatrix(req));
1488
+ },
1489
+ hooks: { beforeChange: [async ({ value, req }) => {
1490
+ if (!Array.isArray(value)) return value;
1491
+ return enforceFeatureLocks(value, await getRolesMatrix(req));
1492
+ }] },
1034
1493
  admin: {
1035
- components: { Field: ROLES_MATRIX_FIELD },
1036
- description: "Drag to change rank. Ticks are Content, Brand, Publish, and Users. The API tab is locked to Developer.",
1494
+ components: { Field: FEATURES_MATRIX_FIELD },
1495
+ description: "Turn a feature on or off per role. Users, Roles, and Features stay on. Developer is always allowed.",
1037
1496
  initCollapsed: false
1038
1497
  },
1039
1498
  fields: [
1040
1499
  {
1041
- name: "role",
1042
- type: "select",
1043
- label: "Role",
1500
+ name: "slug",
1501
+ type: "text",
1502
+ label: "Slug",
1044
1503
  required: true,
1045
- options: ROLES.map((value) => ({
1046
- label: ROLE_LABELS[value],
1047
- value
1048
- })),
1049
1504
  admin: { readOnly: true }
1050
1505
  },
1051
1506
  {
1052
- name: "content",
1053
- type: "checkbox",
1054
- label: "Content"
1055
- },
1056
- {
1057
- name: "brand",
1058
- type: "checkbox",
1059
- label: "Brand"
1507
+ name: "label",
1508
+ type: "text",
1509
+ label: "Name",
1510
+ required: true,
1511
+ admin: { readOnly: true }
1060
1512
  },
1061
1513
  {
1062
- name: "publish",
1514
+ name: "locked",
1063
1515
  type: "checkbox",
1064
- label: "Publish"
1516
+ label: "Locked",
1517
+ admin: { hidden: true }
1065
1518
  },
1066
1519
  {
1067
- name: "users",
1068
- type: "checkbox",
1069
- label: "Users"
1520
+ name: "roles",
1521
+ type: "json",
1522
+ label: "Roles",
1523
+ required: true
1070
1524
  }
1071
1525
  ]
1072
1526
  }]
1073
1527
  };
1074
1528
  }
1075
1529
  //#endregion
1076
- //#region src/roles/seed.ts
1530
+ //#region src/features/seed.ts
1077
1531
  /**
1078
- * Writes the default matrix. For a site's migration `up()`. Never writes a
1079
- * user row seeding `developer` on a person stays a site concern.
1532
+ * Writes the default switchboard (package rows plus any site extras).
1533
+ * For a site's migration `up()`.
1080
1534
  */
1081
- async function seedRoles(payload) {
1535
+ async function seedFeatures(payload, extras = []) {
1082
1536
  return payload.updateGlobal({
1083
- slug: ROLES_SLUG,
1084
- data: { roles: defaultRolesFieldValue() }
1537
+ slug: FEATURES_SLUG,
1538
+ data: { features: defaultFeaturesFieldValue(extras) }
1085
1539
  });
1086
1540
  }
1087
1541
  //#endregion
1542
+ //#region src/features/access.ts
1543
+ function isAccessArgs(value) {
1544
+ return typeof value === "object" && value !== null && "req" in value;
1545
+ }
1546
+ function fallbackFeature(user, slug, matrix) {
1547
+ const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
1548
+ if (!pack) return false;
1549
+ return hasCapability(user, pack.fallback, matrix);
1550
+ }
1551
+ /**
1552
+ * True when any stored role is on the feature row. An empty grid falls
1553
+ * back to the capability map. Developer is always allowed.
1554
+ */
1555
+ function hasFeature(user, slug, features = null, matrix = DEFAULT_ROLE_MATRIX) {
1556
+ if (isDeveloper(user)) return true;
1557
+ if (!user) return false;
1558
+ if (!features || features.length === 0) return fallbackFeature(user, slug, matrix);
1559
+ const row = features.find((entry) => entry.slug === slug);
1560
+ if (!row) return false;
1561
+ const allowed = row.roles ?? [];
1562
+ return storedRoles(user).some((role) => allowed.includes(role));
1563
+ }
1564
+ /**
1565
+ * Reads the saved Features Global. `null` means empty or unreadable — callers
1566
+ * fall back to the capability map. Always override-access.
1567
+ */
1568
+ async function getFeaturesMatrix(req = {}) {
1569
+ const findGlobal = req.payload?.findGlobal;
1570
+ if (typeof findGlobal !== "function") return null;
1571
+ try {
1572
+ const doc = await findGlobal({
1573
+ slug: FEATURES_SLUG,
1574
+ overrideAccess: true,
1575
+ req
1576
+ });
1577
+ const parsed = parseFeaturesMatrix(doc && typeof doc === "object" && "features" in doc ? doc.features : void 0);
1578
+ if (!parsed.ok) return null;
1579
+ const matrix = await getRolesMatrix(req);
1580
+ return enforceFeatureLocks(parsed.rows, matrix);
1581
+ } catch {
1582
+ return null;
1583
+ }
1584
+ }
1585
+ /**
1586
+ * Access / nav helper for one catalogue slug. Sync against a passed grid;
1587
+ * async when given `req` so it can read both Globals.
1588
+ */
1589
+ function canUseFeature(slug) {
1590
+ function predicate(userOrArgs, features, roles) {
1591
+ if (isAccessArgs(userOrArgs)) {
1592
+ const user = userOrArgs.req.user;
1593
+ if (features !== void 0) return hasFeature(user, slug, features, roles ?? DEFAULT_ROLE_MATRIX);
1594
+ return Promise.all([getFeaturesMatrix(userOrArgs.req), getRolesMatrix(userOrArgs.req)]).then(([grid, matrix]) => hasFeature(user, slug, grid, matrix));
1595
+ }
1596
+ return hasFeature(userOrArgs, slug, features ?? null, roles ?? DEFAULT_ROLE_MATRIX);
1597
+ }
1598
+ return predicate;
1599
+ }
1600
+ /** `admin.hidden`: hide when the login cannot use the feature. */
1601
+ function hideUnlessFeature(slug) {
1602
+ return (args) => {
1603
+ const user = args.user;
1604
+ if (args.req) {
1605
+ const result = canUseFeature(slug)({ req: {
1606
+ ...args.req,
1607
+ user
1608
+ } });
1609
+ if (result instanceof Promise) return result.then((ok) => !ok);
1610
+ return !result;
1611
+ }
1612
+ return !canUseFeature(slug)(user ?? null);
1613
+ };
1614
+ }
1615
+ //#endregion
1088
1616
  //#region src/fields/slug.ts
1089
1617
  /**
1090
1618
  * A stored slug from whatever was typed: lowercased, with any run of
@@ -1184,14 +1712,15 @@ function createPages({ heroBlocks, isReservedSlug, layoutBlocks, previewPath, pr
1184
1712
  "_status",
1185
1713
  "updatedAt"
1186
1714
  ],
1715
+ hidden: hideUnlessFeature("pages"),
1187
1716
  preview: (doc) => previewPath(typeof doc.slug === "string" ? doc.slug : ""),
1188
1717
  ...previewButton ? { components: { edit: { PreviewButton: previewButton } } } : {}
1189
1718
  },
1190
1719
  access: {
1191
1720
  read: authenticatedOrPublished,
1192
1721
  readVersions: isAuthenticated,
1193
- create: canManageContent,
1194
- update: canManageContent,
1722
+ create: canUseFeature("pages"),
1723
+ update: canUseFeature("pages"),
1195
1724
  delete: isAdmin
1196
1725
  },
1197
1726
  versions: {
@@ -1283,15 +1812,21 @@ async function adminCount(req, excluding) {
1283
1812
  });
1284
1813
  return totalDocs;
1285
1814
  }
1286
- const rolesValidate = async (value, options) => {
1287
- const builtIn = select(value, options);
1288
- if (builtIn !== true) return builtIn;
1289
- if (options.operation !== "update" || options.id === void 0) return true;
1290
- const matrix = await getRolesMatrix(options.req);
1291
- if (holdsPrivilegedRole(value, matrix) || !holdsPrivilegedRole(options.previousValue, matrix)) return true;
1292
- return await adminCount(options.req, options.id) > 0 ? true : LAST_ADMIN_DEMOTE_MESSAGE;
1293
- };
1294
- function createUsers({ secureCookies, rolesField }) {
1815
+ function rolesValidate(extras) {
1816
+ return async (value, options) => {
1817
+ const matrix = await getRolesMatrix(options.req, extras);
1818
+ const builtIn = select(value, {
1819
+ ...options,
1820
+ options: roleSelectOptions(matrix)
1821
+ });
1822
+ if (builtIn !== true) return builtIn;
1823
+ if (options.operation !== "update" || options.id === void 0) return true;
1824
+ if (holdsPrivilegedRole(value, matrix) || !holdsPrivilegedRole(options.previousValue, matrix)) return true;
1825
+ return await adminCount(options.req, options.id) > 0 ? true : LAST_ADMIN_DEMOTE_MESSAGE;
1826
+ };
1827
+ }
1828
+ function createUsers({ secureCookies, rolesField, extras = [] }) {
1829
+ const fallback = seedRoleRows(extras);
1295
1830
  return {
1296
1831
  slug: "users",
1297
1832
  auth: {
@@ -1304,6 +1839,7 @@ function createUsers({ secureCookies, rolesField }) {
1304
1839
  }
1305
1840
  },
1306
1841
  admin: {
1842
+ group: "Settings",
1307
1843
  useAsTitle: "email",
1308
1844
  defaultColumns: [
1309
1845
  "email",
@@ -1329,7 +1865,7 @@ function createUsers({ secureCookies, rolesField }) {
1329
1865
  disableErrors: true,
1330
1866
  overrideAccess: true,
1331
1867
  req
1332
- })) || await adminCount(req, id) > 0) return;
1868
+ }), await getRolesMatrix(req, extras)) || await adminCount(req, id) > 0) return;
1333
1869
  throw new APIError(LAST_ADMIN_DELETE_MESSAGE, 400);
1334
1870
  }],
1335
1871
  afterOperation: [async (arg) => {
@@ -1349,14 +1885,17 @@ function createUsers({ secureCookies, rolesField }) {
1349
1885
  hasMany: true,
1350
1886
  required: true,
1351
1887
  defaultValue: ["author"],
1352
- options: roleSelectOptions(),
1888
+ options: roleSelectOptions(fallback),
1353
1889
  admin: {
1354
1890
  components: { Field: rolesField ?? "@bison-lab/payload-core/admin#RolesField" },
1355
1891
  description: "One person can hold several, e.g. Author + Designer."
1356
1892
  },
1357
1893
  access: { update: isAdmin },
1358
- validate: rolesValidate,
1359
- hooks: { beforeValidate: [({ value }) => Array.isArray(value) ? normalizeStoredRoles(value) : value] }
1894
+ validate: rolesValidate(extras),
1895
+ hooks: { beforeValidate: [async ({ value, req }) => {
1896
+ if (!Array.isArray(value)) return value;
1897
+ return normalizeStoredRoles(value, await getRolesMatrix(req, extras));
1898
+ }] }
1360
1899
  }]
1361
1900
  };
1362
1901
  }
@@ -1370,10 +1909,11 @@ function createUsers({ secureCookies, rolesField }) {
1370
1909
  function createMedia({ staticDir = "media", mimeTypes = ["image/*"], imageSizes } = {}) {
1371
1910
  return {
1372
1911
  slug: "media",
1912
+ admin: { hidden: hideUnlessFeature("media") },
1373
1913
  access: {
1374
1914
  read: () => true,
1375
- create: canManageContent,
1376
- update: canManageContent,
1915
+ create: canUseFeature("media"),
1916
+ update: canUseFeature("media"),
1377
1917
  delete: isAdmin
1378
1918
  },
1379
1919
  upload: {
@@ -1432,6 +1972,6 @@ const adminOnlyApiTab = definePlugin({
1432
1972
  })
1433
1973
  });
1434
1974
  //#endregion
1435
- export { BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, LAST_ADMIN_DELETE_MESSAGE, LAST_ADMIN_DEMOTE_MESSAGE, LAST_USERS_TICK_MESSAGE, LOOK_FIELD, PAGE_EDITOR_SYSTEM_KEYS, ROLES, ROLES_FIELD, ROLES_MATRIX_FIELD, ROLES_SLUG, ROLE_LABELS, SHARE_IMAGE_SIZE, SYSTEM_COLOR_KEYS, THEME_APPEARANCE_FIELD, THEME_APPEARANCE_SLUG, THEME_COLORS_SLUG, THEME_COLOR_FIELD, THEME_COLOR_SCALE_FIELD, THEME_CONTRAST_REPORT, THEME_DOCUMENT_CONTROLS, THEME_FONT_FIELD, THEME_GREY_SCALE_FIELD, THEME_IDENTITY_FALLBACK, THEME_IDENTITY_SLUG, THEME_LIBRARY_FIELD, THEME_PAIRING_FIELD, THEME_PREVIEW_BREAKPOINTS, THEME_PUBLISH_FIELD, THEME_SAVE_BUTTON, THEME_SECTION_HEADING, THEME_SLUG, THEME_TYPOGRAPHY_SLUG, adminOnlyApiTab, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, colorTokenField, createBrandAssets, createMedia, createPages, createRoles, createTheme, createUsers, defaultRolesFieldValue, deleteLibraryColor, documentTitle, firstImageIn, getRolesMatrix, hasCapability, hasRole, isAdmin, isAdminOrSelf, isAuthenticated, isDeveloper, isDeveloperTab, isPrivilegedRole, isRole, lookField, noIndexField, normalizeSlug, normalizeStoredRoles, pageEditorLooks, pageEditorTokens, parseRolesMatrix, persistThemeChild, publishThemeChild, resolveThemeIdentity, rewriteColorToken, roleDescription, roleSelectOptions, sanitizeSvg, seedRoles, seedTheme, seoPlugin, slugField, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateRolesMatrix };
1975
+ export { BRAND_ASSETS_MIME_TYPES, BRAND_ASSETS_SLUG, CAPABILITIES, DEFAULT_ROLE_MATRIX, DESCRIPTION_LENGTH, DEVELOPER_DESCRIPTION, DUPLICATE_ROLE_SLUG_MESSAGE, FEATURES_MATRIX_FIELD, FEATURES_SLUG, LAST_ADMIN_DELETE_MESSAGE, LAST_ADMIN_DEMOTE_MESSAGE, LAST_USERS_TICK_MESSAGE, LOCKED_FEATURE_MESSAGE, LOOK_FIELD, MISSING_PACKAGE_FEATURES_MESSAGE, MISSING_SEED_ROLES_MESSAGE, PACKAGE_FEATURES, PACKAGE_FEATURE_SLUGS, PAGE_EDITOR_SYSTEM_KEYS, ROLES, ROLES_FIELD, ROLES_FIELD_DESCRIPTION, ROLES_GLOBAL_DESCRIPTION, ROLES_MATRIX_FIELD, ROLES_ROW_LABEL, ROLES_SLUG, ROLE_LABELS, ROLE_SLUG_FIELD, SHARE_IMAGE_SIZE, SYSTEM_COLOR_KEYS, THEME_APPEARANCE_FIELD, THEME_APPEARANCE_SLUG, THEME_COLORS_SLUG, THEME_COLOR_FIELD, THEME_COLOR_SCALE_FIELD, THEME_CONTRAST_REPORT, THEME_DOCUMENT_CONTROLS, THEME_FONT_FIELD, THEME_GREY_SCALE_FIELD, THEME_IDENTITY_FALLBACK, THEME_IDENTITY_SLUG, THEME_LIBRARY_FIELD, THEME_PAIRING_FIELD, THEME_PREVIEW_BREAKPOINTS, THEME_PUBLISH_FIELD, THEME_SAVE_BUTTON, THEME_SECTION_HEADING, THEME_SLUG, THEME_TYPOGRAPHY_SLUG, adminOnlyApiTab, authenticatedOrPublished, canManageBrand, canManageContent, canPublish, canUseFeature, colorTokenField, createBrandAssets, createFeatures, createMedia, createPages, createRoles, createTheme, createUsers, defaultFeaturesFieldValue, defaultRolesFieldValue, deleteLibraryColor, documentTitle, featureCatalogue, findColorTokens, findColorUsages, firstImageIn, getFeaturesMatrix, getRolesMatrix, hasCapability, hasFeature, hasRole, hideUnlessFeature, isAdmin, isAdminOrSelf, isAuthenticated, isDeveloper, isDeveloperTab, isFeatureSlug, isLockedFeature, isPackageFeatureSlug, isPrivilegedRole, isRole, isRoleSlug, lookField, noIndexField, normalizeSlug, normalizeStoredRoles, pageEditorLooks, pageEditorTokens, parseFeaturesMatrix, parseRolesMatrix, persistThemeChild, publishThemeChild, resetRolesMatrix, resolveThemeIdentity, rewriteColorToken, rewriteColorTokens, rewriteColorUsages, roleDescription, roleLabel, roleSelectOptions, sanitizeSvg, seedFeatures, seedRoleRows, seedRoles, seedTheme, seoPlugin, slugField, storedRoles, themeColorKeys, themeLibraryFromDoc, titleTemplate, truncateAtWord, validateFeaturesMatrix, validateRolesMatrix };
1436
1976
 
1437
1977
  //# sourceMappingURL=index.mjs.map