@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.cjs
CHANGED
|
@@ -53,6 +53,8 @@ __export(index_exports, {
|
|
|
53
53
|
TABLE_COUNT_ONLY: () => TABLE_COUNT_ONLY,
|
|
54
54
|
TITLE_FORMAT_RETIRED: () => TITLE_FORMAT_RETIRED,
|
|
55
55
|
TITLE_UNRESOLVABLE: () => TITLE_UNRESOLVABLE,
|
|
56
|
+
VISIBILITY_ALIAS_DEPRECATED: () => VISIBILITY_ALIAS_DEPRECATED,
|
|
57
|
+
VISIBILITY_ROOT_MISLAYERED: () => VISIBILITY_ROOT_MISLAYERED,
|
|
56
58
|
WIDGET_DATASET_UNKNOWN: () => WIDGET_DATASET_UNKNOWN,
|
|
57
59
|
WIDGET_DIMENSION_UNKNOWN: () => WIDGET_DIMENSION_UNKNOWN,
|
|
58
60
|
WIDGET_MEASURE_UNKNOWN: () => WIDGET_MEASURE_UNKNOWN,
|
|
@@ -71,6 +73,7 @@ __export(index_exports, {
|
|
|
71
73
|
validateSecurityPosture: () => validateSecurityPosture,
|
|
72
74
|
validateSemanticRoles: () => validateSemanticRoles,
|
|
73
75
|
validateStackExpressions: () => validateStackExpressions,
|
|
76
|
+
validateVisibilityPredicates: () => validateVisibilityPredicates,
|
|
74
77
|
validateWidgetBindings: () => validateWidgetBindings
|
|
75
78
|
});
|
|
76
79
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -1272,10 +1275,107 @@ function validateFormLayout(stack) {
|
|
|
1272
1275
|
return findings;
|
|
1273
1276
|
}
|
|
1274
1277
|
|
|
1278
|
+
// src/validate-visibility-predicates.ts
|
|
1279
|
+
var VISIBILITY_ALIAS_DEPRECATED = "visibility-alias-deprecated";
|
|
1280
|
+
var VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
|
|
1281
|
+
var CANONICAL = "visibleWhen";
|
|
1282
|
+
var ALIASES = ["visibleOn", "visibility"];
|
|
1283
|
+
function asArray12(v) {
|
|
1284
|
+
if (Array.isArray(v)) return v;
|
|
1285
|
+
if (v && typeof v === "object") {
|
|
1286
|
+
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
1287
|
+
}
|
|
1288
|
+
return [];
|
|
1289
|
+
}
|
|
1290
|
+
function predicateSource(v) {
|
|
1291
|
+
if (typeof v === "string") return v;
|
|
1292
|
+
if (v && typeof v === "object" && typeof v.source === "string") {
|
|
1293
|
+
return v.source;
|
|
1294
|
+
}
|
|
1295
|
+
return void 0;
|
|
1296
|
+
}
|
|
1297
|
+
function usesDataRoot(source) {
|
|
1298
|
+
return /(^|[^.\w$])data\.\w/.test(source);
|
|
1299
|
+
}
|
|
1300
|
+
function checkElement(el, where, path, findings) {
|
|
1301
|
+
for (const alias of ALIASES) {
|
|
1302
|
+
if (el[alias] !== void 0) {
|
|
1303
|
+
findings.push({
|
|
1304
|
+
severity: "warning",
|
|
1305
|
+
rule: VISIBILITY_ALIAS_DEPRECATED,
|
|
1306
|
+
where,
|
|
1307
|
+
path: `${path}.${alias}`,
|
|
1308
|
+
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\`.`,
|
|
1309
|
+
hint: `Rename the key \`${alias}\` \u2192 \`visibleWhen\` (same CEL value).`
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
|
|
1314
|
+
const source = predicateSource(raw);
|
|
1315
|
+
if (source && usesDataRoot(source)) {
|
|
1316
|
+
findings.push({
|
|
1317
|
+
severity: "warning",
|
|
1318
|
+
rule: VISIBILITY_ROOT_MISLAYERED,
|
|
1319
|
+
where,
|
|
1320
|
+
path,
|
|
1321
|
+
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).`,
|
|
1322
|
+
hint: `Runtime record surfaces bind \`record\` + \`current_user\` (pages also expose \`page.<var>\`). Use e.g. \`record.status == 'open'\` instead of \`data.status == 'open'\`.`
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
function isFieldObject(entry) {
|
|
1327
|
+
return !!entry && typeof entry === "object" && !Array.isArray(entry);
|
|
1328
|
+
}
|
|
1329
|
+
function validateVisibilityPredicates(stack) {
|
|
1330
|
+
const findings = [];
|
|
1331
|
+
const views = asArray12(stack.views);
|
|
1332
|
+
for (let i = 0; i < views.length; i++) {
|
|
1333
|
+
const view = views[i];
|
|
1334
|
+
if (!view || typeof view !== "object") continue;
|
|
1335
|
+
const viewName = typeof view.name === "string" ? view.name : `(view ${i})`;
|
|
1336
|
+
const where = `view "${viewName}"`;
|
|
1337
|
+
for (const bucket of ["sections", "groups"]) {
|
|
1338
|
+
const sections = Array.isArray(view[bucket]) ? view[bucket] : [];
|
|
1339
|
+
for (let s = 0; s < sections.length; s++) {
|
|
1340
|
+
const sec = sections[s];
|
|
1341
|
+
if (!sec || typeof sec !== "object") continue;
|
|
1342
|
+
const secPath = `views[${i}].${bucket}[${s}]`;
|
|
1343
|
+
checkElement(sec, where, secPath, findings);
|
|
1344
|
+
const secFields = Array.isArray(sec.fields) ? sec.fields : [];
|
|
1345
|
+
for (let f = 0; f < secFields.length; f++) {
|
|
1346
|
+
const entry = secFields[f];
|
|
1347
|
+
if (isFieldObject(entry)) {
|
|
1348
|
+
checkElement(entry, where, `${secPath}.fields[${f}]`, findings);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
const pages = asArray12(stack.pages);
|
|
1355
|
+
for (let i = 0; i < pages.length; i++) {
|
|
1356
|
+
const page = pages[i];
|
|
1357
|
+
if (!page || typeof page !== "object") continue;
|
|
1358
|
+
const pageName = typeof page.name === "string" ? page.name : `(page ${i})`;
|
|
1359
|
+
const where = `page "${pageName}"`;
|
|
1360
|
+
const regions = Array.isArray(page.regions) ? page.regions : [];
|
|
1361
|
+
for (let r = 0; r < regions.length; r++) {
|
|
1362
|
+
const region = regions[r];
|
|
1363
|
+
const components = region && typeof region === "object" && Array.isArray(region.components) ? region.components : [];
|
|
1364
|
+
for (let c = 0; c < components.length; c++) {
|
|
1365
|
+
const comp = components[c];
|
|
1366
|
+
if (comp && typeof comp === "object") {
|
|
1367
|
+
checkElement(comp, where, `pages[${i}].regions[${r}].components[${c}]`, findings);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return findings;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1275
1375
|
// src/validate-capability-references.ts
|
|
1276
1376
|
var import_security = require("@objectstack/spec/security");
|
|
1277
1377
|
var CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
|
|
1278
|
-
function
|
|
1378
|
+
function asArray13(v) {
|
|
1279
1379
|
if (Array.isArray(v)) return v;
|
|
1280
1380
|
if (v && typeof v === "object") {
|
|
1281
1381
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1300,10 +1400,10 @@ function validateCapabilityReferences(stack) {
|
|
|
1300
1400
|
const findings = [];
|
|
1301
1401
|
if (!stack || typeof stack !== "object") return findings;
|
|
1302
1402
|
const known = new Set(import_security.PLATFORM_CAPABILITY_NAMES);
|
|
1303
|
-
for (const ps of
|
|
1403
|
+
for (const ps of asArray13(stack.permissions)) {
|
|
1304
1404
|
for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
|
|
1305
1405
|
}
|
|
1306
|
-
for (const seed of
|
|
1406
|
+
for (const seed of asArray13(stack.data)) {
|
|
1307
1407
|
if (seed.object !== "sys_capability") continue;
|
|
1308
1408
|
for (const rec of Array.isArray(seed.records) ? seed.records : []) {
|
|
1309
1409
|
const name = rec?.name;
|
|
@@ -1322,7 +1422,7 @@ function validateCapabilityReferences(stack) {
|
|
|
1322
1422
|
hint
|
|
1323
1423
|
});
|
|
1324
1424
|
};
|
|
1325
|
-
const objects =
|
|
1425
|
+
const objects = asArray13(stack.objects);
|
|
1326
1426
|
for (let i = 0; i < objects.length; i++) {
|
|
1327
1427
|
const obj = objects[i];
|
|
1328
1428
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -1331,27 +1431,27 @@ function validateCapabilityReferences(stack) {
|
|
|
1331
1431
|
for (const { cap, key } of flattenObjectRequired(obj.requiredPermissions)) {
|
|
1332
1432
|
flag(cap, `object "${objName}"`, `${objPath}.requiredPermissions${key ? `.${key}` : ""}`);
|
|
1333
1433
|
}
|
|
1334
|
-
const fields =
|
|
1434
|
+
const fields = asArray13(obj.fields);
|
|
1335
1435
|
for (const f of fields) {
|
|
1336
1436
|
const fname = typeof f.name === "string" ? f.name : "(field)";
|
|
1337
1437
|
for (const cap of asCapArray(f.requiredPermissions)) {
|
|
1338
1438
|
flag(cap, `field "${objName}.${fname}"`, `${objPath}.fields.${fname}.requiredPermissions`);
|
|
1339
1439
|
}
|
|
1340
1440
|
}
|
|
1341
|
-
for (const [ai, action] of
|
|
1441
|
+
for (const [ai, action] of asArray13(obj.actions).entries()) {
|
|
1342
1442
|
const aName = typeof action.name === "string" ? action.name : `(action ${ai})`;
|
|
1343
1443
|
for (const cap of asCapArray(action.requiredPermissions)) {
|
|
1344
1444
|
flag(cap, `action "${objName}.${aName}"`, `${objPath}.actions[${ai}].requiredPermissions`);
|
|
1345
1445
|
}
|
|
1346
1446
|
}
|
|
1347
1447
|
}
|
|
1348
|
-
for (const [i, action] of
|
|
1448
|
+
for (const [i, action] of asArray13(stack.actions).entries()) {
|
|
1349
1449
|
const aName = typeof action.name === "string" ? action.name : `(action ${i})`;
|
|
1350
1450
|
for (const cap of asCapArray(action.requiredPermissions)) {
|
|
1351
1451
|
flag(cap, `action "${aName}"`, `actions[${i}].requiredPermissions`);
|
|
1352
1452
|
}
|
|
1353
1453
|
}
|
|
1354
|
-
const apps =
|
|
1454
|
+
const apps = asArray13(stack.apps);
|
|
1355
1455
|
for (let i = 0; i < apps.length; i++) {
|
|
1356
1456
|
const app = apps[i];
|
|
1357
1457
|
if (!app || typeof app !== "object") continue;
|
|
@@ -1387,7 +1487,7 @@ var TYPE_FIX = {
|
|
|
1387
1487
|
business_unit: "department",
|
|
1388
1488
|
bu: "department"
|
|
1389
1489
|
};
|
|
1390
|
-
function
|
|
1490
|
+
function asArray14(v) {
|
|
1391
1491
|
if (Array.isArray(v)) return v;
|
|
1392
1492
|
if (v && typeof v === "object") {
|
|
1393
1493
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1397,7 +1497,7 @@ function asArray13(v) {
|
|
|
1397
1497
|
function validateApprovalApprovers(stack) {
|
|
1398
1498
|
const findings = [];
|
|
1399
1499
|
if (!stack || typeof stack !== "object") return findings;
|
|
1400
|
-
const flows =
|
|
1500
|
+
const flows = asArray14(stack.flows);
|
|
1401
1501
|
const validTypes = new Set(import_automation.ApproverType.options);
|
|
1402
1502
|
for (let fi = 0; fi < flows.length; fi++) {
|
|
1403
1503
|
const flow = flows[fi];
|
|
@@ -1485,7 +1585,7 @@ var OWD_WIDTH = {
|
|
|
1485
1585
|
public_read: 1,
|
|
1486
1586
|
public_read_write: 2
|
|
1487
1587
|
};
|
|
1488
|
-
function
|
|
1588
|
+
function asArray15(v) {
|
|
1489
1589
|
if (Array.isArray(v)) return v;
|
|
1490
1590
|
if (v && typeof v === "object") {
|
|
1491
1591
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1511,7 +1611,7 @@ function refOf(def) {
|
|
|
1511
1611
|
return typeof r === "string" && r ? r : void 0;
|
|
1512
1612
|
}
|
|
1513
1613
|
function firstMasterDetailField(obj) {
|
|
1514
|
-
for (const f of
|
|
1614
|
+
for (const f of asArray15(obj.fields)) {
|
|
1515
1615
|
if (f.type === "master_detail") {
|
|
1516
1616
|
return { name: String(f.name ?? "?"), parent: refOf(f) };
|
|
1517
1617
|
}
|
|
@@ -1524,8 +1624,8 @@ function grantsObjectAccess(p) {
|
|
|
1524
1624
|
function validateSecurityPosture(stack, opts) {
|
|
1525
1625
|
const findings = [];
|
|
1526
1626
|
if (!stack || typeof stack !== "object") return findings;
|
|
1527
|
-
const objects =
|
|
1528
|
-
const permissionSets =
|
|
1627
|
+
const objects = asArray15(stack.objects);
|
|
1628
|
+
const permissionSets = asArray15(stack.permissions);
|
|
1529
1629
|
for (let i = 0; i < objects.length; i++) {
|
|
1530
1630
|
const obj = objects[i];
|
|
1531
1631
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -1654,10 +1754,10 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1654
1754
|
if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
|
|
1655
1755
|
const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
|
|
1656
1756
|
flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
|
|
1657
|
-
for (const f of
|
|
1757
|
+
for (const f of asArray15(obj.fields)) {
|
|
1658
1758
|
flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
|
|
1659
1759
|
}
|
|
1660
|
-
for (const [ai, action] of
|
|
1760
|
+
for (const [ai, action] of asArray15(obj.actions).entries()) {
|
|
1661
1761
|
flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
|
|
1662
1762
|
}
|
|
1663
1763
|
}
|
|
@@ -1666,19 +1766,19 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1666
1766
|
if (!ps || typeof ps !== "object") continue;
|
|
1667
1767
|
flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
|
|
1668
1768
|
}
|
|
1669
|
-
for (const [i, pos] of
|
|
1769
|
+
for (const [i, pos] of asArray15(stack.positions).entries()) {
|
|
1670
1770
|
flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
|
|
1671
1771
|
}
|
|
1672
|
-
for (const [i, app] of
|
|
1772
|
+
for (const [i, app] of asArray15(stack.apps).entries()) {
|
|
1673
1773
|
flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
|
|
1674
1774
|
}
|
|
1675
|
-
for (const [i, book] of
|
|
1775
|
+
for (const [i, book] of asArray15(stack.books).entries()) {
|
|
1676
1776
|
flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
|
|
1677
1777
|
}
|
|
1678
1778
|
const stackSetNames = new Set(
|
|
1679
1779
|
permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
|
|
1680
1780
|
);
|
|
1681
|
-
for (const [i, book] of
|
|
1781
|
+
for (const [i, book] of asArray15(stack.books).entries()) {
|
|
1682
1782
|
const audience = book.audience;
|
|
1683
1783
|
if (!audience || typeof audience !== "object") continue;
|
|
1684
1784
|
const setName = audience.permissionSet;
|
|
@@ -1756,7 +1856,7 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1756
1856
|
}
|
|
1757
1857
|
const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
|
|
1758
1858
|
const nowMs = opts?.nowMs ?? Date.now();
|
|
1759
|
-
for (const [i, seed] of
|
|
1859
|
+
for (const [i, seed] of asArray15(stack.data).entries()) {
|
|
1760
1860
|
const seedObject = typeof seed.object === "string" ? seed.object : "";
|
|
1761
1861
|
if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
|
|
1762
1862
|
const records = Array.isArray(seed.records) ? seed.records : [];
|
|
@@ -1797,7 +1897,7 @@ function validateSecurityPosture(stack, opts) {
|
|
|
1797
1897
|
}
|
|
1798
1898
|
|
|
1799
1899
|
// src/build-access-matrix.ts
|
|
1800
|
-
function
|
|
1900
|
+
function asArray16(v) {
|
|
1801
1901
|
if (Array.isArray(v)) return v;
|
|
1802
1902
|
if (v && typeof v === "object") {
|
|
1803
1903
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1808,13 +1908,13 @@ function buildAccessMatrix(stack) {
|
|
|
1808
1908
|
const entries = [];
|
|
1809
1909
|
if (!stack || typeof stack !== "object") return { version: 1, entries };
|
|
1810
1910
|
const owdByObject = /* @__PURE__ */ new Map();
|
|
1811
|
-
for (const obj of
|
|
1911
|
+
for (const obj of asArray16(stack.objects)) {
|
|
1812
1912
|
const name = typeof obj.name === "string" ? obj.name : "";
|
|
1813
1913
|
if (!name) continue;
|
|
1814
1914
|
const owd = obj.sharingModel ?? obj.security?.sharingModel;
|
|
1815
1915
|
if (typeof owd === "string") owdByObject.set(name, owd);
|
|
1816
1916
|
}
|
|
1817
|
-
for (const ps of
|
|
1917
|
+
for (const ps of asArray16(stack.permissions)) {
|
|
1818
1918
|
const psName = typeof ps.name === "string" ? ps.name : "";
|
|
1819
1919
|
if (!psName) continue;
|
|
1820
1920
|
const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
|
|
@@ -1919,6 +2019,8 @@ function diffAccessMatrix(before, after) {
|
|
|
1919
2019
|
TABLE_COUNT_ONLY,
|
|
1920
2020
|
TITLE_FORMAT_RETIRED,
|
|
1921
2021
|
TITLE_UNRESOLVABLE,
|
|
2022
|
+
VISIBILITY_ALIAS_DEPRECATED,
|
|
2023
|
+
VISIBILITY_ROOT_MISLAYERED,
|
|
1922
2024
|
WIDGET_DATASET_UNKNOWN,
|
|
1923
2025
|
WIDGET_DIMENSION_UNKNOWN,
|
|
1924
2026
|
WIDGET_MEASURE_UNKNOWN,
|
|
@@ -1937,6 +2039,7 @@ function diffAccessMatrix(before, after) {
|
|
|
1937
2039
|
validateSecurityPosture,
|
|
1938
2040
|
validateSemanticRoles,
|
|
1939
2041
|
validateStackExpressions,
|
|
2042
|
+
validateVisibilityPredicates,
|
|
1940
2043
|
validateWidgetBindings
|
|
1941
2044
|
});
|
|
1942
2045
|
//# sourceMappingURL=index.cjs.map
|