nodality 1.2.10 → 1.3.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.
Files changed (283) hide show
  1. package/bin/mcp-server.mjs +131 -0
  2. package/bin/schema-cli.mjs +55 -11
  3. package/dist/animator.cjs.js +1 -1
  4. package/dist/animator.cjs.js.LICENSE.txt +1 -1
  5. package/dist/animator.esm.js +1 -1
  6. package/dist/animator.esm.js.LICENSE.txt +1 -1
  7. package/dist/audionew.cjs.js +1 -1
  8. package/dist/audionew.cjs.js.LICENSE.txt +1 -1
  9. package/dist/audionew.esm.js +1 -1
  10. package/dist/audionew.esm.js.LICENSE.txt +1 -1
  11. package/dist/base.cjs.js +1 -1
  12. package/dist/base.cjs.js.LICENSE.txt +1 -1
  13. package/dist/base.esm.js +1 -1
  14. package/dist/base.esm.js.LICENSE.txt +1 -1
  15. package/dist/beta-desktop-bar.cjs.js +1 -1
  16. package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
  17. package/dist/beta-desktop-bar.esm.js +1 -1
  18. package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
  19. package/dist/beta-mobile-bar.cjs.js +1 -1
  20. package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
  21. package/dist/beta-mobile-bar.esm.js +1 -1
  22. package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
  23. package/dist/bundle.umd.js +1 -1
  24. package/dist/bundle.umd.js.LICENSE.txt +1 -28
  25. package/dist/button.cjs.js +1 -1
  26. package/dist/button.cjs.js.LICENSE.txt +1 -1
  27. package/dist/button.esm.js +1 -1
  28. package/dist/button.esm.js.LICENSE.txt +1 -1
  29. package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
  30. package/dist/card-getter.esm.js.LICENSE.txt +1 -1
  31. package/dist/center.cjs.js +1 -1
  32. package/dist/center.cjs.js.LICENSE.txt +1 -1
  33. package/dist/center.esm.js +1 -1
  34. package/dist/center.esm.js.LICENSE.txt +1 -1
  35. package/dist/checkbox.cjs.js +1 -1
  36. package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
  37. package/dist/checkbox.esm.js +1 -1
  38. package/dist/checkbox.esm.js.LICENSE.txt +1 -1
  39. package/dist/code.cjs.js +1 -1
  40. package/dist/code.cjs.js.LICENSE.txt +1 -1
  41. package/dist/code.esm.js +1 -1
  42. package/dist/code.esm.js.LICENSE.txt +1 -1
  43. package/dist/container.cjs.js +1 -1
  44. package/dist/container.cjs.js.LICENSE.txt +1 -1
  45. package/dist/container.esm.js +1 -1
  46. package/dist/container.esm.js.LICENSE.txt +1 -1
  47. package/dist/data-list.cjs.js.LICENSE.txt +1 -1
  48. package/dist/data-list.esm.js.LICENSE.txt +1 -1
  49. package/dist/designer.cjs.js +1 -1
  50. package/dist/designer.cjs.js.LICENSE.txt +1 -28
  51. package/dist/designer.esm.js +1 -1
  52. package/dist/designer.esm.js.LICENSE.txt +1 -28
  53. package/dist/element-mapper.cjs.js +1 -1
  54. package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
  55. package/dist/element-mapper.esm.js +1 -1
  56. package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
  57. package/dist/finalresult.esm.js +1 -1
  58. package/dist/finalresult.esm.js.LICENSE.txt +1 -28
  59. package/dist/flex-card.cjs.js +1 -1
  60. package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
  61. package/dist/flex-card.esm.js +1 -1
  62. package/dist/flex-card.esm.js.LICENSE.txt +1 -1
  63. package/dist/flex-grid.cjs.js +1 -1
  64. package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
  65. package/dist/flex-grid.esm.js +1 -1
  66. package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
  67. package/dist/flex-row.cjs.js +1 -1
  68. package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
  69. package/dist/flex-row.esm.js +1 -1
  70. package/dist/flex-row.esm.js.LICENSE.txt +1 -1
  71. package/dist/floating-input.cjs.js +1 -1
  72. package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
  73. package/dist/floating-input.esm.js +1 -1
  74. package/dist/floating-input.esm.js.LICENSE.txt +1 -1
  75. package/dist/free.cjs.js +1 -1
  76. package/dist/free.cjs.js.LICENSE.txt +1 -1
  77. package/dist/free.esm.js +1 -1
  78. package/dist/free.esm.js.LICENSE.txt +1 -1
  79. package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
  80. package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
  81. package/dist/image-picker.cjs.js +1 -1
  82. package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
  83. package/dist/image-picker.esm.js +1 -1
  84. package/dist/image-picker.esm.js.LICENSE.txt +1 -1
  85. package/dist/image.cjs.js +1 -1
  86. package/dist/image.cjs.js.LICENSE.txt +1 -1
  87. package/dist/image.esm.js +1 -1
  88. package/dist/image.esm.js.LICENSE.txt +1 -1
  89. package/dist/index.cjs.js +1 -1
  90. package/dist/index.cjs.js.LICENSE.txt +1 -28
  91. package/dist/index.d.ts +3 -2
  92. package/dist/index.esm.js +1 -1
  93. package/dist/index.esm.js.LICENSE.txt +1 -28
  94. package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
  95. package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
  96. package/dist/link-getter.cjs.js +1 -1
  97. package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
  98. package/dist/link-getter.esm.js +1 -1
  99. package/dist/link-getter.esm.js.LICENSE.txt +1 -1
  100. package/dist/link.cjs.js +1 -1
  101. package/dist/link.cjs.js.LICENSE.txt +1 -1
  102. package/dist/link.esm.js +1 -1
  103. package/dist/link.esm.js.LICENSE.txt +1 -1
  104. package/dist/meta-adder.cjs.js +1 -1
  105. package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
  106. package/dist/meta-adder.esm.js +1 -1
  107. package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
  108. package/dist/modal-2025.cjs.js +1 -1
  109. package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
  110. package/dist/modal-2025.esm.js +1 -1
  111. package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
  112. package/dist/multiswitcher.cjs.js +1 -1
  113. package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
  114. package/dist/multiswitcher.esm.js +1 -1
  115. package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
  116. package/dist/new-nav-bar.cjs.js +1 -1
  117. package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
  118. package/dist/new-nav-bar.esm.js +1 -1
  119. package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
  120. package/dist/picker.cjs.js +1 -1
  121. package/dist/picker.cjs.js.LICENSE.txt +1 -1
  122. package/dist/picker.esm.js +1 -1
  123. package/dist/picker.esm.js.LICENSE.txt +1 -1
  124. package/dist/progress.cjs.js +1 -1
  125. package/dist/progress.cjs.js.LICENSE.txt +1 -1
  126. package/dist/progress.esm.js +1 -1
  127. package/dist/progress.esm.js.LICENSE.txt +1 -1
  128. package/dist/radio.cjs.js.LICENSE.txt +1 -1
  129. package/dist/radio.esm.js.LICENSE.txt +1 -1
  130. package/dist/range.cjs.js.LICENSE.txt +1 -1
  131. package/dist/range.esm.js.LICENSE.txt +1 -1
  132. package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
  133. package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
  134. package/dist/side-bar.cjs.js +1 -1
  135. package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
  136. package/dist/side-bar.esm.js +1 -1
  137. package/dist/side-bar.esm.js.LICENSE.txt +1 -1
  138. package/dist/side-nav-bar.cjs.js +1 -1
  139. package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
  140. package/dist/side-nav-bar.esm.js +1 -1
  141. package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
  142. package/dist/simple-bar.cjs.js +1 -1
  143. package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
  144. package/dist/simple-bar.esm.js +1 -1
  145. package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
  146. package/dist/slider-2025.cjs.js +1 -1
  147. package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
  148. package/dist/slider-2025.esm.js +1 -1
  149. package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
  150. package/dist/spacer.cjs.js +1 -1
  151. package/dist/spacer.cjs.js.LICENSE.txt +1 -1
  152. package/dist/spacer.esm.js +1 -1
  153. package/dist/spacer.esm.js.LICENSE.txt +1 -1
  154. package/dist/stack.cjs.js +1 -1
  155. package/dist/stack.cjs.js.LICENSE.txt +1 -1
  156. package/dist/stack.esm.js +1 -1
  157. package/dist/stack.esm.js.LICENSE.txt +1 -1
  158. package/dist/stacker.cjs.js.LICENSE.txt +1 -1
  159. package/dist/stacker.esm.js.LICENSE.txt +1 -1
  160. package/dist/table.cjs.js +1 -1
  161. package/dist/table.cjs.js.LICENSE.txt +1 -1
  162. package/dist/table.esm.js +1 -1
  163. package/dist/table.esm.js.LICENSE.txt +1 -1
  164. package/dist/text-field.cjs.js +1 -1
  165. package/dist/text-field.cjs.js.LICENSE.txt +1 -1
  166. package/dist/text-field.esm.js +1 -1
  167. package/dist/text-field.esm.js.LICENSE.txt +1 -1
  168. package/dist/text.cjs.js +1 -1
  169. package/dist/text.cjs.js.LICENSE.txt +1 -1
  170. package/dist/text.esm.js +1 -1
  171. package/dist/text.esm.js.LICENSE.txt +1 -1
  172. package/dist/theme.cjs.js.LICENSE.txt +1 -1
  173. package/dist/theme.esm.js.LICENSE.txt +1 -1
  174. package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
  175. package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
  176. package/dist/ulist.cjs.js +1 -1
  177. package/dist/ulist.cjs.js.LICENSE.txt +1 -1
  178. package/dist/ulist.esm.js +1 -1
  179. package/dist/ulist.esm.js.LICENSE.txt +1 -1
  180. package/dist/video.cjs.js +1 -1
  181. package/dist/video.cjs.js.LICENSE.txt +1 -1
  182. package/dist/video.esm.js +1 -1
  183. package/dist/video.esm.js.LICENSE.txt +1 -1
  184. package/dist/wrap.cjs.js +1 -1
  185. package/dist/wrap.cjs.js.LICENSE.txt +1 -1
  186. package/dist/wrap.esm.js +1 -1
  187. package/dist/wrap.esm.js.LICENSE.txt +1 -1
  188. package/dist/zoom-card.cjs.js +1 -1
  189. package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
  190. package/dist/zoom-card.esm.js +1 -1
  191. package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
  192. package/layout/animator.js +15 -2
  193. package/layout/audio.js +1 -1
  194. package/layout/audionew.js +1 -1
  195. package/layout/base.js +1 -1
  196. package/layout/beta-desktop-bar.js +1 -1
  197. package/layout/beta-mobile-bar.js +1 -1
  198. package/layout/button.js +1 -1
  199. package/layout/center.js +1 -1
  200. package/layout/checkbox.js +1 -1
  201. package/layout/circle.js +1 -1
  202. package/layout/code.js +1 -1
  203. package/layout/container.js +1 -1
  204. package/layout/dropdown-2025.js +10 -3
  205. package/layout/flex-card.js +1 -1
  206. package/layout/flex-grid.js +1 -1
  207. package/layout/flex-row.js +1 -1
  208. package/layout/form-components/custom.js +1 -1
  209. package/layout/form-components/data-list.js +1 -1
  210. package/layout/form-components/floating-input.js +14 -1
  211. package/layout/form-components/form-all.js +1 -1
  212. package/layout/form-components/form.js +1 -1
  213. package/layout/form-components/image-picker.js +5 -3
  214. package/layout/form-components/picker.js +1 -1
  215. package/layout/form-components/radio.js +1 -1
  216. package/layout/form-components/radiogroup.js +1 -1
  217. package/layout/form-components/range.js +1 -1
  218. package/layout/free.js +1 -1
  219. package/layout/grid-switcher.js +1 -1
  220. package/layout/grid.js +1 -1
  221. package/layout/horizontal-scroller.js +1 -1
  222. package/layout/image.js +15 -2
  223. package/layout/index.js +1 -1
  224. package/layout/link.js +1 -1
  225. package/layout/list.js +1 -1
  226. package/layout/meta-adder.js +1 -1
  227. package/layout/modal-2025.js +1 -1
  228. package/layout/morph.js +1 -1
  229. package/layout/multiswitcher.js +1 -1
  230. package/layout/nav-bar.js +1 -1
  231. package/layout/nav-factor/custom-div.js +1 -1
  232. package/layout/new-nav-bar.js +1 -1
  233. package/layout/polygon.js +1 -1
  234. package/layout/prerender-site.js +1 -1
  235. package/layout/prerender.js +1 -1
  236. package/layout/progress.js +1 -1
  237. package/layout/row.js +1 -1
  238. package/layout/scroll-video.js +1 -1
  239. package/layout/side-bar.js +11 -3
  240. package/layout/side-nav-bar.js +16 -3
  241. package/layout/simple-bar.js +1 -1
  242. package/layout/slider-2025.js +1 -1
  243. package/layout/spacer.js +1 -1
  244. package/layout/stack.js +1 -1
  245. package/layout/svg.js +1 -1
  246. package/layout/switcher.js +1 -1
  247. package/layout/table.js +1 -1
  248. package/layout/text-field.js +16 -1
  249. package/layout/text.js +28 -1
  250. package/layout/ulist.js +1 -1
  251. package/layout/video.js +1 -1
  252. package/layout/wrap.js +1 -1
  253. package/layout/zoom-card.js +1 -1
  254. package/lib/agent-surface.js +1 -1
  255. package/lib/card-getter.js +1 -1
  256. package/lib/check-page.js +239 -0
  257. package/lib/codegen.js +1 -1
  258. package/lib/data.js +1 -1
  259. package/lib/designer.js +64 -2
  260. package/lib/element-mapper.js +113 -23
  261. package/lib/element-params.generated.js +2 -2
  262. package/lib/keyframe-animation.js +1 -1
  263. package/lib/link-getter.js +1 -1
  264. package/lib/morph-node.js +8 -1
  265. package/lib/normalize-spec.js +134 -0
  266. package/lib/parse-html.js +14 -2
  267. package/lib/raster-inspect.js +1 -1
  268. package/lib/raster-ops.js +1 -1
  269. package/lib/raster-presets.js +1 -1
  270. package/lib/resolve-refs.js +121 -0
  271. package/lib/scroll-video.js +1 -1
  272. package/lib/seo.js +1 -1
  273. package/lib/stacker.js +1 -1
  274. package/lib/suggest.js +1 -1
  275. package/lib/theme.js +1 -1
  276. package/lib/transform-anim.js +1 -1
  277. package/lib/transition.js +1 -1
  278. package/lib/validate-nodes.js +99 -3
  279. package/lib/webmcp-adapter.js +1 -1
  280. package/package.json +6 -2
  281. package/schema.json +8152 -0
  282. package/scripts/generate-schema.mjs +251 -0
  283. package/skills/nodality/SKILL.md +121 -1
