@objectstack/lint 14.7.0 → 14.8.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.cjs +127 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -23
- package/dist/index.d.ts +78 -23
- package/dist/index.js +124 -24
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1193,10 +1193,107 @@ function validateFormLayout(stack) {
|
|
|
1193
1193
|
return findings;
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
// src/validate-visibility-predicates.ts
|
|
1197
|
+
var VISIBILITY_ALIAS_DEPRECATED = "visibility-alias-deprecated";
|
|
1198
|
+
var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
|
|
1199
|
+
var CANONICAL = "visibleWhen";
|
|
1200
|
+
var ALIASES = ["visibleOn", "visibility"];
|
|
1201
|
+
function asArray12(v) {
|
|
1202
|
+
if (Array.isArray(v)) return v;
|
|
1203
|
+
if (v && typeof v === "object") {
|
|
1204
|
+
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
1205
|
+
}
|
|
1206
|
+
return [];
|
|
1207
|
+
}
|
|
1208
|
+
function predicateSource(v) {
|
|
1209
|
+
if (typeof v === "string") return v;
|
|
1210
|
+
if (v && typeof v === "object" && typeof v.source === "string") {
|
|
1211
|
+
return v.source;
|
|
1212
|
+
}
|
|
1213
|
+
return void 0;
|
|
1214
|
+
}
|
|
1215
|
+
function usesDataRoot(source) {
|
|
1216
|
+
return /(^|[^.\w$])data\.\w/.test(source);
|
|
1217
|
+
}
|
|
1218
|
+
function checkElement(el, where, path, findings) {
|
|
1219
|
+
for (const alias of ALIASES) {
|
|
1220
|
+
if (el[alias] !== void 0) {
|
|
1221
|
+
findings.push({
|
|
1222
|
+
severity: "warning",
|
|
1223
|
+
rule: VISIBILITY_ALIAS_DEPRECATED,
|
|
1224
|
+
where,
|
|
1225
|
+
path: `${path}.${alias}`,
|
|
1226
|
+
message: `\`${alias}\` is the deprecated spelling of the conditional-visibility predicate (ADR-0089). It still works \u2014 it is normalized to \`visibleWhen\` at parse \u2014 but the canonical key is \`visibleWhen\`.`,
|
|
1227
|
+
hint: `Rename the key \`${alias}\` \u2192 \`visibleWhen\` (same CEL value).`
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
|
|
1232
|
+
const source = predicateSource(raw);
|
|
1233
|
+
if (source && usesDataRoot(source)) {
|
|
1234
|
+
findings.push({
|
|
1235
|
+
severity: "warning",
|
|
1236
|
+
rule: VISIBILITY_ROOT_MISLAYERED,
|
|
1237
|
+
where,
|
|
1238
|
+
path,
|
|
1239
|
+
message: `visibility predicate is rooted at \`data.\` \u2014 that is the metadata-editing-form root (a \`*.form.ts\` row under edit), not a runtime surface. A runtime view/page predicate that binds \`data.\` never matches and the element renders unconditionally (ADR-0089).`,
|
|
1240
|
+
hint: `Runtime record surfaces bind \`record\` + \`current_user\` (pages also expose \`page.<var>\`). Use e.g. \`record.status == 'open'\` instead of \`data.status == 'open'\`.`
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function isFieldObject(entry) {
|
|
1245
|
+
return !!entry && typeof entry === "object" && !Array.isArray(entry);
|
|
1246
|
+
}
|
|
1247
|
+
function validateVisibilityPredicates(stack) {
|
|
1248
|
+
const findings = [];
|
|
1249
|
+
const views = asArray12(stack.views);
|
|
1250
|
+
for (let i = 0; i < views.length; i++) {
|
|
1251
|
+
const view = views[i];
|
|
1252
|
+
if (!view || typeof view !== "object") continue;
|
|
1253
|
+
const viewName = typeof view.name === "string" ? view.name : `(view ${i})`;
|
|
1254
|
+
const where = `view "${viewName}"`;
|
|
1255
|
+
for (const bucket of ["sections", "groups"]) {
|
|
1256
|
+
const sections = Array.isArray(view[bucket]) ? view[bucket] : [];
|
|
1257
|
+
for (let s = 0; s < sections.length; s++) {
|
|
1258
|
+
const sec = sections[s];
|
|
1259
|
+
if (!sec || typeof sec !== "object") continue;
|
|
1260
|
+
const secPath = `views[${i}].${bucket}[${s}]`;
|
|
1261
|
+
checkElement(sec, where, secPath, findings);
|
|
1262
|
+
const secFields = Array.isArray(sec.fields) ? sec.fields : [];
|
|
1263
|
+
for (let f = 0; f < secFields.length; f++) {
|
|
1264
|
+
const entry = secFields[f];
|
|
1265
|
+
if (isFieldObject(entry)) {
|
|
1266
|
+
checkElement(entry, where, `${secPath}.fields[${f}]`, findings);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
const pages = asArray12(stack.pages);
|
|
1273
|
+
for (let i = 0; i < pages.length; i++) {
|
|
1274
|
+
const page = pages[i];
|
|
1275
|
+
if (!page || typeof page !== "object") continue;
|
|
1276
|
+
const pageName = typeof page.name === "string" ? page.name : `(page ${i})`;
|
|
1277
|
+
const where = `page "${pageName}"`;
|
|
1278
|
+
const regions = Array.isArray(page.regions) ? page.regions : [];
|
|
1279
|
+
for (let r = 0; r < regions.length; r++) {
|
|
1280
|
+
const region = regions[r];
|
|
1281
|
+
const components = region && typeof region === "object" && Array.isArray(region.components) ? region.components : [];
|
|
1282
|
+
for (let c = 0; c < components.length; c++) {
|
|
1283
|
+
const comp = components[c];
|
|
1284
|
+
if (comp && typeof comp === "object") {
|
|
1285
|
+
checkElement(comp, where, `pages[${i}].regions[${r}].components[${c}]`, findings);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return findings;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1196
1293
|
// src/validate-capability-references.ts
|
|
1197
1294
|
import { PLATFORM_CAPABILITY_NAMES } from "@objectstack/spec/security";
|
|
1198
1295
|
var CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
|
|
1199
|
-
function
|
|
1296
|
+
function asArray13(v) {
|
|
1200
1297
|
if (Array.isArray(v)) return v;
|
|
1201
1298
|
if (v && typeof v === "object") {
|
|
1202
1299
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1221,10 +1318,10 @@ function validateCapabilityReferences(stack) {
|
|
|
1221
1318
|
const findings = [];
|
|
1222
1319
|
if (!stack || typeof stack !== "object") return findings;
|
|
1223
1320
|
const known = new Set(PLATFORM_CAPABILITY_NAMES);
|
|
1224
|
-
for (const ps of
|
|
1321
|
+
for (const ps of asArray13(stack.permissions)) {
|
|
1225
1322
|
for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
|
|
1226
1323
|
}
|
|
1227
|
-
for (const seed of
|
|
1324
|
+
for (const seed of asArray13(stack.data)) {
|
|
1228
1325
|
if (seed.object !== "sys_capability") continue;
|
|
1229
1326
|
for (const rec of Array.isArray(seed.records) ? seed.records : []) {
|
|
1230
1327
|
const name = rec?.name;
|
|
@@ -1243,7 +1340,7 @@ function validateCapabilityReferences(stack) {
|
|
|
1243
1340
|
hint
|
|
1244
1341
|
});
|
|
1245
1342
|
};
|
|
1246
|
-
const objects =
|
|
1343
|
+
const objects = asArray13(stack.objects);
|
|
1247
1344
|
for (let i = 0; i < objects.length; i++) {
|
|
1248
1345
|
const obj = objects[i];
|
|
1249
1346
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -1252,27 +1349,27 @@ function validateCapabilityReferences(stack) {
|
|
|
1252
1349
|
for (const { cap, key } of flattenObjectRequired(obj.requiredPermissions)) {
|
|
1253
1350
|
flag(cap, `object "${objName}"`, `${objPath}.requiredPermissions${key ? `.${key}` : ""}`);
|
|
1254
1351
|
}
|
|
1255
|
-
const fields =
|
|
1352
|
+
const fields = asArray13(obj.fields);
|
|
1256
1353
|
for (const f of fields) {
|
|
1257
1354
|
const fname = typeof f.name === "string" ? f.name : "(field)";
|
|
1258
1355
|
for (const cap of asCapArray(f.requiredPermissions)) {
|
|
1259
1356
|
flag(cap, `field "${objName}.${fname}"`, `${objPath}.fields.${fname}.requiredPermissions`);
|
|
1260
1357
|
}
|
|
1261
1358
|
}
|
|
1262
|
-
for (const [ai, action] of
|
|
1359
|
+
for (const [ai, action] of asArray13(obj.actions).entries()) {
|
|
1263
1360
|
const aName = typeof action.name === "string" ? action.name : `(action ${ai})`;
|
|
1264
1361
|
for (const cap of asCapArray(action.requiredPermissions)) {
|
|
1265
1362
|
flag(cap, `action "${objName}.${aName}"`, `${objPath}.actions[${ai}].requiredPermissions`);
|
|
1266
1363
|
}
|
|
1267
1364
|
}
|
|
1268
1365
|
}
|
|
1269
|
-
for (const [i, action] of
|
|
1366
|
+
for (const [i, action] of asArray13(stack.actions).entries()) {
|
|
1270
1367
|
const aName = typeof action.name === "string" ? action.name : `(action ${i})`;
|
|
1271
1368
|
for (const cap of asCapArray(action.requiredPermissions)) {
|
|
1272
1369
|
flag(cap, `action "${aName}"`, `actions[${i}].requiredPermissions`);
|
|
1273
1370
|
}
|
|
1274
1371
|
}
|
|
1275
|
-
const apps =
|
|
1372
|
+
const apps = asArray13(stack.apps);
|
|
1276
1373
|
for (let i = 0; i < apps.length; i++) {
|
|
1277
1374
|
const app = apps[i];
|
|
1278
1375
|
if (!app || typeof app !== "object") continue;
|
|
@@ -1308,7 +1405,7 @@ var TYPE_FIX = {
|
|
|
1308
1405
|
business_unit: "department",
|
|
1309
1406
|
bu: "department"
|
|
1310
1407
|
};
|
|
1311
|
-
function
|
|
1408
|
+
function asArray14(v) {
|
|
1312
1409
|
if (Array.isArray(v)) return v;
|
|
1313
1410
|
if (v && typeof v === "object") {
|
|
1314
1411
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1318,7 +1415,7 @@ function asArray13(v) {
|
|
|
1318
1415
|
function validateApprovalApprovers(stack) {
|
|
1319
1416
|
const findings = [];
|
|
1320
1417
|
if (!stack || typeof stack !== "object") return findings;
|
|
1321
|
-
const flows =
|
|
1418
|
+
const flows = asArray14(stack.flows);
|
|
1322
1419
|
const validTypes = new Set(ApproverType.options);
|
|
1323
1420
|
for (let fi = 0; fi < flows.length; fi++) {
|
|
1324
1421
|
const flow = flows[fi];
|
|
@@ -1406,7 +1503,7 @@ var OWD_WIDTH = {
|
|
|
1406
1503
|
public_read: 1,
|
|
1407
1504
|
public_read_write: 2
|
|
1408
1505
|
};
|
|
1409
|
-
function
|
|
1506
|
+
function asArray15(v) {
|
|
1410
1507
|
if (Array.isArray(v)) return v;
|
|
1411
1508
|
if (v && typeof v === "object") {
|
|
1412
1509
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1432,7 +1529,7 @@ function refOf(def) {
|
|
|
1432
1529
|
return typeof r === "string" && r ? r : void 0;
|
|
1433
1530
|
}
|
|
1434
1531
|
function firstMasterDetailField(obj) {
|
|
1435
|
-
for (const f of
|
|
1532
|
+
for (const f of asArray15(obj.fields)) {
|
|
1436
1533
|
if (f.type === "master_detail") {
|
|
1437
1534
|
return { name: String(f.name ?? "?"), parent: refOf(f) };
|
|
1438
1535
|
}
|
|
@@ -1445,8 +1542,8 @@ function grantsObjectAccess(p) {
|
|
|
1445
1542
|
function validateSecurityPosture(stack, opts) {
|
|
1446
1543
|
const findings = [];
|
|
1447
1544
|
if (!stack || typeof stack !== "object") return findings;
|
|
1448
|
-
const objects =
|
|
1449
|
-
const permissionSets =
|
|
1545
|
+
const objects = asArray15(stack.objects);
|
|
1546
|
+
const permissionSets = asArray15(stack.permissions);
|
|
1450
1547
|
for (let i = 0; i < objects.length; i++) {
|
|
1451
1548
|
const obj = objects[i];
|
|
1452
1549
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -1575,10 +1672,10 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1575
1672
|
if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
|
|
1576
1673
|
const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
|
|
1577
1674
|
flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
|
|
1578
|
-
for (const f of
|
|
1675
|
+
for (const f of asArray15(obj.fields)) {
|
|
1579
1676
|
flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
|
|
1580
1677
|
}
|
|
1581
|
-
for (const [ai, action] of
|
|
1678
|
+
for (const [ai, action] of asArray15(obj.actions).entries()) {
|
|
1582
1679
|
flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
|
|
1583
1680
|
}
|
|
1584
1681
|
}
|
|
@@ -1587,19 +1684,19 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1587
1684
|
if (!ps || typeof ps !== "object") continue;
|
|
1588
1685
|
flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
|
|
1589
1686
|
}
|
|
1590
|
-
for (const [i, pos] of
|
|
1687
|
+
for (const [i, pos] of asArray15(stack.positions).entries()) {
|
|
1591
1688
|
flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
|
|
1592
1689
|
}
|
|
1593
|
-
for (const [i, app] of
|
|
1690
|
+
for (const [i, app] of asArray15(stack.apps).entries()) {
|
|
1594
1691
|
flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
|
|
1595
1692
|
}
|
|
1596
|
-
for (const [i, book] of
|
|
1693
|
+
for (const [i, book] of asArray15(stack.books).entries()) {
|
|
1597
1694
|
flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
|
|
1598
1695
|
}
|
|
1599
1696
|
const stackSetNames = new Set(
|
|
1600
1697
|
permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
|
|
1601
1698
|
);
|
|
1602
|
-
for (const [i, book] of
|
|
1699
|
+
for (const [i, book] of asArray15(stack.books).entries()) {
|
|
1603
1700
|
const audience = book.audience;
|
|
1604
1701
|
if (!audience || typeof audience !== "object") continue;
|
|
1605
1702
|
const setName = audience.permissionSet;
|
|
@@ -1677,7 +1774,7 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1677
1774
|
}
|
|
1678
1775
|
const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
|
|
1679
1776
|
const nowMs = opts?.nowMs ?? Date.now();
|
|
1680
|
-
for (const [i, seed] of
|
|
1777
|
+
for (const [i, seed] of asArray15(stack.data).entries()) {
|
|
1681
1778
|
const seedObject = typeof seed.object === "string" ? seed.object : "";
|
|
1682
1779
|
if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
|
|
1683
1780
|
const records = Array.isArray(seed.records) ? seed.records : [];
|
|
@@ -1718,7 +1815,7 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1718
1815
|
}
|
|
1719
1816
|
|
|
1720
1817
|
// src/build-access-matrix.ts
|
|
1721
|
-
function
|
|
1818
|
+
function asArray16(v) {
|
|
1722
1819
|
if (Array.isArray(v)) return v;
|
|
1723
1820
|
if (v && typeof v === "object") {
|
|
1724
1821
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1729,13 +1826,13 @@ function buildAccessMatrix(stack) {
|
|
|
1729
1826
|
const entries = [];
|
|
1730
1827
|
if (!stack || typeof stack !== "object") return { version: 1, entries };
|
|
1731
1828
|
const owdByObject = /* @__PURE__ */ new Map();
|
|
1732
|
-
for (const obj of
|
|
1829
|
+
for (const obj of asArray16(stack.objects)) {
|
|
1733
1830
|
const name = typeof obj.name === "string" ? obj.name : "";
|
|
1734
1831
|
if (!name) continue;
|
|
1735
1832
|
const owd = obj.sharingModel ?? obj.security?.sharingModel;
|
|
1736
1833
|
if (typeof owd === "string") owdByObject.set(name, owd);
|
|
1737
1834
|
}
|
|
1738
|
-
for (const ps of
|
|
1835
|
+
for (const ps of asArray16(stack.permissions)) {
|
|
1739
1836
|
const psName = typeof ps.name === "string" ? ps.name : "";
|
|
1740
1837
|
if (!psName) continue;
|
|
1741
1838
|
const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
|
|
@@ -1839,6 +1936,8 @@ export {
|
|
|
1839
1936
|
TABLE_COUNT_ONLY,
|
|
1840
1937
|
TITLE_FORMAT_RETIRED,
|
|
1841
1938
|
TITLE_UNRESOLVABLE,
|
|
1939
|
+
VISIBILITY_ALIAS_DEPRECATED,
|
|
1940
|
+
VISIBILITY_ROOT_MISLAYERED,
|
|
1842
1941
|
WIDGET_DATASET_UNKNOWN,
|
|
1843
1942
|
WIDGET_DIMENSION_UNKNOWN,
|
|
1844
1943
|
WIDGET_MEASURE_UNKNOWN,
|
|
@@ -1857,6 +1956,7 @@ export {
|
|
|
1857
1956
|
validateSecurityPosture,
|
|
1858
1957
|
validateSemanticRoles,
|
|
1859
1958
|
validateStackExpressions,
|
|
1959
|
+
validateVisibilityPredicates,
|
|
1860
1960
|
validateWidgetBindings
|
|
1861
1961
|
};
|
|
1862
1962
|
//# sourceMappingURL=index.js.map
|