@objectstack/lint 11.2.0 → 11.4.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.d.cts CHANGED
@@ -69,7 +69,7 @@ interface WidgetBindingFinding {
69
69
  /** How to fix (or deliberately suppress) it. */
70
70
  hint: string;
71
71
  }
72
- type AnyRec$3 = Record<string, unknown>;
72
+ type AnyRec$5 = Record<string, unknown>;
73
73
  /**
74
74
  * Validate every dashboard widget's dataset binding. Returns the list of
75
75
  * findings (empty = clean). Caller decides how to surface them: `error`
@@ -77,7 +77,7 @@ type AnyRec$3 = Record<string, unknown>;
77
77
  * should fail validate/build; `warning` findings are advisory and must
78
78
  * never fail the build on their own.
79
79
  */
80
- declare function validateWidgetBindings(stack: AnyRec$3): WidgetBindingFinding[];
80
+ declare function validateWidgetBindings(stack: AnyRec$5): WidgetBindingFinding[];
81
81
 
82
82
  interface ExprIssue {
83
83
  where: string;
@@ -90,12 +90,12 @@ interface ExprIssue {
90
90
  */
91
91
  severity?: 'error' | 'warning';
92
92
  }
93
- type AnyRec$2 = Record<string, unknown>;
93
+ type AnyRec$4 = Record<string, unknown>;
94
94
  /**
95
95
  * Validate every predicate in the stack. Returns the list of issues (empty =
96
96
  * clean). Caller decides how to surface / whether to fail the build.
97
97
  */
98
- declare function validateStackExpressions(stack: AnyRec$2): ExprIssue[];
98
+ declare function validateStackExpressions(stack: AnyRec$4): ExprIssue[];
99
99
 
100
100
  type StyleSeverity = 'error' | 'warning';
101
101
  interface StyleFinding {
@@ -113,14 +113,14 @@ declare const STYLE_CLASSNAME_TAILWIND = "style-classname-tailwind";
113
113
  declare const STYLE_RESPONSIVE_NO_BASE = "style-responsive-no-base";
114
114
  declare const STYLE_UNKNOWN_CSS_PROPERTY = "style-unknown-css-property";
115
115
  declare const STYLE_UNKNOWN_TOKEN = "style-unknown-token";
116
- type AnyRec$1 = Record<string, unknown>;
116
+ type AnyRec$3 = Record<string, unknown>;
117
117
  /**
118
118
  * Validate every page's component tree for SDUI styling correctness (ADR-0065).
119
119
  * Returns findings (empty = clean). `error` findings describe styles that are
120
120
  * silently dropped and should fail validate/build; `warning` findings are
121
121
  * advisory (typos, drift, footguns).
122
122
  */
123
- declare function validateResponsiveStyles(stack: AnyRec$1): StyleFinding[];
123
+ declare function validateResponsiveStyles(stack: AnyRec$3): StyleFinding[];
124
124
 
125
125
  type JsxPageSeverity = 'error' | 'warning';
126
126
  interface JsxPageFinding {
@@ -133,9 +133,72 @@ interface JsxPageFinding {
133
133
  message: string;
134
134
  hint: string;
135
135
  }
136
- type AnyRec = Record<string, unknown>;
137
- declare function validateJsxPages(stack: AnyRec, opts?: {
136
+ type AnyRec$2 = Record<string, unknown>;
137
+ declare function validateJsxPages(stack: AnyRec$2, opts?: {
138
138
  manifest?: Manifest;
139
139
  }): JsxPageFinding[];
140
140
 
141
- export { CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type ExprIssue, type JsxPageFinding, type JsxPageSeverity, MEASURE_AGGREGATE_INCOHERENT, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, validateJsxPages, validateResponsiveStyles, validateStackExpressions, validateWidgetBindings };
141
+ type ReactPageSeverity = 'error' | 'warning';
142
+ interface ReactPageFinding {
143
+ severity: ReactPageSeverity;
144
+ rule: string;
145
+ where: string;
146
+ path: string;
147
+ message: string;
148
+ hint: string;
149
+ }
150
+ type AnyRec$1 = Record<string, unknown>;
151
+ declare function validateReactPages(stack: AnyRec$1): ReactPageFinding[];
152
+
153
+ /**
154
+ * Build-time record-title diagnostics (ADR-0079).
155
+ *
156
+ * A record's human title is a structural invariant: every object resolves a
157
+ * primary title from a real STORED field via `nameField` (the canonical
158
+ * pointer; `displayNameField` is the deprecated alias) or a deterministic
159
+ * derivation. Two authoring smells are flagged here so `os build`/`os lint`,
160
+ * the MCP authoring surface, and hand-authoring all get the coverage cloud
161
+ * graph-lint already has (the ADR-0078 "not cloud-only" principle):
162
+ *
163
+ * - `title-format-retired` — the object declares a `titleFormat`. That field
164
+ * is a RENDER-ONLY template the server can neither return nor query; ADR-0079
165
+ * retires it in favour of `nameField`. The schema still parses it (existing
166
+ * metadata keeps loading), so this is advisory, not an error.
167
+ * - `title-unresolvable` — `objectTitleCompleteness` reports `status: 'none'`:
168
+ * no `nameField`/`displayNameField` pointer and no title-eligible field to
169
+ * derive one from. Records will have no meaningful title (the runtime falls
170
+ * back to the auto-provisioned primary / `Record #<id>` floor), so this is a
171
+ * warning, not an error — nothing is fully broken.
172
+ *
173
+ * Both are warnings: the auto-provision transform and the id floor mean a
174
+ * green build never ships a fully title-less object. Reuses the shared spec
175
+ * predicate (`@objectstack/spec/data` → display-name) so cloud and framework
176
+ * classify titles identically.
177
+ */
178
+ declare const TITLE_FORMAT_RETIRED = "title-format-retired";
179
+ declare const TITLE_UNRESOLVABLE = "title-unresolvable";
180
+ type RecordTitleSeverity = 'error' | 'warning';
181
+ interface RecordTitleFinding {
182
+ /** Always `warning` today — both rules are advisory (see module note). */
183
+ severity: RecordTitleSeverity;
184
+ /** Diagnostic rule id (registry entry), e.g. `title-format-retired`. */
185
+ rule: string;
186
+ /** Human-readable location, e.g. `object "invoice"`. */
187
+ where: string;
188
+ /** Config path, e.g. `objects[3]`. */
189
+ path: string;
190
+ /** What is wrong. */
191
+ message: string;
192
+ /** How to fix it. */
193
+ hint: string;
194
+ }
195
+ type AnyRec = Record<string, unknown>;
196
+ /**
197
+ * Validate every object's record-title declaration. Returns the list of
198
+ * findings (empty = clean). Both rules are advisory (`warning`): the caller
199
+ * must never fail the build on them alone — auto-provision + the `Record #<id>`
200
+ * floor guarantee a resolvable title at runtime.
201
+ */
202
+ declare function validateRecordTitle(stack: AnyRec): RecordTitleFinding[];
203
+
204
+ export { CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type ExprIssue, type JsxPageFinding, type JsxPageSeverity, MEASURE_AGGREGATE_INCOHERENT, type ReactPageFinding, type ReactPageSeverity, type RecordTitleFinding, type RecordTitleSeverity, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, validateJsxPages, validateReactPages, validateRecordTitle, validateResponsiveStyles, validateStackExpressions, validateWidgetBindings };
package/dist/index.d.ts CHANGED
@@ -69,7 +69,7 @@ interface WidgetBindingFinding {
69
69
  /** How to fix (or deliberately suppress) it. */
70
70
  hint: string;
71
71
  }
72
- type AnyRec$3 = Record<string, unknown>;
72
+ type AnyRec$5 = Record<string, unknown>;
73
73
  /**
74
74
  * Validate every dashboard widget's dataset binding. Returns the list of
75
75
  * findings (empty = clean). Caller decides how to surface them: `error`
@@ -77,7 +77,7 @@ type AnyRec$3 = Record<string, unknown>;
77
77
  * should fail validate/build; `warning` findings are advisory and must
78
78
  * never fail the build on their own.
79
79
  */
80
- declare function validateWidgetBindings(stack: AnyRec$3): WidgetBindingFinding[];
80
+ declare function validateWidgetBindings(stack: AnyRec$5): WidgetBindingFinding[];
81
81
 
82
82
  interface ExprIssue {
83
83
  where: string;
@@ -90,12 +90,12 @@ interface ExprIssue {
90
90
  */
91
91
  severity?: 'error' | 'warning';
92
92
  }
93
- type AnyRec$2 = Record<string, unknown>;
93
+ type AnyRec$4 = Record<string, unknown>;
94
94
  /**
95
95
  * Validate every predicate in the stack. Returns the list of issues (empty =
96
96
  * clean). Caller decides how to surface / whether to fail the build.
97
97
  */
98
- declare function validateStackExpressions(stack: AnyRec$2): ExprIssue[];
98
+ declare function validateStackExpressions(stack: AnyRec$4): ExprIssue[];
99
99
 
100
100
  type StyleSeverity = 'error' | 'warning';
101
101
  interface StyleFinding {
@@ -113,14 +113,14 @@ declare const STYLE_CLASSNAME_TAILWIND = "style-classname-tailwind";
113
113
  declare const STYLE_RESPONSIVE_NO_BASE = "style-responsive-no-base";
114
114
  declare const STYLE_UNKNOWN_CSS_PROPERTY = "style-unknown-css-property";
115
115
  declare const STYLE_UNKNOWN_TOKEN = "style-unknown-token";
116
- type AnyRec$1 = Record<string, unknown>;
116
+ type AnyRec$3 = Record<string, unknown>;
117
117
  /**
118
118
  * Validate every page's component tree for SDUI styling correctness (ADR-0065).
119
119
  * Returns findings (empty = clean). `error` findings describe styles that are
120
120
  * silently dropped and should fail validate/build; `warning` findings are
121
121
  * advisory (typos, drift, footguns).
122
122
  */
123
- declare function validateResponsiveStyles(stack: AnyRec$1): StyleFinding[];
123
+ declare function validateResponsiveStyles(stack: AnyRec$3): StyleFinding[];
124
124
 
125
125
  type JsxPageSeverity = 'error' | 'warning';
126
126
  interface JsxPageFinding {
@@ -133,9 +133,72 @@ interface JsxPageFinding {
133
133
  message: string;
134
134
  hint: string;
135
135
  }
136
- type AnyRec = Record<string, unknown>;
137
- declare function validateJsxPages(stack: AnyRec, opts?: {
136
+ type AnyRec$2 = Record<string, unknown>;
137
+ declare function validateJsxPages(stack: AnyRec$2, opts?: {
138
138
  manifest?: Manifest;
139
139
  }): JsxPageFinding[];
140
140
 
141
- export { CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type ExprIssue, type JsxPageFinding, type JsxPageSeverity, MEASURE_AGGREGATE_INCOHERENT, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, validateJsxPages, validateResponsiveStyles, validateStackExpressions, validateWidgetBindings };
141
+ type ReactPageSeverity = 'error' | 'warning';
142
+ interface ReactPageFinding {
143
+ severity: ReactPageSeverity;
144
+ rule: string;
145
+ where: string;
146
+ path: string;
147
+ message: string;
148
+ hint: string;
149
+ }
150
+ type AnyRec$1 = Record<string, unknown>;
151
+ declare function validateReactPages(stack: AnyRec$1): ReactPageFinding[];
152
+
153
+ /**
154
+ * Build-time record-title diagnostics (ADR-0079).
155
+ *
156
+ * A record's human title is a structural invariant: every object resolves a
157
+ * primary title from a real STORED field via `nameField` (the canonical
158
+ * pointer; `displayNameField` is the deprecated alias) or a deterministic
159
+ * derivation. Two authoring smells are flagged here so `os build`/`os lint`,
160
+ * the MCP authoring surface, and hand-authoring all get the coverage cloud
161
+ * graph-lint already has (the ADR-0078 "not cloud-only" principle):
162
+ *
163
+ * - `title-format-retired` — the object declares a `titleFormat`. That field
164
+ * is a RENDER-ONLY template the server can neither return nor query; ADR-0079
165
+ * retires it in favour of `nameField`. The schema still parses it (existing
166
+ * metadata keeps loading), so this is advisory, not an error.
167
+ * - `title-unresolvable` — `objectTitleCompleteness` reports `status: 'none'`:
168
+ * no `nameField`/`displayNameField` pointer and no title-eligible field to
169
+ * derive one from. Records will have no meaningful title (the runtime falls
170
+ * back to the auto-provisioned primary / `Record #<id>` floor), so this is a
171
+ * warning, not an error — nothing is fully broken.
172
+ *
173
+ * Both are warnings: the auto-provision transform and the id floor mean a
174
+ * green build never ships a fully title-less object. Reuses the shared spec
175
+ * predicate (`@objectstack/spec/data` → display-name) so cloud and framework
176
+ * classify titles identically.
177
+ */
178
+ declare const TITLE_FORMAT_RETIRED = "title-format-retired";
179
+ declare const TITLE_UNRESOLVABLE = "title-unresolvable";
180
+ type RecordTitleSeverity = 'error' | 'warning';
181
+ interface RecordTitleFinding {
182
+ /** Always `warning` today — both rules are advisory (see module note). */
183
+ severity: RecordTitleSeverity;
184
+ /** Diagnostic rule id (registry entry), e.g. `title-format-retired`. */
185
+ rule: string;
186
+ /** Human-readable location, e.g. `object "invoice"`. */
187
+ where: string;
188
+ /** Config path, e.g. `objects[3]`. */
189
+ path: string;
190
+ /** What is wrong. */
191
+ message: string;
192
+ /** How to fix it. */
193
+ hint: string;
194
+ }
195
+ type AnyRec = Record<string, unknown>;
196
+ /**
197
+ * Validate every object's record-title declaration. Returns the list of
198
+ * findings (empty = clean). Both rules are advisory (`warning`): the caller
199
+ * must never fail the build on them alone — auto-provision + the `Record #<id>`
200
+ * floor guarantee a resolvable title at runtime.
201
+ */
202
+ declare function validateRecordTitle(stack: AnyRec): RecordTitleFinding[];
203
+
204
+ export { CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type ExprIssue, type JsxPageFinding, type JsxPageSeverity, MEASURE_AGGREGATE_INCOHERENT, type ReactPageFinding, type ReactPageSeverity, type RecordTitleFinding, type RecordTitleSeverity, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, validateJsxPages, validateReactPages, validateRecordTitle, validateResponsiveStyles, validateStackExpressions, validateWidgetBindings };
package/dist/index.js CHANGED
@@ -690,7 +690,7 @@ function validateJsxPages(stack, opts = {}) {
690
690
  const pages = asArray4(stack.pages);
691
691
  for (let p = 0; p < pages.length; p++) {
692
692
  const page = pages[p];
693
- if (!page || page.kind !== "jsx") continue;
693
+ if (!page || page.kind !== "html" && page.kind !== "jsx") continue;
694
694
  const name = String(page.name ?? `#${p}`);
695
695
  const source = page.source;
696
696
  if (typeof source !== "string" || source.trim() === "") {
@@ -699,7 +699,7 @@ function validateJsxPages(stack, opts = {}) {
699
699
  rule: "jsx-page-empty-source",
700
700
  where: `page "${name}"`,
701
701
  path: `pages[${p}].source`,
702
- message: "kind:'jsx' page has no `source`.",
702
+ message: `kind:'${page.kind}' page has no \`source\`.`,
703
703
  hint: "Author the page as a constrained JSX/Tailwind string in `source`."
704
704
  });
705
705
  continue;
@@ -718,6 +718,89 @@ function validateJsxPages(stack, opts = {}) {
718
718
  }
719
719
  return findings;
720
720
  }
721
+
722
+ // src/validate-react-pages.ts
723
+ import { transform } from "sucrase";
724
+ var asArray5 = (v) => Array.isArray(v) ? v : [];
725
+ function validateReactPages(stack) {
726
+ const findings = [];
727
+ const pages = asArray5(stack.pages);
728
+ for (let p = 0; p < pages.length; p++) {
729
+ const page = pages[p];
730
+ if (!page || page.kind !== "react") continue;
731
+ const name = String(page.name ?? `#${p}`);
732
+ const source = page.source;
733
+ if (typeof source !== "string" || source.trim() === "") {
734
+ findings.push({
735
+ severity: "error",
736
+ rule: "react-page-empty-source",
737
+ where: `page "${name}"`,
738
+ path: `pages[${p}].source`,
739
+ message: "kind:'react' page has no `source`.",
740
+ hint: "Author the page as a real React component string in `source`."
741
+ });
742
+ continue;
743
+ }
744
+ try {
745
+ transform(source, { transforms: ["jsx", "typescript"], production: true });
746
+ } catch (err) {
747
+ const message = err instanceof Error ? err.message : String(err);
748
+ findings.push({
749
+ severity: "error",
750
+ rule: "react-page-syntax",
751
+ where: `page "${name}"`,
752
+ path: `pages[${p}].source`,
753
+ message: `kind:'react' source has a syntax error: ${message.split("\n")[0]}`,
754
+ hint: "The source is transpiled (never executed) at build to catch syntax errors early \u2014 fix the JS/JSX."
755
+ });
756
+ }
757
+ }
758
+ return findings;
759
+ }
760
+
761
+ // src/validate-record-title.ts
762
+ import { objectTitleCompleteness } from "@objectstack/spec/data";
763
+ var TITLE_FORMAT_RETIRED = "title-format-retired";
764
+ var TITLE_UNRESOLVABLE = "title-unresolvable";
765
+ function asArray6(v) {
766
+ if (Array.isArray(v)) return v;
767
+ if (v && typeof v === "object") {
768
+ return Object.entries(v).map(([name, def]) => ({ name, ...def }));
769
+ }
770
+ return [];
771
+ }
772
+ function validateRecordTitle(stack) {
773
+ const findings = [];
774
+ const objects = asArray6(stack.objects);
775
+ for (let i = 0; i < objects.length; i++) {
776
+ const obj = objects[i];
777
+ const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
778
+ const where = `object "${objName}"`;
779
+ const path = `objects[${i}]`;
780
+ if (obj.titleFormat !== void 0 && obj.titleFormat !== null && obj.titleFormat !== "") {
781
+ findings.push({
782
+ severity: "warning",
783
+ rule: TITLE_FORMAT_RETIRED,
784
+ where,
785
+ path,
786
+ message: `${objName}: titleFormat is retired (ADR-0079) \u2014 migrate to nameField (single field) or a formula field designated nameField`,
787
+ hint: `titleFormat is a render-only template the server cannot return or query, and an explicit nameField now takes precedence. For a single-field title set nameField: '<field>'. For a composite title, add a formula field (returnType: 'text') and designate it via nameField.`
788
+ });
789
+ }
790
+ const completeness = objectTitleCompleteness(obj);
791
+ if (completeness.status === "none") {
792
+ findings.push({
793
+ severity: "warning",
794
+ rule: TITLE_UNRESOLVABLE,
795
+ where,
796
+ path,
797
+ message: `${objName}: no resolvable record title \u2014 records will have no meaningful name (no nameField and no title-eligible field to derive one)`,
798
+ hint: `Set nameField to a text/email field (or a formula field with returnType: 'text'), or add a text field named "name"/"title". The runtime auto-provisions a primary and falls back to "Record #<id>", but an explicit title is far more useful.`
799
+ });
800
+ }
801
+ }
802
+ return findings;
803
+ }
721
804
  export {
722
805
  CHART_CONFIG_MISSING,
723
806
  CHART_FIELD_UNKNOWN,
@@ -728,10 +811,14 @@ export {
728
811
  STYLE_UNKNOWN_CSS_PROPERTY,
729
812
  STYLE_UNKNOWN_TOKEN,
730
813
  TABLE_COUNT_ONLY,
814
+ TITLE_FORMAT_RETIRED,
815
+ TITLE_UNRESOLVABLE,
731
816
  WIDGET_DATASET_UNKNOWN,
732
817
  WIDGET_DIMENSION_UNKNOWN,
733
818
  WIDGET_MEASURE_UNKNOWN,
734
819
  validateJsxPages,
820
+ validateReactPages,
821
+ validateRecordTitle,
735
822
  validateResponsiveStyles,
736
823
  validateStackExpressions,
737
824
  validateWidgetBindings