@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 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 asArray12(v) {
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 asArray12(stack.permissions)) {
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 asArray12(stack.data)) {
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 = asArray12(stack.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 = asArray12(obj.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 asArray12(obj.actions).entries()) {
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 asArray12(stack.actions).entries()) {
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 = asArray12(stack.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 asArray13(v) {
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 = asArray13(stack.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 asArray14(v) {
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 asArray14(obj.fields)) {
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 = asArray14(stack.objects);
1528
- const permissionSets = asArray14(stack.permissions);
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 asArray14(obj.fields)) {
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 asArray14(obj.actions).entries()) {
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 asArray14(stack.positions).entries()) {
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 asArray14(stack.apps).entries()) {
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 asArray14(stack.books).entries()) {
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 asArray14(stack.books).entries()) {
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 asArray14(stack.data).entries()) {
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 asArray15(v) {
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 asArray15(stack.objects)) {
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 asArray15(stack.permissions)) {
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