@objectstack/lint 14.7.0 → 15.0.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 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,121 @@ 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 usesRoot(source, root) {
1298
+ return new RegExp(`(^|[^.\\w$])${root}\\.\\w`).test(source);
1299
+ }
1300
+ var MISLAYER_BY_LAYER = {
1301
+ runtime: {
1302
+ forbiddenRoot: "data",
1303
+ 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).",
1304
+ hint: "Runtime record surfaces bind `record` + `current_user` (pages also expose `page.<var>`). Use e.g. `record.status == 'open'` instead of `data.status == 'open'`."
1305
+ },
1306
+ metadata: {
1307
+ forbiddenRoot: "record",
1308
+ message: "visibility predicate is rooted at `record.` \u2014 that is the runtime record-surface root (a `*.view.ts` / `*.page.ts` live record), not a metadata-editing form. A `*.form.ts` predicate that binds `record.` never matches and the element renders unconditionally (ADR-0089).",
1309
+ hint: "Metadata-editing forms bind `data` (the row under edit). Use e.g. `data.type == 'grid'` instead of `record.type == 'grid'`."
1310
+ }
1311
+ };
1312
+ function checkElement(el, where, path, layer, findings) {
1313
+ for (const alias of ALIASES) {
1314
+ if (el[alias] !== void 0) {
1315
+ findings.push({
1316
+ severity: "warning",
1317
+ rule: VISIBILITY_ALIAS_DEPRECATED,
1318
+ where,
1319
+ path: `${path}.${alias}`,
1320
+ 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\`.`,
1321
+ hint: `Rename the key \`${alias}\` \u2192 \`visibleWhen\` (same CEL value).`
1322
+ });
1323
+ }
1324
+ }
1325
+ const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
1326
+ const source = predicateSource(raw);
1327
+ const rule = MISLAYER_BY_LAYER[layer];
1328
+ if (source && usesRoot(source, rule.forbiddenRoot)) {
1329
+ findings.push({
1330
+ severity: "warning",
1331
+ rule: VISIBILITY_ROOT_MISLAYERED,
1332
+ where,
1333
+ path,
1334
+ message: rule.message,
1335
+ hint: rule.hint
1336
+ });
1337
+ }
1338
+ }
1339
+ function isFieldObject(entry) {
1340
+ return !!entry && typeof entry === "object" && !Array.isArray(entry);
1341
+ }
1342
+ function validateVisibilityPredicates(stack, opts = {}) {
1343
+ const layer = opts.layer ?? "runtime";
1344
+ const findings = [];
1345
+ const views = asArray12(stack.views);
1346
+ for (let i = 0; i < views.length; i++) {
1347
+ const view = views[i];
1348
+ if (!view || typeof view !== "object") continue;
1349
+ const viewName = typeof view.name === "string" ? view.name : `(view ${i})`;
1350
+ const where = `view "${viewName}"`;
1351
+ for (const bucket of ["sections", "groups"]) {
1352
+ const sections = Array.isArray(view[bucket]) ? view[bucket] : [];
1353
+ for (let s = 0; s < sections.length; s++) {
1354
+ const sec = sections[s];
1355
+ if (!sec || typeof sec !== "object") continue;
1356
+ const secPath = `views[${i}].${bucket}[${s}]`;
1357
+ checkElement(sec, where, secPath, layer, findings);
1358
+ const secFields = Array.isArray(sec.fields) ? sec.fields : [];
1359
+ for (let f = 0; f < secFields.length; f++) {
1360
+ const entry = secFields[f];
1361
+ if (isFieldObject(entry)) {
1362
+ checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1367
+ }
1368
+ const pages = asArray12(stack.pages);
1369
+ for (let i = 0; i < pages.length; i++) {
1370
+ const page = pages[i];
1371
+ if (!page || typeof page !== "object") continue;
1372
+ const pageName = typeof page.name === "string" ? page.name : `(page ${i})`;
1373
+ const where = `page "${pageName}"`;
1374
+ const regions = Array.isArray(page.regions) ? page.regions : [];
1375
+ for (let r = 0; r < regions.length; r++) {
1376
+ const region = regions[r];
1377
+ const components = region && typeof region === "object" && Array.isArray(region.components) ? region.components : [];
1378
+ for (let c = 0; c < components.length; c++) {
1379
+ const comp = components[c];
1380
+ if (comp && typeof comp === "object") {
1381
+ checkElement(comp, where, `pages[${i}].regions[${r}].components[${c}]`, layer, findings);
1382
+ }
1383
+ }
1384
+ }
1385
+ }
1386
+ return findings;
1387
+ }
1388
+
1275
1389
  // src/validate-capability-references.ts
1276
1390
  var import_security = require("@objectstack/spec/security");
1277
1391
  var CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
1278
- function asArray12(v) {
1392
+ function asArray13(v) {
1279
1393
  if (Array.isArray(v)) return v;
1280
1394
  if (v && typeof v === "object") {
1281
1395
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1300,17 +1414,20 @@ function validateCapabilityReferences(stack) {
1300
1414
  const findings = [];
1301
1415
  if (!stack || typeof stack !== "object") return findings;
1302
1416
  const known = new Set(import_security.PLATFORM_CAPABILITY_NAMES);
1303
- for (const ps of asArray12(stack.permissions)) {
1417
+ for (const cap of asArray13(stack.capabilities)) {
1418
+ if (typeof cap.name === "string" && cap.name.length > 0) known.add(cap.name);
1419
+ }
1420
+ for (const ps of asArray13(stack.permissions)) {
1304
1421
  for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
1305
1422
  }
1306
- for (const seed of asArray12(stack.data)) {
1423
+ for (const seed of asArray13(stack.data)) {
1307
1424
  if (seed.object !== "sys_capability") continue;
1308
1425
  for (const rec of Array.isArray(seed.records) ? seed.records : []) {
1309
1426
  const name = rec?.name;
1310
1427
  if (typeof name === "string" && name.length > 0) known.add(name);
1311
1428
  }
1312
1429
  }
1313
- const hint = "Fix the capability name, declare it on a permission set\u2019s systemPermissions, ship a sys_capability seed row, or ignore this if the capability is provided by another installed package (references fail closed at runtime).";
1430
+ const hint = "Fix the capability name, define it with defineCapability (stack.capabilities), declare it on a permission set\u2019s systemPermissions, ship a sys_capability seed row, or ignore this if the capability is provided by another installed package (references fail closed at runtime).";
1314
1431
  const flag = (cap, where, path) => {
1315
1432
  if (known.has(cap)) return;
1316
1433
  findings.push({
@@ -1322,7 +1439,7 @@ function validateCapabilityReferences(stack) {
1322
1439
  hint
1323
1440
  });
1324
1441
  };
1325
- const objects = asArray12(stack.objects);
1442
+ const objects = asArray13(stack.objects);
1326
1443
  for (let i = 0; i < objects.length; i++) {
1327
1444
  const obj = objects[i];
1328
1445
  if (!obj || typeof obj !== "object") continue;
@@ -1331,27 +1448,27 @@ function validateCapabilityReferences(stack) {
1331
1448
  for (const { cap, key } of flattenObjectRequired(obj.requiredPermissions)) {
1332
1449
  flag(cap, `object "${objName}"`, `${objPath}.requiredPermissions${key ? `.${key}` : ""}`);
1333
1450
  }
1334
- const fields = asArray12(obj.fields);
1451
+ const fields = asArray13(obj.fields);
1335
1452
  for (const f of fields) {
1336
1453
  const fname = typeof f.name === "string" ? f.name : "(field)";
1337
1454
  for (const cap of asCapArray(f.requiredPermissions)) {
1338
1455
  flag(cap, `field "${objName}.${fname}"`, `${objPath}.fields.${fname}.requiredPermissions`);
1339
1456
  }
1340
1457
  }
1341
- for (const [ai, action] of asArray12(obj.actions).entries()) {
1458
+ for (const [ai, action] of asArray13(obj.actions).entries()) {
1342
1459
  const aName = typeof action.name === "string" ? action.name : `(action ${ai})`;
1343
1460
  for (const cap of asCapArray(action.requiredPermissions)) {
1344
1461
  flag(cap, `action "${objName}.${aName}"`, `${objPath}.actions[${ai}].requiredPermissions`);
1345
1462
  }
1346
1463
  }
1347
1464
  }
1348
- for (const [i, action] of asArray12(stack.actions).entries()) {
1465
+ for (const [i, action] of asArray13(stack.actions).entries()) {
1349
1466
  const aName = typeof action.name === "string" ? action.name : `(action ${i})`;
1350
1467
  for (const cap of asCapArray(action.requiredPermissions)) {
1351
1468
  flag(cap, `action "${aName}"`, `actions[${i}].requiredPermissions`);
1352
1469
  }
1353
1470
  }
1354
- const apps = asArray12(stack.apps);
1471
+ const apps = asArray13(stack.apps);
1355
1472
  for (let i = 0; i < apps.length; i++) {
1356
1473
  const app = apps[i];
1357
1474
  if (!app || typeof app !== "object") continue;
@@ -1387,7 +1504,7 @@ var TYPE_FIX = {
1387
1504
  business_unit: "department",
1388
1505
  bu: "department"
1389
1506
  };
1390
- function asArray13(v) {
1507
+ function asArray14(v) {
1391
1508
  if (Array.isArray(v)) return v;
1392
1509
  if (v && typeof v === "object") {
1393
1510
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1397,7 +1514,7 @@ function asArray13(v) {
1397
1514
  function validateApprovalApprovers(stack) {
1398
1515
  const findings = [];
1399
1516
  if (!stack || typeof stack !== "object") return findings;
1400
- const flows = asArray13(stack.flows);
1517
+ const flows = asArray14(stack.flows);
1401
1518
  const validTypes = new Set(import_automation.ApproverType.options);
1402
1519
  for (let fi = 0; fi < flows.length; fi++) {
1403
1520
  const flow = flows[fi];
@@ -1485,7 +1602,7 @@ var OWD_WIDTH = {
1485
1602
  public_read: 1,
1486
1603
  public_read_write: 2
1487
1604
  };
1488
- function asArray14(v) {
1605
+ function asArray15(v) {
1489
1606
  if (Array.isArray(v)) return v;
1490
1607
  if (v && typeof v === "object") {
1491
1608
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1511,7 +1628,7 @@ function refOf(def) {
1511
1628
  return typeof r === "string" && r ? r : void 0;
1512
1629
  }
1513
1630
  function firstMasterDetailField(obj) {
1514
- for (const f of asArray14(obj.fields)) {
1631
+ for (const f of asArray15(obj.fields)) {
1515
1632
  if (f.type === "master_detail") {
1516
1633
  return { name: String(f.name ?? "?"), parent: refOf(f) };
1517
1634
  }
@@ -1524,8 +1641,8 @@ function grantsObjectAccess(p) {
1524
1641
  function validateSecurityPosture(stack, opts) {
1525
1642
  const findings = [];
1526
1643
  if (!stack || typeof stack !== "object") return findings;
1527
- const objects = asArray14(stack.objects);
1528
- const permissionSets = asArray14(stack.permissions);
1644
+ const objects = asArray15(stack.objects);
1645
+ const permissionSets = asArray15(stack.permissions);
1529
1646
  for (let i = 0; i < objects.length; i++) {
1530
1647
  const obj = objects[i];
1531
1648
  if (!obj || typeof obj !== "object") continue;
@@ -1654,10 +1771,10 @@ function validateSecurityPosture(stack, opts) {
1654
1771
  if (!obj || typeof obj !== "object" || isSystemObject(obj)) continue;
1655
1772
  const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
1656
1773
  flagRole("object", obj.name, obj.label, `object "${objName}"`, `objects[${i}].name`);
1657
- for (const f of asArray14(obj.fields)) {
1774
+ for (const f of asArray15(obj.fields)) {
1658
1775
  flagRole("field", f.name, f.label, `field "${objName}.${String(f.name ?? "?")}"`, `objects[${i}].fields.${String(f.name ?? "?")}.name`);
1659
1776
  }
1660
- for (const [ai, action] of asArray14(obj.actions).entries()) {
1777
+ for (const [ai, action] of asArray15(obj.actions).entries()) {
1661
1778
  flagRole("action", action.name, action.label, `action "${objName}.${String(action.name ?? "?")}"`, `objects[${i}].actions[${ai}].name`);
1662
1779
  }
1663
1780
  }
@@ -1666,19 +1783,19 @@ function validateSecurityPosture(stack, opts) {
1666
1783
  if (!ps || typeof ps !== "object") continue;
1667
1784
  flagRole("permission set", ps.name, ps.label, `permission set "${String(ps.name ?? i)}"`, `permissions[${i}].name`);
1668
1785
  }
1669
- for (const [i, pos] of asArray14(stack.positions).entries()) {
1786
+ for (const [i, pos] of asArray15(stack.positions).entries()) {
1670
1787
  flagRole("position", pos.name, pos.label, `position "${String(pos.name ?? i)}"`, `positions[${i}].name`);
1671
1788
  }
1672
- for (const [i, app] of asArray14(stack.apps).entries()) {
1789
+ for (const [i, app] of asArray15(stack.apps).entries()) {
1673
1790
  flagRole("app", app.name, app.label, `app "${String(app.name ?? i)}"`, `apps[${i}].name`);
1674
1791
  }
1675
- for (const [i, book] of asArray14(stack.books).entries()) {
1792
+ for (const [i, book] of asArray15(stack.books).entries()) {
1676
1793
  flagRole("book", book.name, book.label, `book "${String(book.name ?? i)}"`, `books[${i}].name`);
1677
1794
  }
1678
1795
  const stackSetNames = new Set(
1679
1796
  permissionSets.map((ps) => typeof ps.name === "string" ? ps.name : void 0).filter((n) => !!n)
1680
1797
  );
1681
- for (const [i, book] of asArray14(stack.books).entries()) {
1798
+ for (const [i, book] of asArray15(stack.books).entries()) {
1682
1799
  const audience = book.audience;
1683
1800
  if (!audience || typeof audience !== "object") continue;
1684
1801
  const setName = audience.permissionSet;
@@ -1756,7 +1873,7 @@ function validateSecurityPosture(stack, opts) {
1756
1873
  }
1757
1874
  const GRANT_SEED_OBJECTS = /* @__PURE__ */ new Set(["sys_user_position", "sys_user_permission_set"]);
1758
1875
  const nowMs = opts?.nowMs ?? Date.now();
1759
- for (const [i, seed] of asArray14(stack.data).entries()) {
1876
+ for (const [i, seed] of asArray15(stack.data).entries()) {
1760
1877
  const seedObject = typeof seed.object === "string" ? seed.object : "";
1761
1878
  if (!GRANT_SEED_OBJECTS.has(seedObject)) continue;
1762
1879
  const records = Array.isArray(seed.records) ? seed.records : [];
@@ -1797,7 +1914,7 @@ function validateSecurityPosture(stack, opts) {
1797
1914
  }
1798
1915
 
1799
1916
  // src/build-access-matrix.ts
1800
- function asArray15(v) {
1917
+ function asArray16(v) {
1801
1918
  if (Array.isArray(v)) return v;
1802
1919
  if (v && typeof v === "object") {
1803
1920
  return Object.entries(v).map(([name, def]) => ({ name, ...def }));
@@ -1808,13 +1925,13 @@ function buildAccessMatrix(stack) {
1808
1925
  const entries = [];
1809
1926
  if (!stack || typeof stack !== "object") return { version: 1, entries };
1810
1927
  const owdByObject = /* @__PURE__ */ new Map();
1811
- for (const obj of asArray15(stack.objects)) {
1928
+ for (const obj of asArray16(stack.objects)) {
1812
1929
  const name = typeof obj.name === "string" ? obj.name : "";
1813
1930
  if (!name) continue;
1814
1931
  const owd = obj.sharingModel ?? obj.security?.sharingModel;
1815
1932
  if (typeof owd === "string") owdByObject.set(name, owd);
1816
1933
  }
1817
- for (const ps of asArray15(stack.permissions)) {
1934
+ for (const ps of asArray16(stack.permissions)) {
1818
1935
  const psName = typeof ps.name === "string" ? ps.name : "";
1819
1936
  if (!psName) continue;
1820
1937
  const objects = ps.objects && typeof ps.objects === "object" ? ps.objects : {};
@@ -1919,6 +2036,8 @@ function diffAccessMatrix(before, after) {
1919
2036
  TABLE_COUNT_ONLY,
1920
2037
  TITLE_FORMAT_RETIRED,
1921
2038
  TITLE_UNRESOLVABLE,
2039
+ VISIBILITY_ALIAS_DEPRECATED,
2040
+ VISIBILITY_ROOT_MISLAYERED,
1922
2041
  WIDGET_DATASET_UNKNOWN,
1923
2042
  WIDGET_DIMENSION_UNKNOWN,
1924
2043
  WIDGET_MEASURE_UNKNOWN,
@@ -1937,6 +2056,7 @@ function diffAccessMatrix(before, after) {
1937
2056
  validateSecurityPosture,
1938
2057
  validateSemanticRoles,
1939
2058
  validateStackExpressions,
2059
+ validateVisibilityPredicates,
1940
2060
  validateWidgetBindings
1941
2061
  });
1942
2062
  //# sourceMappingURL=index.cjs.map