@@ -135,6 +135,79 @@ const TOOLS = [
135
135
  required: ["nodes"],
136
136
  },
137
137
  },
138
+ {
139
+ name: "check_page",
140
+ description:
141
+ "What the page actually DOES, once it has real layout. Everything else " +
142
+ "here checks the input: validate_nodes checks vocabulary, get_schema " +
143
+ "says what a type accepts, parse_html says what a page was made from. " +
144
+ "None of them can tell you the page you just produced scrolls " +
145
+ "sideways, clips its own text, or is unreadable \u2014 a spec can be " +
146
+ "perfect and the page still wrong. Give it `html` (or `path` to a file " +
147
+ "`preview` wrote) and it drives a real browser at a phone and a desktop " +
148
+ "width and reports horizontal overflow, elements past the viewport, " +
149
+ "clipped content, images with no description, tap targets under 24px, " +
150
+ "controls with no accessible name, skipped heading levels and text " +
151
+ "below the WCAG contrast minimum. Same report shape as validate_nodes; " +
152
+ "`path` in an error is a CSS selector into the rendered page. It " +
153
+ "reports facts, never taste \u2014 it will not tell you whether a " +
154
+ "design is good.",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ html: { type: "string", description: "Rendered HTML: a document or a fragment." },
159
+ path: { type: "string", description: "Path to an HTML file, e.g. what `preview` returned." },
160
+ },
161
+ },
162
+ },
163
+ {
164
+ name: "get_schema",
165
+ description:
166
+ "One element type's parameters, on demand. This is the other half of " +
167
+ "list_ops: that returns the vocabulary of N, this returns the " +
168
+ "vocabulary of E. Call it for the type you are about to write rather " +
169
+ "than carrying every type's parameters in context \u2014 that is the " +
170
+ "difference between paying for a schema once and paying for it in " +
171
+ "every request. Each entry names the mapper, the components it builds " +
172
+ "and every parameter recovered from the source those components " +
173
+ "actually read, so it cannot describe a parameter the library does " +
174
+ "not have. Omit `type` for the whole schema. Composites carry content " +
175
+ "in a slot \u2014 `items` for cards, nav, sideNav, table and ulist; " +
176
+ "`children` for row, form and wrap \u2014 and declaring it in the " +
177
+ "other one renders the placeholders instead.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ type: {
182
+ type: "string",
183
+ description: "An element type, e.g. \"cards\". Omit for the whole schema.",
184
+ },
185
+ },
186
+ },
187
+ },
188
+ {
189
+ name: "parse_html",
190
+ description:
191
+ "Read a rendered page back into an E array, so a page can be EDITED " +
192
+ "rather than only generated: parse it, change the one thing, render " +
193
+ "again. Two tiers, and the report says which one you got. A page " +
194
+ "rendered with `{annotate: true}` carries its own descriptors and " +
195
+ "comes back exactly \u2014 every type, every option, nothing " +
196
+ "inferred. Without that, only what the tag settles is recovered: " +
197
+ "headings, paragraphs, links, images and lists. Thirteen composite " +
198
+ "types render as a bare <div> with nothing to tell them apart, so " +
199
+ "those are reported unrecovered rather than guessed \u2014 a wrong " +
200
+ "guess would silently become a different page. The recovered spec is " +
201
+ "validated before it is returned, because a descriptor read out of a " +
202
+ "document is untrusted input.",
203
+ inputSchema: {
204
+ type: "object",
205
+ properties: {
206
+ html: { type: "string", description: "The rendered HTML to read back." },
207
+ },
208
+ required: ["html"],
209
+ },
210
+ },
138
211
  {
139
212
  name: "preview",
140
213
  description:
@@ -180,6 +253,64 @@ const IMPL = {
180
253
  validate_nodes: async ({ nodes, elements }) =>
181
254
  ok(validateNodes(nodes, elements)),
182
255
 
256
+ // Served from the shipped schema.json rather than regenerated: the
257
+ // generator is a build-time script and this server answers in-process. The
258
+ // drift test keeps the committed copy honest, so reading it is not a second
259
+ // source of truth, it is the same one at rest.
260
+ check_page: async ({ html, path: file } = {}) => {
261
+ const { checkPage } = await import("../lib/check-page.js");
262
+ let source = html;
263
+ if (!source && file) {
264
+ const { readFile } = await import("node:fs/promises");
265
+ source = await readFile(file, "utf8");
266
+ }
267
+ if (!source) {
268
+ return ok({
269
+ ok: false,
270
+ errors: [{
271
+ code: "MISSING_FIELD", path: "html", got: undefined,
272
+ suggestions: ["html", "path"],
273
+ valid: ["rendered HTML, or a path to a file containing it"],
274
+ }],
275
+ });
276
+ }
277
+ return ok(await checkPage(source));
278
+ },
279
+
280
+ get_schema: async ({ type } = {}) => {
281
+ const [{ readFile }, { fileURLToPath }, { dirname, join }] = await Promise.all([
282
+ import("node:fs/promises"), import("node:url"), import("node:path"),
283
+ ]);
284
+ const here = dirname(fileURLToPath(import.meta.url));
285
+ const schema = JSON.parse(await readFile(join(here, "..", "schema.json"), "utf8"));
286
+ const types = schema.types || {};
287
+ if (!type) return ok(schema);
288
+ if (!types[type]) {
289
+ // Same report shape as everything else, so an agent parses one thing.
290
+ return ok({
291
+ ok: false,
292
+ errors: [{
293
+ code: "UNKNOWN_ELEMENT_TYPE", path: "type", got: type,
294
+ suggestions: Object.keys(types)
295
+ .filter((t) => t.slice(0, 2) === String(type).slice(0, 2)).slice(0, 3),
296
+ valid: Object.keys(types),
297
+ }],
298
+ });
299
+ }
300
+ return ok({ type, ...types[type] });
301
+ },
302
+
303
+ parse_html: async ({ html }) => {
304
+ // Same arrangement as `preview`: a DOM is a build-time concern, so jsdom
305
+ // is not bundled and its absence arrives as MISSING_PEER_DEPENDENCY from
306
+ // the handler below rather than as a stack trace.
307
+ const [{ JSDOM }, { parseReport }] = await Promise.all([
308
+ import("jsdom"), import("../lib/parse-html.js"),
309
+ ]);
310
+ const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
311
+ return ok(parseReport(String(html ?? ""), dom.window.document));
312
+ },
313
+
183
314
  preview: async ({ elements, nodes, output }) => {
184
315
  const N = nodes || [];
185
316
  const report = validateNodes(N, elements);
@@ -5,24 +5,68 @@
5
5
  // that can fetch one type's parameters pays for that type once, instead of
6
6
  // carrying every type's vocabulary in every request.
7
7
  //
8
- // The schema is regenerated from source on each call rather than read from
9
- // the committed schema.json, so what an agent is told can never be staler
10
- // than the library it is talking to. `--check` compares the two and is what
11
- // CI runs.
8
+ // TWO SOURCES, in order, because the first one is not always there.
9
+ //
10
+ // In a checkout the generator runs and the schema is rebuilt from source, so
11
+ // what an agent is told cannot be staler than the library it is talking to.
12
+ // In an INSTALLED package it is read from the shipped schema.json instead.
13
+ //
14
+ // That second path is not belt-and-braces, it is a bug fix. `schema.json` and
15
+ // `scripts/` were both outside package.json's `files`, so neither reached the
16
+ // tarball, and `npx nodality schema` failed with MODULE_NOT_FOUND for every
17
+ // consumer while working perfectly in the dev tree — property 2 shipped as
18
+ // nothing at all. Both are packaged now, and `packaging/smoke.mjs` runs this
19
+ // subcommand against the packed tarball so the surface cannot regress
20
+ // invisibly again.
12
21
  import { spawn } from "node:child_process";
13
22
  import { fileURLToPath } from "node:url";
23
+ import { existsSync, readFileSync } from "node:fs";
14
24
  import path from "node:path";
15
25
 
16
26
  export async function runSchema(args) {
17
27
  const here = path.dirname(fileURLToPath(import.meta.url));
18
28
  const gen = path.join(here, "..", "scripts", "generate-schema.mjs");
29
+ const committed = path.join(here, "..", "schema.json");
19
30
  const type = args.find((a) => !a.startsWith("--"));
20
- const passthrough = args.includes("--check")
21
- ? ["--check"]
22
- : type ? ["--type", type] : ["--stdout"];
23
31
 
24
- await new Promise((resolve) => {
25
- const p = spawn(process.execPath, [gen, ...passthrough], { stdio: "inherit" });
26
- p.on("exit", (code) => { process.exitCode = code ?? 0; resolve(); });
27
- });
32
+ if (existsSync(gen)) {
33
+ const passthrough = args.includes("--check")
34
+ ? ["--check"]
35
+ : type ? ["--type", type] : ["--stdout"];
36
+ await new Promise((resolve) => {
37
+ const p = spawn(process.execPath, [gen, ...passthrough], { stdio: "inherit" });
38
+ p.on("exit", (code) => { process.exitCode = code ?? 0; resolve(); });
39
+ });
40
+ return;
41
+ }
42
+
43
+ if (!existsSync(committed)) {
44
+ console.error("[nodality] no schema available: neither scripts/generate-schema.mjs");
45
+ console.error(" nor schema.json is present in this installation.");
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+
50
+ // `--check` asks whether the committed copy matches what the source would
51
+ // produce. Without the generator that question cannot be answered, and
52
+ // answering "yes" from the file being checked would be circular.
53
+ if (args.includes("--check")) {
54
+ console.error("[nodality] --check needs scripts/generate-schema.mjs, which is not");
55
+ console.error(" present in an installed package. Run it from a checkout.");
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+
60
+ const schema = JSON.parse(readFileSync(committed, "utf8"));
61
+ if (type) {
62
+ const entry = schema.types?.[type];
63
+ if (!entry) {
64
+ console.error(`unknown type "${type}"`);
65
+ process.exitCode = 1;
66
+ return;
67
+ }
68
+ console.log(JSON.stringify({ type, ...entry }, null, 2));
69
+ return;
70
+ }
71
+ process.stdout.write(JSON.stringify(schema, null, 2) + "\n");
28
72
  }