@bison-lab/payload-core 3.14.0 → 3.15.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/README.md +46 -25
- package/dist/admin.mjs +2 -8
- package/dist/admin.mjs.map +1 -1
- package/dist/{identity-Dwyq8519.mjs → identity-LAzSeorC.mjs} +1 -1
- package/dist/{identity-Dwyq8519.mjs.map → identity-LAzSeorC.mjs.map} +1 -1
- package/dist/index.d.mts +32 -16
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +823 -197
- package/dist/index.mjs.map +1 -1
- package/dist/{looks-Xs6v5vYU.d.mts → looks-C6kTjvk3.d.mts} +1 -1
- package/dist/{looks-Xs6v5vYU.d.mts.map → looks-C6kTjvk3.d.mts.map} +1 -1
- package/dist/metadata.d.mts +1 -1
- package/dist/metadata.mjs +1 -1
- package/dist/{share-image-BlFAJLnd.mjs → share-image-C4ILz4p2.mjs} +1 -1
- package/dist/{share-image-BlFAJLnd.mjs.map → share-image-C4ILz4p2.mjs.map} +1 -1
- package/dist/theme.d.mts +1 -1
- package/dist/theme.mjs +1 -1
- package/dist/{title-sWwcCqHT.d.mts → title-B2-gWQZd.d.mts} +1 -1
- package/dist/{title-sWwcCqHT.d.mts.map → title-B2-gWQZd.d.mts.map} +1 -1
- package/package.json +3 -3
- package/dist/access-B3LglN2J.mjs +0 -662
- package/dist/access-B3LglN2J.mjs.map +0 -1
package/dist/access-B3LglN2J.mjs
DELETED
|
@@ -1,662 +0,0 @@
|
|
|
1
|
-
//#region \0rolldown/runtime.js
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __exportAll = (all, no_symbols) => {
|
|
4
|
-
let target = {};
|
|
5
|
-
for (var name in all) __defProp(target, name, {
|
|
6
|
-
get: all[name],
|
|
7
|
-
enumerable: true
|
|
8
|
-
});
|
|
9
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
-
return target;
|
|
11
|
-
};
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region src/features/types.ts
|
|
14
|
-
const FEATURES_SLUG = "features";
|
|
15
|
-
const FEATURE_GROUPS = [
|
|
16
|
-
"pages",
|
|
17
|
-
"media",
|
|
18
|
-
"theme",
|
|
19
|
-
"users"
|
|
20
|
-
];
|
|
21
|
-
const FEATURE_GROUP_LABELS = {
|
|
22
|
-
pages: "Pages",
|
|
23
|
-
media: "Media",
|
|
24
|
-
theme: "Theme",
|
|
25
|
-
users: "Users"
|
|
26
|
-
};
|
|
27
|
-
const PACKAGE_FEATURE_SLUGS = [
|
|
28
|
-
"content",
|
|
29
|
-
"publish",
|
|
30
|
-
"api-tab",
|
|
31
|
-
"media",
|
|
32
|
-
"theme-colors",
|
|
33
|
-
"theme-typography",
|
|
34
|
-
"theme-appearance",
|
|
35
|
-
"theme-identity",
|
|
36
|
-
"brand-assets",
|
|
37
|
-
"users",
|
|
38
|
-
"roles"
|
|
39
|
-
];
|
|
40
|
-
/**
|
|
41
|
-
* Package catalogue. Features itself is not a row — that screen is
|
|
42
|
-
* Developer-only in code. Empty Global falls back to these defaults.
|
|
43
|
-
*/
|
|
44
|
-
const PACKAGE_FEATURES = [
|
|
45
|
-
{
|
|
46
|
-
slug: "content",
|
|
47
|
-
label: "Content",
|
|
48
|
-
group: "pages",
|
|
49
|
-
defaultReleased: true
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
slug: "publish",
|
|
53
|
-
label: "Publish",
|
|
54
|
-
group: "pages",
|
|
55
|
-
defaultReleased: true
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
slug: "api-tab",
|
|
59
|
-
label: "API tab",
|
|
60
|
-
group: "pages",
|
|
61
|
-
defaultReleased: false
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
slug: "media",
|
|
65
|
-
label: "Media",
|
|
66
|
-
group: "media",
|
|
67
|
-
defaultReleased: true
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
slug: "theme-colors",
|
|
71
|
-
label: "Colors",
|
|
72
|
-
group: "theme",
|
|
73
|
-
defaultReleased: true
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
slug: "theme-typography",
|
|
77
|
-
label: "Typography",
|
|
78
|
-
group: "theme",
|
|
79
|
-
defaultReleased: true
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
slug: "theme-appearance",
|
|
83
|
-
label: "Appearance",
|
|
84
|
-
group: "theme",
|
|
85
|
-
defaultReleased: true
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
slug: "theme-identity",
|
|
89
|
-
label: "Identity",
|
|
90
|
-
group: "theme",
|
|
91
|
-
defaultReleased: true
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
slug: "brand-assets",
|
|
95
|
-
label: "Brand assets",
|
|
96
|
-
group: "theme",
|
|
97
|
-
defaultReleased: true
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
slug: "users",
|
|
101
|
-
label: "Users",
|
|
102
|
-
group: "users",
|
|
103
|
-
defaultReleased: true
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
slug: "roles",
|
|
107
|
-
label: "Roles",
|
|
108
|
-
group: "users",
|
|
109
|
-
defaultReleased: true
|
|
110
|
-
}
|
|
111
|
-
];
|
|
112
|
-
function isFeatureGroupId(value) {
|
|
113
|
-
return typeof value === "string" && FEATURE_GROUPS.includes(value);
|
|
114
|
-
}
|
|
115
|
-
function isPackageFeatureSlug(value) {
|
|
116
|
-
return typeof value === "string" && PACKAGE_FEATURE_SLUGS.includes(value);
|
|
117
|
-
}
|
|
118
|
-
function isFeatureSlug(value) {
|
|
119
|
-
return typeof value === "string" && /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value);
|
|
120
|
-
}
|
|
121
|
-
/** @deprecated Locks are gone; the release switch is the valve. */
|
|
122
|
-
function isLockedFeature(_slug, _locked) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
//#endregion
|
|
126
|
-
//#region src/features/matrix.ts
|
|
127
|
-
const MISSING_PACKAGE_FEATURES_MESSAGE = "Features must include Content, Publish, API tab, Media, Theme screens, Brand assets, Users, and Roles.";
|
|
128
|
-
function featureCatalogue(extras = []) {
|
|
129
|
-
return [...PACKAGE_FEATURES.map((feature) => ({ ...feature })), ...extras.map((extra) => ({
|
|
130
|
-
slug: extra.slug,
|
|
131
|
-
label: extra.label,
|
|
132
|
-
group: extra.group ?? "users",
|
|
133
|
-
defaultReleased: extra.defaultReleased ?? false
|
|
134
|
-
}))];
|
|
135
|
-
}
|
|
136
|
-
function defaultFeaturesFieldValue(extras = []) {
|
|
137
|
-
return featureCatalogue(extras).map((feature) => ({
|
|
138
|
-
id: feature.slug,
|
|
139
|
-
slug: feature.slug,
|
|
140
|
-
label: feature.label,
|
|
141
|
-
group: feature.group,
|
|
142
|
-
released: feature.defaultReleased
|
|
143
|
-
}));
|
|
144
|
-
}
|
|
145
|
-
function stampFeatureCatalogue(value, extras = []) {
|
|
146
|
-
const bySlug = new Map(featureCatalogue(extras).map((feature) => [feature.slug, feature]));
|
|
147
|
-
if (!Array.isArray(value)) return defaultFeaturesFieldValue(extras);
|
|
148
|
-
return value.flatMap((item) => {
|
|
149
|
-
if (!item || typeof item !== "object") return [];
|
|
150
|
-
const slug = "slug" in item && typeof item.slug === "string" ? item.slug : "";
|
|
151
|
-
const feature = bySlug.get(slug);
|
|
152
|
-
if (!feature) return [];
|
|
153
|
-
return [{
|
|
154
|
-
id: feature.slug,
|
|
155
|
-
slug: feature.slug,
|
|
156
|
-
label: feature.label,
|
|
157
|
-
group: feature.group,
|
|
158
|
-
released: Boolean("released" in item && item.released)
|
|
159
|
-
}];
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function parseFeaturesMatrix(value) {
|
|
163
|
-
if (!Array.isArray(value) || value.length === 0) return {
|
|
164
|
-
ok: false,
|
|
165
|
-
message: MISSING_PACKAGE_FEATURES_MESSAGE
|
|
166
|
-
};
|
|
167
|
-
const rows = [];
|
|
168
|
-
const seen = /* @__PURE__ */ new Set();
|
|
169
|
-
for (const item of value) {
|
|
170
|
-
if (!item || typeof item !== "object") continue;
|
|
171
|
-
const slug = "slug" in item ? item.slug : void 0;
|
|
172
|
-
if (!isFeatureSlug(slug) || seen.has(slug)) continue;
|
|
173
|
-
seen.add(slug);
|
|
174
|
-
const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
|
|
175
|
-
const label = "label" in item && typeof item.label === "string" ? item.label : pack?.label ?? slug;
|
|
176
|
-
const group = "group" in item && isFeatureGroupId(item.group) ? item.group : pack?.group ?? "users";
|
|
177
|
-
rows.push({
|
|
178
|
-
id: slug,
|
|
179
|
-
slug,
|
|
180
|
-
label,
|
|
181
|
-
group,
|
|
182
|
-
released: Boolean("released" in item && item.released)
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
if (PACKAGE_FEATURES.some((feature) => !seen.has(feature.slug))) return {
|
|
186
|
-
ok: false,
|
|
187
|
-
message: MISSING_PACKAGE_FEATURES_MESSAGE
|
|
188
|
-
};
|
|
189
|
-
return {
|
|
190
|
-
ok: true,
|
|
191
|
-
rows
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function validateFeaturesMatrix(value, extras = []) {
|
|
195
|
-
const parsed = parseFeaturesMatrix(value);
|
|
196
|
-
if (!parsed.ok) return parsed.message;
|
|
197
|
-
const allowed = new Set(featureCatalogue(extras).map((feature) => feature.slug));
|
|
198
|
-
if (parsed.rows.some((row) => !allowed.has(row.slug))) return MISSING_PACKAGE_FEATURES_MESSAGE;
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
201
|
-
function featureGroupLabel(group) {
|
|
202
|
-
return isFeatureGroupId(group) ? FEATURE_GROUP_LABELS[group] : group;
|
|
203
|
-
}
|
|
204
|
-
function packageFeatureLabel(slug, extras = []) {
|
|
205
|
-
const pack = PACKAGE_FEATURES.find((feature) => feature.slug === slug);
|
|
206
|
-
if (pack) return pack.label;
|
|
207
|
-
return extras.find((feature) => feature.slug === slug)?.label ?? slug;
|
|
208
|
-
}
|
|
209
|
-
function isGroupOnlySlug(slug) {
|
|
210
|
-
return isFeatureGroupId(slug) && !PACKAGE_FEATURES.some((feature) => feature.slug === slug);
|
|
211
|
-
}
|
|
212
|
-
//#endregion
|
|
213
|
-
//#region src/roles/types.ts
|
|
214
|
-
/**
|
|
215
|
-
* The Roles Global a site's generated types will describe. Optional and
|
|
216
|
-
* nullable, no index signature: a generated Global is assignable to this,
|
|
217
|
-
* never the reverse.
|
|
218
|
-
*/
|
|
219
|
-
const ROLES = [
|
|
220
|
-
"developer",
|
|
221
|
-
"admin",
|
|
222
|
-
"designer",
|
|
223
|
-
"author"
|
|
224
|
-
];
|
|
225
|
-
const ROLE_LABELS = {
|
|
226
|
-
developer: "Developer",
|
|
227
|
-
admin: "Admin",
|
|
228
|
-
designer: "Designer",
|
|
229
|
-
author: "Author"
|
|
230
|
-
};
|
|
231
|
-
/** Capability aliases over feature grants. Brand is any Theme-group leaf. */
|
|
232
|
-
const CAPABILITIES = [
|
|
233
|
-
"content",
|
|
234
|
-
"brand",
|
|
235
|
-
"publish",
|
|
236
|
-
"users"
|
|
237
|
-
];
|
|
238
|
-
const THEME_FEATURE_SLUGS = [
|
|
239
|
-
"theme-colors",
|
|
240
|
-
"theme-typography",
|
|
241
|
-
"theme-appearance",
|
|
242
|
-
"theme-identity",
|
|
243
|
-
"brand-assets"
|
|
244
|
-
];
|
|
245
|
-
const CAPABILITY_FEATURES = {
|
|
246
|
-
content: ["content"],
|
|
247
|
-
publish: ["publish"],
|
|
248
|
-
users: ["users"],
|
|
249
|
-
brand: THEME_FEATURE_SLUGS
|
|
250
|
-
};
|
|
251
|
-
const DEFAULT_ROLE_GRANTS = {
|
|
252
|
-
developer: [],
|
|
253
|
-
admin: [
|
|
254
|
-
"content",
|
|
255
|
-
"publish",
|
|
256
|
-
"media",
|
|
257
|
-
"users",
|
|
258
|
-
"roles"
|
|
259
|
-
],
|
|
260
|
-
designer: [
|
|
261
|
-
"content",
|
|
262
|
-
"media",
|
|
263
|
-
...THEME_FEATURE_SLUGS,
|
|
264
|
-
"users",
|
|
265
|
-
"roles"
|
|
266
|
-
],
|
|
267
|
-
author: ["content", "media"]
|
|
268
|
-
};
|
|
269
|
-
function isRole(value) {
|
|
270
|
-
return typeof value === "string" && ROLES.includes(value);
|
|
271
|
-
}
|
|
272
|
-
/** Lowercase slug from a letters-and-spaces name: `designer`, `the-designer`. */
|
|
273
|
-
function isRoleSlug(value) {
|
|
274
|
-
return typeof value === "string" && /^[a-z]+(-[a-z]+)*$/.test(value);
|
|
275
|
-
}
|
|
276
|
-
/** Display name: letters and single spaces only. `The Greatest Designer`. */
|
|
277
|
-
function isRoleName(value) {
|
|
278
|
-
return typeof value === "string" && /^[A-Za-z]+(?: [A-Za-z]+)*$/.test(value.trim());
|
|
279
|
-
}
|
|
280
|
-
/** Strip digits and punctuation as the name is typed. Trailing space stays. */
|
|
281
|
-
function sanitizeRoleNameInput(value) {
|
|
282
|
-
return value.replace(/[^A-Za-z ]/g, "").replace(/ {2,}/g, " ");
|
|
283
|
-
}
|
|
284
|
-
/** Name → stored key. `The Greatest Designer` → `the-greatest-designer`. */
|
|
285
|
-
function slugifyRoleName(value) {
|
|
286
|
-
if (typeof value !== "string") return "";
|
|
287
|
-
return value.trim().toLowerCase().replace(/[^a-z]+/g, "-").replace(/^-|-$/g, "");
|
|
288
|
-
}
|
|
289
|
-
function roleLabel(role, label) {
|
|
290
|
-
const trimmed = typeof label === "string" ? label.trim() : "";
|
|
291
|
-
if (trimmed) return trimmed;
|
|
292
|
-
return isRole(role) ? ROLE_LABELS[role] : role;
|
|
293
|
-
}
|
|
294
|
-
function defaultGrantsForRole(role, extraGrants) {
|
|
295
|
-
if (isRole(role)) return [...DEFAULT_ROLE_GRANTS[role]];
|
|
296
|
-
return extraGrants ? [...extraGrants] : [];
|
|
297
|
-
}
|
|
298
|
-
//#endregion
|
|
299
|
-
//#region src/roles/matrix.ts
|
|
300
|
-
const ROLES_SLUG = "roles";
|
|
301
|
-
const ROLES_GLOBAL_DESCRIPTION = "Who may do what. Drag to change rank.";
|
|
302
|
-
const ROLES_FIELD_DESCRIPTION = "Drag to change rank. Ticks are features released on Features. Developer has the whole catalogue.";
|
|
303
|
-
/**
|
|
304
|
-
* Default rank and grants. Developer is implicit (empty grants). Brand
|
|
305
|
-
* screens default to Designer. The API tab is not granted until released.
|
|
306
|
-
*/
|
|
307
|
-
const DEFAULT_ROLE_MATRIX = ROLES.map((role) => ({
|
|
308
|
-
role,
|
|
309
|
-
grants: defaultGrantsForRole(role)
|
|
310
|
-
}));
|
|
311
|
-
const DEVELOPER_DESCRIPTION = "Everything, including features not released for assignment.";
|
|
312
|
-
const LAST_USERS_TICK_MESSAGE = "Keep Users granted on at least one of Admin or Developer.";
|
|
313
|
-
const MISSING_SEED_ROLES_MESSAGE = "Roles must include Developer, Admin, Designer, and Author.";
|
|
314
|
-
const DUPLICATE_ROLE_SLUG_MESSAGE = "Each role needs a unique slug.";
|
|
315
|
-
const DUPLICATE_ROLE_NAME_MESSAGE = "Each role needs a unique name.";
|
|
316
|
-
const INVALID_ROLE_NAME_MESSAGE = "Use letters and spaces only.";
|
|
317
|
-
function uniqueSlugs(values) {
|
|
318
|
-
const slugs = [];
|
|
319
|
-
for (const value of values) if (typeof value === "string" && isFeatureSlug(value) && !slugs.includes(value)) slugs.push(value);
|
|
320
|
-
return slugs;
|
|
321
|
-
}
|
|
322
|
-
/** Read stored grants, or rebuild them from the old capability ticks. */
|
|
323
|
-
function grantsFromStoredRole(item) {
|
|
324
|
-
if ("grants" in item && Array.isArray(item.grants)) return uniqueSlugs(item.grants);
|
|
325
|
-
const grants = [];
|
|
326
|
-
if ("content" in item && item.content) grants.push("content", "media");
|
|
327
|
-
if ("publish" in item && item.publish) grants.push("publish");
|
|
328
|
-
if ("users" in item && item.users) grants.push("users", "roles");
|
|
329
|
-
if ("brand" in item && item.brand) grants.push(...THEME_FEATURE_SLUGS);
|
|
330
|
-
return uniqueSlugs(grants);
|
|
331
|
-
}
|
|
332
|
-
function seedRoleRows(extras = []) {
|
|
333
|
-
return [...DEFAULT_ROLE_MATRIX.map((row) => ({
|
|
334
|
-
...row,
|
|
335
|
-
grants: [...row.grants],
|
|
336
|
-
label: isRole(row.role) ? ROLE_LABELS[row.role] : row.role
|
|
337
|
-
})), ...extras.map((extra) => ({
|
|
338
|
-
role: extra.role,
|
|
339
|
-
label: extra.label,
|
|
340
|
-
grants: extra.grants ? [...extra.grants] : []
|
|
341
|
-
}))];
|
|
342
|
-
}
|
|
343
|
-
function defaultRolesFieldValue(extras = []) {
|
|
344
|
-
return seedRoleRows(extras).map((row) => ({
|
|
345
|
-
id: row.role,
|
|
346
|
-
...row
|
|
347
|
-
}));
|
|
348
|
-
}
|
|
349
|
-
function readRoleRow(item) {
|
|
350
|
-
const role = "role" in item ? item.role : void 0;
|
|
351
|
-
if (typeof role !== "string" || role.trim() === "") return { error: DUPLICATE_ROLE_SLUG_MESSAGE };
|
|
352
|
-
if (!isRoleSlug(role)) return { error: DUPLICATE_ROLE_SLUG_MESSAGE };
|
|
353
|
-
return {
|
|
354
|
-
id: role,
|
|
355
|
-
role,
|
|
356
|
-
label: roleLabel(role, "label" in item && typeof item.label === "string" ? item.label : void 0),
|
|
357
|
-
grants: grantsFromStoredRole(item)
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
function parseRolesMatrix(value) {
|
|
361
|
-
if (!Array.isArray(value)) return {
|
|
362
|
-
ok: false,
|
|
363
|
-
message: MISSING_SEED_ROLES_MESSAGE
|
|
364
|
-
};
|
|
365
|
-
const rows = [];
|
|
366
|
-
const seen = /* @__PURE__ */ new Set();
|
|
367
|
-
for (const item of value) {
|
|
368
|
-
if (!item || typeof item !== "object") continue;
|
|
369
|
-
const role = "role" in item ? item.role : void 0;
|
|
370
|
-
if (typeof role !== "string" || role.trim() === "") continue;
|
|
371
|
-
const parsed = readRoleRow(item);
|
|
372
|
-
if ("error" in parsed) return {
|
|
373
|
-
ok: false,
|
|
374
|
-
message: parsed.error
|
|
375
|
-
};
|
|
376
|
-
if (seen.has(parsed.role)) return {
|
|
377
|
-
ok: false,
|
|
378
|
-
message: DUPLICATE_ROLE_SLUG_MESSAGE
|
|
379
|
-
};
|
|
380
|
-
seen.add(parsed.role);
|
|
381
|
-
rows.push(parsed);
|
|
382
|
-
}
|
|
383
|
-
if (ROLES.some((role) => !seen.has(role))) return {
|
|
384
|
-
ok: false,
|
|
385
|
-
message: MISSING_SEED_ROLES_MESSAGE
|
|
386
|
-
};
|
|
387
|
-
return {
|
|
388
|
-
ok: true,
|
|
389
|
-
rows
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
function roleHasGrant(row, slug) {
|
|
393
|
-
if (row.role === "developer") return true;
|
|
394
|
-
return row.grants.includes(slug);
|
|
395
|
-
}
|
|
396
|
-
function roleHasCapability(row, capability) {
|
|
397
|
-
if (row.role === "developer") return true;
|
|
398
|
-
return CAPABILITY_FEATURES[capability].some((slug) => row.grants.includes(slug));
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Mint a slug from the name only when the row has none yet. An existing
|
|
402
|
-
* key — seed or custom — stays put so a rename cannot orphan users.
|
|
403
|
-
*/
|
|
404
|
-
function applyRoleSlugsFromNames(value) {
|
|
405
|
-
if (!Array.isArray(value)) return value;
|
|
406
|
-
return value.map((item) => {
|
|
407
|
-
if (!item || typeof item !== "object") return item;
|
|
408
|
-
const row = item;
|
|
409
|
-
if (isRoleSlug(row.role)) return item;
|
|
410
|
-
const slug = slugifyRoleName(row.label);
|
|
411
|
-
return isRoleSlug(slug) ? {
|
|
412
|
-
...row,
|
|
413
|
-
role: slug
|
|
414
|
-
} : item;
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
function hasDuplicateRoleNames(value) {
|
|
418
|
-
if (!Array.isArray(value)) return false;
|
|
419
|
-
const seen = /* @__PURE__ */ new Set();
|
|
420
|
-
for (const item of value) {
|
|
421
|
-
if (!item || typeof item !== "object") continue;
|
|
422
|
-
const role = "role" in item && typeof item.role === "string" ? item.role : "";
|
|
423
|
-
const name = ("label" in item && typeof item.label === "string" ? item.label : "").trim() || (isRole(role) ? ROLE_LABELS[role] : "");
|
|
424
|
-
if (!name) continue;
|
|
425
|
-
const key = name.toLowerCase();
|
|
426
|
-
if (seen.has(key)) return true;
|
|
427
|
-
seen.add(key);
|
|
428
|
-
}
|
|
429
|
-
return false;
|
|
430
|
-
}
|
|
431
|
-
function hasInvalidRoleName(value) {
|
|
432
|
-
if (!Array.isArray(value)) return false;
|
|
433
|
-
for (const item of value) {
|
|
434
|
-
if (!item || typeof item !== "object") continue;
|
|
435
|
-
const label = "label" in item && typeof item.label === "string" ? item.label.trim() : "";
|
|
436
|
-
if (!label) continue;
|
|
437
|
-
if (!isRoleName(label)) return true;
|
|
438
|
-
}
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
function validateRolesMatrix(value) {
|
|
442
|
-
if (hasDuplicateRoleNames(value)) return DUPLICATE_ROLE_NAME_MESSAGE;
|
|
443
|
-
if (hasInvalidRoleName(value)) return INVALID_ROLE_NAME_MESSAGE;
|
|
444
|
-
const parsed = parseRolesMatrix(applyRoleSlugsFromNames(value));
|
|
445
|
-
if (!parsed.ok) return parsed.message;
|
|
446
|
-
return true;
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Developer is exclusive. Admin does not swallow Designer: both store.
|
|
450
|
-
* Unknown slugs (dropped catalogue keys such as `approver`) fall away unless
|
|
451
|
-
* they appear on the matrix.
|
|
452
|
-
*/
|
|
453
|
-
function normalizeStoredRoles(value, matrix = DEFAULT_ROLE_MATRIX) {
|
|
454
|
-
if (!Array.isArray(value)) return [];
|
|
455
|
-
const allowed = new Set(matrix.map((row) => row.role));
|
|
456
|
-
const roles = [...new Set(value.filter((entry) => typeof entry === "string" && allowed.has(entry)))];
|
|
457
|
-
if (roles.includes("developer")) return ["developer"];
|
|
458
|
-
return roles;
|
|
459
|
-
}
|
|
460
|
-
function roleSelectOptions(matrix = DEFAULT_ROLE_MATRIX) {
|
|
461
|
-
return matrix.map((row) => ({
|
|
462
|
-
label: roleLabel(row.role, row.label),
|
|
463
|
-
value: row.role
|
|
464
|
-
}));
|
|
465
|
-
}
|
|
466
|
-
/** Grant copy only. Developer names the unreleased catalogue; nothing about MCP or seed. */
|
|
467
|
-
function roleDescription(role, matrix = DEFAULT_ROLE_MATRIX) {
|
|
468
|
-
if (role === "developer") return DEVELOPER_DESCRIPTION;
|
|
469
|
-
const row = matrix.find((entry) => entry.role === role);
|
|
470
|
-
if (!row) return "No capabilities";
|
|
471
|
-
return row.grants.map((slug) => packageFeatureLabel(slug)).join(", ") || "No capabilities";
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Seed grants and package labels come back; extra rows stay as they are.
|
|
475
|
-
*/
|
|
476
|
-
function resetRolesMatrix(current) {
|
|
477
|
-
const extras = [];
|
|
478
|
-
if (Array.isArray(current)) for (const item of current) {
|
|
479
|
-
if (!item || typeof item !== "object") continue;
|
|
480
|
-
const parsed = readRoleRow(item);
|
|
481
|
-
if ("error" in parsed || isRole(parsed.role)) continue;
|
|
482
|
-
extras.push(parsed);
|
|
483
|
-
}
|
|
484
|
-
return [...defaultRolesFieldValue(), ...extras];
|
|
485
|
-
}
|
|
486
|
-
//#endregion
|
|
487
|
-
//#region src/roles/access.ts
|
|
488
|
-
function isAccessArgs$1(value) {
|
|
489
|
-
return typeof value === "object" && value !== null && "req" in value;
|
|
490
|
-
}
|
|
491
|
-
function storedRoles(user) {
|
|
492
|
-
return Array.isArray(user?.roles) ? user.roles : [];
|
|
493
|
-
}
|
|
494
|
-
function hasRole(user, role) {
|
|
495
|
-
return storedRoles(user).includes(role);
|
|
496
|
-
}
|
|
497
|
-
/** Not a tick. True only when `developer` is stored on the row. */
|
|
498
|
-
function isDeveloper(user) {
|
|
499
|
-
return hasRole(user, "developer");
|
|
500
|
-
}
|
|
501
|
-
function hasCapability(user, capability, matrix = DEFAULT_ROLE_MATRIX) {
|
|
502
|
-
if (isDeveloper(user)) return true;
|
|
503
|
-
return storedRoles(user).some((role) => {
|
|
504
|
-
const row = matrix.find((entry) => entry.role === role);
|
|
505
|
-
return row ? roleHasCapability(row, capability) : false;
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
function hasGrant(user, slug, matrix = DEFAULT_ROLE_MATRIX) {
|
|
509
|
-
if (isDeveloper(user)) return true;
|
|
510
|
-
return storedRoles(user).some((role) => {
|
|
511
|
-
const row = matrix.find((entry) => entry.role === role);
|
|
512
|
-
return row ? roleHasGrant(row, slug) : false;
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Reads the saved Roles Global, falling back to the seed when the row is
|
|
517
|
-
* empty, missing, or unreadable. Always override-access so a Designer
|
|
518
|
-
* evaluating Theme does not have to read Settings → Roles.
|
|
519
|
-
*/
|
|
520
|
-
async function getRolesMatrix(req = {}, extras = []) {
|
|
521
|
-
const fallback = seedRoleRows(extras);
|
|
522
|
-
const findGlobal = req.payload?.findGlobal;
|
|
523
|
-
if (typeof findGlobal !== "function") return fallback;
|
|
524
|
-
try {
|
|
525
|
-
const doc = await findGlobal({
|
|
526
|
-
slug: ROLES_SLUG,
|
|
527
|
-
overrideAccess: true,
|
|
528
|
-
req
|
|
529
|
-
});
|
|
530
|
-
const parsed = parseRolesMatrix(doc && typeof doc === "object" && "roles" in doc ? doc.roles : void 0);
|
|
531
|
-
return parsed.ok ? parsed.rows : fallback;
|
|
532
|
-
} catch {
|
|
533
|
-
return fallback;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
function capabilityPredicate(capability) {
|
|
537
|
-
function predicate(userOrArgs, matrix) {
|
|
538
|
-
if (isAccessArgs$1(userOrArgs)) {
|
|
539
|
-
const user = userOrArgs.req.user;
|
|
540
|
-
if (matrix) return hasCapability(user, capability, matrix);
|
|
541
|
-
return getRolesMatrix(userOrArgs.req).then((rows) => hasCapability(user, capability, rows));
|
|
542
|
-
}
|
|
543
|
-
return hasCapability(userOrArgs, capability, matrix ?? DEFAULT_ROLE_MATRIX);
|
|
544
|
-
}
|
|
545
|
-
return predicate;
|
|
546
|
-
}
|
|
547
|
-
const canManageContent = capabilityPredicate("content");
|
|
548
|
-
const canManageBrand = capabilityPredicate("brand");
|
|
549
|
-
const canPublish = capabilityPredicate("publish");
|
|
550
|
-
/** Users grant on any held role (Developer is implicit). */
|
|
551
|
-
const isAdmin = capabilityPredicate("users");
|
|
552
|
-
/** This role id currently has the Users grant, or is Developer. */
|
|
553
|
-
function isPrivilegedRole(role, matrix = DEFAULT_ROLE_MATRIX) {
|
|
554
|
-
if (typeof role !== "string") return false;
|
|
555
|
-
if (role === "developer") return true;
|
|
556
|
-
const row = matrix.find((entry) => entry.role === role);
|
|
557
|
-
return row ? roleHasGrant(row, "users") : false;
|
|
558
|
-
}
|
|
559
|
-
function isAuthenticated(userOrArgs) {
|
|
560
|
-
if (isAccessArgs$1(userOrArgs)) return Boolean(userOrArgs.req.user);
|
|
561
|
-
return Boolean(userOrArgs);
|
|
562
|
-
}
|
|
563
|
-
const isAdminOrSelf = async ({ req }) => {
|
|
564
|
-
if (!req.user) return false;
|
|
565
|
-
if (await isAdmin({ req })) return true;
|
|
566
|
-
return { id: { equals: req.user.id } };
|
|
567
|
-
};
|
|
568
|
-
const authenticatedOrPublished = ({ req: { user } }) => {
|
|
569
|
-
if (isAuthenticated(user)) return true;
|
|
570
|
-
return { _status: { equals: "published" } };
|
|
571
|
-
};
|
|
572
|
-
/**
|
|
573
|
-
* API tab condition. Unreleased (the default) is Developer only.
|
|
574
|
-
* After Features releases it, whoever holds the grant sees it.
|
|
575
|
-
*/
|
|
576
|
-
function isDeveloperTab({ req }) {
|
|
577
|
-
if (isDeveloper(req.user)) return true;
|
|
578
|
-
if (!req.payload) return false;
|
|
579
|
-
return Promise.resolve().then(() => access_exports).then(({ canUseFeature }) => canUseFeature("api-tab")({ req }));
|
|
580
|
-
}
|
|
581
|
-
//#endregion
|
|
582
|
-
//#region src/features/access.ts
|
|
583
|
-
var access_exports = /* @__PURE__ */ __exportAll({
|
|
584
|
-
canUseFeature: () => canUseFeature,
|
|
585
|
-
getFeaturesMatrix: () => getFeaturesMatrix,
|
|
586
|
-
hasFeature: () => hasFeature,
|
|
587
|
-
hideUnlessFeature: () => hideUnlessFeature
|
|
588
|
-
});
|
|
589
|
-
function isAccessArgs(value) {
|
|
590
|
-
return typeof value === "object" && value !== null && "req" in value;
|
|
591
|
-
}
|
|
592
|
-
function grantedOnReleased(user, slug, row, matrix) {
|
|
593
|
-
if (!row.released) return false;
|
|
594
|
-
return hasGrant(user, slug, matrix);
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* True when the feature is released and a held role is granted it.
|
|
598
|
-
* Developer is always allowed. An empty Global falls back to catalogue
|
|
599
|
-
* defaults. A group slug (`pages`, `theme`) is true when any child is.
|
|
600
|
-
*/
|
|
601
|
-
function hasFeature(user, slug, features = null, matrix = DEFAULT_ROLE_MATRIX) {
|
|
602
|
-
if (isDeveloper(user)) return true;
|
|
603
|
-
if (!user) return false;
|
|
604
|
-
const rows = features && features.length > 0 ? features : defaultFeaturesFieldValue();
|
|
605
|
-
const leaf = rows.find((entry) => entry.slug === slug);
|
|
606
|
-
if (leaf) return grantedOnReleased(user, slug, leaf, matrix);
|
|
607
|
-
if (!isGroupOnlySlug(slug)) return false;
|
|
608
|
-
return rows.some((entry) => entry.group === slug && grantedOnReleased(user, entry.slug, entry, matrix));
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Reads the saved Features Global. `null` means empty or unreadable — callers
|
|
612
|
-
* fall back to catalogue defaults. Always override-access.
|
|
613
|
-
*/
|
|
614
|
-
async function getFeaturesMatrix(req = {}) {
|
|
615
|
-
const findGlobal = req.payload?.findGlobal;
|
|
616
|
-
if (typeof findGlobal !== "function") return null;
|
|
617
|
-
try {
|
|
618
|
-
const doc = await findGlobal({
|
|
619
|
-
slug: FEATURES_SLUG,
|
|
620
|
-
overrideAccess: true,
|
|
621
|
-
req
|
|
622
|
-
});
|
|
623
|
-
const parsed = parseFeaturesMatrix(doc && typeof doc === "object" && "features" in doc ? doc.features : void 0);
|
|
624
|
-
return parsed.ok ? parsed.rows : null;
|
|
625
|
-
} catch {
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Access / nav helper for one catalogue slug. Sync against a passed grid;
|
|
631
|
-
* async when given `req` so it can read both Globals.
|
|
632
|
-
*/
|
|
633
|
-
function canUseFeature(slug) {
|
|
634
|
-
function predicate(userOrArgs, features, roles) {
|
|
635
|
-
if (isAccessArgs(userOrArgs)) {
|
|
636
|
-
const user = userOrArgs.req.user;
|
|
637
|
-
if (features !== void 0) return hasFeature(user, slug, features, roles ?? DEFAULT_ROLE_MATRIX);
|
|
638
|
-
return Promise.all([getFeaturesMatrix(userOrArgs.req), getRolesMatrix(userOrArgs.req)]).then(([grid, matrix]) => hasFeature(user, slug, grid, matrix));
|
|
639
|
-
}
|
|
640
|
-
return hasFeature(userOrArgs, slug, features ?? null, roles ?? DEFAULT_ROLE_MATRIX);
|
|
641
|
-
}
|
|
642
|
-
return predicate;
|
|
643
|
-
}
|
|
644
|
-
/** `admin.hidden`: hide when the login cannot use the feature. */
|
|
645
|
-
function hideUnlessFeature(slug) {
|
|
646
|
-
return (args) => {
|
|
647
|
-
const user = args.user;
|
|
648
|
-
if (args.req) {
|
|
649
|
-
const result = canUseFeature(slug)({ req: {
|
|
650
|
-
...args.req,
|
|
651
|
-
user
|
|
652
|
-
} });
|
|
653
|
-
if (result instanceof Promise) return result.then((ok) => !ok);
|
|
654
|
-
return !result;
|
|
655
|
-
}
|
|
656
|
-
return !canUseFeature(slug)(user ?? null);
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
//#endregion
|
|
660
|
-
export { defaultFeaturesFieldValue as $, ROLES_SLUG as A, CAPABILITIES as B, DUPLICATE_ROLE_NAME_MESSAGE as C, MISSING_SEED_ROLES_MESSAGE as D, LAST_USERS_TICK_MESSAGE as E, resetRolesMatrix as F, THEME_FEATURE_SLUGS as G, DEFAULT_ROLE_GRANTS as H, roleDescription as I, isRoleSlug as J, isRole as K, roleSelectOptions as L, defaultRolesFieldValue as M, normalizeStoredRoles as N, ROLES_FIELD_DESCRIPTION as O, parseRolesMatrix as P, MISSING_PACKAGE_FEATURES_MESSAGE as Q, seedRoleRows as R, DEVELOPER_DESCRIPTION as S, INVALID_ROLE_NAME_MESSAGE as T, ROLES as U, CAPABILITY_FEATURES as V, ROLE_LABELS as W, sanitizeRoleNameInput as X, roleLabel as Y, slugifyRoleName as Z, isDeveloper as _, hideUnlessFeature as a, FEATURES_SLUG as at, storedRoles as b, canManageContent as c, PACKAGE_FEATURES as ct, hasCapability as d, isFeatureSlug as dt, featureCatalogue as et, hasGrant as f, isLockedFeature as ft, isAuthenticated as g, isAdminOrSelf as h, hasFeature as i, validateFeaturesMatrix as it, applyRoleSlugsFromNames as j, ROLES_GLOBAL_DESCRIPTION as k, canPublish as l, PACKAGE_FEATURE_SLUGS as lt, isAdmin as m, canUseFeature as n, parseFeaturesMatrix as nt, authenticatedOrPublished as o, FEATURE_GROUPS as ot, hasRole as p, isPackageFeatureSlug as pt, isRoleName as q, getFeaturesMatrix as r, stampFeatureCatalogue as rt, canManageBrand as s, FEATURE_GROUP_LABELS as st, access_exports as t, featureGroupLabel as tt, getRolesMatrix as u, isFeatureGroupId as ut, isDeveloperTab as v, DUPLICATE_ROLE_SLUG_MESSAGE as w, DEFAULT_ROLE_MATRIX as x, isPrivilegedRole as y, validateRolesMatrix as z };
|
|
661
|
-
|
|
662
|
-
//# sourceMappingURL=access-B3LglN2J.mjs.map
|