@golemui/schemas 1.5.1 → 1.6.0-rc.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/CHANGELOG.md +4 -0
- package/README.md +117 -0
- package/cli.cjs +26 -13
- package/cli.js +283 -121
- package/json-schema-Bdnmzg_d.js +1514 -0
- package/json-schema-Dp0MTcM8.cjs +1 -0
- package/json-schema.cjs +1 -0
- package/json-schema.d.ts +2 -0
- package/json-schema.js +4 -0
- package/lib/json-schema/conditions.d.ts +32 -0
- package/lib/json-schema/convert.d.ts +25 -0
- package/lib/json-schema/declarative-rules.d.ts +17 -0
- package/lib/json-schema/enum-options.d.ts +14 -0
- package/lib/json-schema/expressions.d.ts +49 -0
- package/lib/json-schema/merge-schemas.d.ts +42 -0
- package/lib/json-schema/normalize.d.ts +73 -0
- package/lib/json-schema/pointer.d.ts +36 -0
- package/lib/json-schema/text.d.ts +11 -0
- package/lib/json-schema/types.d.ts +256 -0
- package/lib/json-schema/unions.d.ts +34 -0
- package/lib/json-schema/widget-json.d.ts +26 -0
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ schemas/
|
|
|
17
17
|
core/common.schema.json shared structural $defs (baseWidget, localizable, chunkRef, ...)
|
|
18
18
|
index.js / index.cjs the JavaScript entry point
|
|
19
19
|
generator.js / generator.cjs the file-writing generator, `@golemui/schemas/generator`
|
|
20
|
+
json-schema.js / json-schema.cjs the JSON Schema to form converter, `@golemui/schemas/json-schema`
|
|
20
21
|
cli.js the `golemui-schemas` command
|
|
21
22
|
index.d.ts, lib/*.d.ts type declarations
|
|
22
23
|
```
|
|
@@ -133,3 +134,119 @@ Editing workflow for the core file: change it here, then run `npm run generate:s
|
|
|
133
134
|
so every vendored copy is regenerated. A vendored copy is identical to its source apart from
|
|
134
135
|
an `$id` rebased onto the implementation's own tree, which is what lets that tree be loaded by
|
|
135
136
|
`$id` alone. A drift test enforces this, and CI fails when generated files are stale.
|
|
137
|
+
|
|
138
|
+
## Converting a JSON Schema into a form
|
|
139
|
+
|
|
140
|
+
`@golemui/schemas/json-schema` exports `fromJsonSchema`, a pure function that turns a JSON
|
|
141
|
+
Schema of the form data into a form definition. It runs in Node, browsers and Workers, so it
|
|
142
|
+
works at runtime with a schema from an API, and at build time to write a form file.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { fromJsonSchema } from '@golemui/schemas/json-schema';
|
|
146
|
+
import { guiPreset } from '@golemui/gui-schemas/json-schema';
|
|
147
|
+
|
|
148
|
+
const { formDefinition, diagnostics } = fromJsonSchema(schema, {
|
|
149
|
+
preset: guiPreset(),
|
|
150
|
+
rules: [{ match: { $type: 'string', format: 'email' }, props: { icon: 'mail' } }],
|
|
151
|
+
overrides: { 'address.street': { widget: 'textarea' } },
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The widget set provides a preset. Its rules choose a widget for each schema node, and its
|
|
156
|
+
builders create the widgets. Four layers change the result. For each node, the first layer
|
|
157
|
+
that names a widget builds it:
|
|
158
|
+
|
|
159
|
+
1. A path override, e.g. `overrides: { 'address.street': { widget: 'textarea' } }`. Inside
|
|
160
|
+
arrays the path uses the `items` token: `lines.items.quantity`.
|
|
161
|
+
2. A definition override, e.g. `overrides: { '$defs/Address': { ... } }`, for every node that
|
|
162
|
+
comes from that definition.
|
|
163
|
+
3. The `x-golemui` keyword in the schema. Rename it with `vendorKeyword`, or ignore it with
|
|
164
|
+
`vendorKeyword: false`.
|
|
165
|
+
4. The rules, in order: first `rules`, then the preset rules. A rule is either a predicate,
|
|
166
|
+
`{ when(node), build(node, context) }`, or plain JSON, `{ match, widget, ...fields }`.
|
|
167
|
+
|
|
168
|
+
The other fields of these layers (`label`, `props`, `validator`, `defaultValue`, `readonly`,
|
|
169
|
+
`size`, `uid`) are merged onto the built widget, the most specific layer last. `skip: true`
|
|
170
|
+
leaves a node out. `order` sets the property order of an object, and `'*'` stands for the
|
|
171
|
+
properties it does not list.
|
|
172
|
+
|
|
173
|
+
A JSON rule matches schema keywords by deep equality or with `$in`, `$exists` and `$regex`. It
|
|
174
|
+
matches node fields with `$type`, `$path` (a glob: `*` is one segment, `**` any number of
|
|
175
|
+
segments), `$name`, `$defName`, `$required` and `$inRepeater`. A JSON rule without `widget`
|
|
176
|
+
only patches what the next rule builds.
|
|
177
|
+
|
|
178
|
+
Refs are resolved inside the document: `$defs`, `definitions`, and OpenAPI
|
|
179
|
+
`components/schemas` through the `refRoot` option. A recursive ref stops after `maxRefDepth`
|
|
180
|
+
expansions, 2 by default. `allOf` parts are merged, nullable types are unwrapped, and draft-07,
|
|
181
|
+
draft-04 and OpenAPI 3.0 keywords are read in their 2020-12 form.
|
|
182
|
+
|
|
183
|
+
A `oneOf` or `anyOf` of objects becomes a selector plus the branch properties. The selector is
|
|
184
|
+
the discriminator: OpenAPI `discriminator.propertyName` when present, otherwise the first
|
|
185
|
+
property with a different `const` in every branch. Its options are labelled by the branch
|
|
186
|
+
titles. Each branch property is built once, with an `include.when` condition on the branches
|
|
187
|
+
that define it, for example `$form.payment?.method === "card"`. Inside an array the condition
|
|
188
|
+
reads the row through `$item`. The form removes the data of hidden widgets on submit, so only
|
|
189
|
+
the chosen branch is sent. A union with no discriminator renders every branch property,
|
|
190
|
+
optional and always visible, with a `no-discriminator` warning.
|
|
191
|
+
|
|
192
|
+
`if/then/else`, `dependentRequired`, `dependentSchemas` and draft-07 `dependencies` on an
|
|
193
|
+
object are compiled too:
|
|
194
|
+
|
|
195
|
+
- A property that only a branch defines is built once, with an `include.when` condition, right
|
|
196
|
+
after the last property the condition reads.
|
|
197
|
+
- A declared property that a branch constrains gets a state validator: the branch condition
|
|
198
|
+
becomes a form state (`if01`, `dep01`, ...) and the constraints a `validator.<state>` entry.
|
|
199
|
+
One state validator applies per field at a time. So a field can become required under any
|
|
200
|
+
number of conditions (one state joins them with `||`), or take its constraints from the
|
|
201
|
+
branches of one `if`. Constraints from a second condition give an `overlay-limit` warning.
|
|
202
|
+
- Inside an array, a condition can show and hide widgets through `$item`, but states cannot
|
|
203
|
+
read the row, so conditional constraints there give a `conditional-validator-in-repeater`
|
|
204
|
+
warning.
|
|
205
|
+
|
|
206
|
+
An `if` compiles when it uses `properties`, `required`, `const`, `enum`, `not` with `const` or
|
|
207
|
+
`enum`, numeric bounds and nested `properties`. As in JSON Schema, a tested property that is
|
|
208
|
+
not in `if.required` passes when it is absent, which the `if-vacuous` note points out. Any
|
|
209
|
+
other `if` gives an `if-unsupported` warning, and its branch properties are always shown and
|
|
210
|
+
optional.
|
|
211
|
+
|
|
212
|
+
### From the command line
|
|
213
|
+
|
|
214
|
+
`convert` writes the form definition of a JSON Schema file, to standard output or to `--out`.
|
|
215
|
+
The preset comes from a module that exports `preset(options)`, such as the gui entry:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npx @golemui/schemas convert schemas/signup.schema.json \
|
|
219
|
+
--preset @golemui/gui-schemas/json-schema --out src/forms/signup.form.json
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
A generated form is often edited by hand, so an existing `--out` file is only replaced with
|
|
223
|
+
`--force`. `--pointer /components/schemas/User` converts one subschema of a larger document,
|
|
224
|
+
such as an OpenAPI file, and resolves its `$ref`s against the whole document. Diagnostics go to
|
|
225
|
+
standard error. `--fail-on error` or `--fail-on warning` makes the command fail on them.
|
|
226
|
+
|
|
227
|
+
The settings can also live in `json-schema.config.mjs` (or `.json`) in the current directory,
|
|
228
|
+
or in the file `--config` names. Its paths are relative to the config file, and flags win over
|
|
229
|
+
it. A `.mjs` config can pass predicate rules and the preset itself:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
import { guiPreset } from '@golemui/gui-schemas/json-schema';
|
|
233
|
+
|
|
234
|
+
export default {
|
|
235
|
+
input: 'schemas/signup.schema.json',
|
|
236
|
+
output: 'src/forms/signup.form.json',
|
|
237
|
+
preset: guiPreset({ submitLabel: 'Sign up' }),
|
|
238
|
+
overrides: { 'address.street': { widget: 'textarea' } },
|
|
239
|
+
rules: [{ match: { format: 'email' }, props: { icon: 'mail' } }],
|
|
240
|
+
};
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
A `.json` config names the preset module instead (`"preset": "@golemui/gui-schemas/json-schema"`)
|
|
244
|
+
and passes its options as `presetOptions`. The other fields are `pointer`, `vendorKeyword`,
|
|
245
|
+
`maxRefDepth`, `maxNodes` and, in a `.mjs` config, `transform`.
|
|
246
|
+
|
|
247
|
+
### Diagnostics
|
|
248
|
+
|
|
249
|
+
The conversion never throws because of the schema shape. What it cannot express exactly is
|
|
250
|
+
listed in `diagnostics`. Each entry has a `severity` (`error`: not rendered, `warning`:
|
|
251
|
+
rendered approximately, `info`: a note), a `code`, the form data `path` and the JSON
|
|
252
|
+
`pointer` into the input.
|
package/cli.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";const p=require("node:fs"),s=require("node:path"),b=require("node:readline/promises"),w=require("node:url"),v=require("./generate-implementation-schemas-D57DzM2J.cjs"),x="../src/lib/form.editor.schema.json",j=[{kind:"layout",type:"flex",uid:"#root",props:{direction:"column",gap:16},children:[{kind:"input",type:"__IMPLEMENTATION__-input",uid:"email",path:"email",label:"Email address",validator:{type:"string",required:!0,format:"email"},props:{placeholder:"you@example.com"}}]}],S={$schema:x,form:j},P="https://json-schema.org/draft/2020-12/schema",T="__ID_BASE__components/example-input.schema.json",E="STARTER TEMPLATE, owned by you. One component schema per widget type. Copy this file for each widget, then list it in schemas.config.mjs. The pattern to keep: allOf the baseWidget, `kind` and `type` consts, one `$defs` entry per prop, a `.state` suffixed pattern for every suffixable key, `additionalProperties: false` inside `props`, and `unevaluatedProperties: false` at the root.",_="__IMPLEMENTATION__ example input widget",I="object",q={labelDef:{$ref:"../core/common.schema.json#/$defs/localizable"},disabledDef:{$ref:"../core/common.schema.json#/$defs/boolOrWhen"},readonlyDef:{$ref:"../core/common.schema.json#/$defs/boolOrWhen"},hintProp:{type:"string"},placeholderProp:{type:"string"},maxlengthProp:{type:"number"}},z=[{$ref:"../core/common.schema.json#/$defs/baseWidget"}],A={kind:{const:"input"},type:{const:"__IMPLEMENTATION__-input"},path:{$ref:"../core/common.schema.json#/$defs/dotPath"},label:{$ref:"#/$defs/labelDef"},disabled:{$ref:"#/$defs/disabledDef"},readonly:{$ref:"#/$defs/readonlyDef"},on:{$ref:"../core/common.schema.json#/$defs/on"},validator:{$ref:"../validators.schema.json#/$defs/validator"},defaultValue:{type:"string"},props:{type:"object",properties:{hint:{$ref:"#/$defs/hintProp"},placeholder:{$ref:"#/$defs/placeholderProp"},maxlength:{$ref:"#/$defs/maxlengthProp"}},patternProperties:{"^hint\\.[^.]+$":{$ref:"#/$defs/hintProp"},"^placeholder\\.[^.]+$":{$ref:"#/$defs/placeholderProp"},"^maxlength\\.[^.]+$":{$ref:"#/$defs/maxlengthProp"}},additionalProperties:!1}},M={"^label\\.[^.]+$":{$ref:"#/$defs/labelDef"},"^disabled\\.[^.]+$":{$ref:"#/$defs/disabledDef"},"^readonly\\.[^.]+$":{$ref:"#/$defs/readonlyDef"},"^validator\\.[^.]+$":{$ref:"../validators.schema.json#/$defs/validator"}},O=["kind","type","path"],k=!1,L={$schema:P,$id:T,$comment:E,title:_,type:I,$defs:q,allOf:z,properties:A,patternProperties:M,required:O,unevaluatedProperties:k},N="https://json-schema.org/draft/2020-12/schema",R="__ID_BASE__components/flex.schema.json",C="STARTER TEMPLATE, owned by you. `flex` is a reserved widget type: keep the name and the `children` array, change the props to match your layout widget.",V="__IMPLEMENTATION__ flex widget",U="object",W={directionProp:{enum:["row","row-reverse","column","column-reverse"]},justifyProp:{enum:["start","center","end","stretch","space-between","space-around"]},alignProp:{enum:["start","center","end","stretch","baseline"]},wrapProp:{type:"boolean"},gapProp:{type:"number"},paddingProp:{type:"number"}},D=[{$ref:"../core/common.schema.json#/$defs/baseWidget"}],F={kind:{const:"layout"},type:{const:"flex"},children:{type:"array",minItems:1,items:{$ref:"../widgets.schema.json#/$defs/formWidget"}},props:{type:"object",properties:{direction:{$ref:"#/$defs/directionProp"},justify:{$ref:"#/$defs/justifyProp"},align:{$ref:"#/$defs/alignProp"},wrap:{$ref:"#/$defs/wrapProp"},gap:{$ref:"#/$defs/gapProp"},padding:{$ref:"#/$defs/paddingProp"}},patternProperties:{"^direction\\.[^.]+$":{$ref:"#/$defs/directionProp"},"^justify\\.[^.]+$":{$ref:"#/$defs/justifyProp"},"^align\\.[^.]+$":{$ref:"#/$defs/alignProp"},"^wrap\\.[^.]+$":{$ref:"#/$defs/wrapProp"},"^gap\\.[^.]+$":{$ref:"#/$defs/gapProp"},"^padding\\.[^.]+$":{$ref:"#/$defs/paddingProp"}},additionalProperties:!1}},B=["kind","type","children"],J=!1,Y={$schema:N,$id:R,$comment:C,title:V,type:U,$defs:W,allOf:D,properties:F,required:B,unevaluatedProperties:J},G="https://json-schema.org/draft/2020-12/schema",H="__ID_BASE__validators.schema.json",K="STARTER TEMPLATE, owned by you. Edit it freely. The only contract is that it exposes #/$defs/validator, which every component schema references. Copied from the golemui gui validators when the tree was scaffolded, it does not track them afterwards.",Z="__IMPLEMENTATION__ validator definitions",Q={localizable:{$ref:"./core/common.schema.json#/$defs/localizable"},stringValidator:{type:"object",properties:{type:{const:"string"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the value is empty or absent"},minLength:{type:"number",description:"Minimum number of characters the string must contain"},maxLength:{type:"number",description:"Maximum number of characters the string may contain"},pattern:{type:"string",description:"A regular expression pattern the value must match"},format:{type:"string",description:"A named format the string must conform to (e.g. `email`, `url`, `date`, `uuid`)",enum:["email","hostname","ipv4","ipv6","url","uuid","date","time","date-time","duration"]},const:{description:"The string must equal exactly this value"},enum:{type:"array",description:"The string must be one of these allowed values"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},required:{$ref:"#/$defs/localizable",description:"Shown when a required field is empty"},minLength:{$ref:"#/$defs/localizable",description:"Shown when the value is shorter than `minLength`"},maxLength:{$ref:"#/$defs/localizable",description:"Shown when the value is longer than `maxLength`"},pattern:{$ref:"#/$defs/localizable",description:"Shown when the value does not match the `pattern` regex"},format:{$ref:"#/$defs/localizable",description:"Shown when the value does not conform to the specified `format`"},enum:{$ref:"#/$defs/localizable",description:"Shown when the value is not in the `enum` list"},const:{$ref:"#/$defs/localizable",description:"Shown when the value does not equal `const`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},numberValidator:{type:"object",properties:{type:{type:"string",enum:["number","integer"]},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the value is absent"},minimum:{type:"number",description:"The value must be greater than or equal to this number (inclusive)"},maximum:{type:"number",description:"The value must be less than or equal to this number (inclusive)"},exclusiveMinimum:{type:"number",description:"The value must be strictly greater than this number (exclusive)"},exclusiveMaximum:{type:"number",description:"The value must be strictly less than this number (exclusive)"},multipleOf:{type:"number",description:"The value must be a multiple of this number"},const:{description:"The value must equal exactly this number"},enum:{type:"array",description:"The value must be one of these allowed numbers"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},minimum:{$ref:"#/$defs/localizable",description:"Shown when the value is below `minimum`"},maximum:{$ref:"#/$defs/localizable",description:"Shown when the value exceeds `maximum`"},exclusiveMinimum:{$ref:"#/$defs/localizable",description:"Shown when the value is not strictly above `exclusiveMinimum`"},exclusiveMaximum:{$ref:"#/$defs/localizable",description:"Shown when the value is not strictly below `exclusiveMaximum`"},multipleOf:{$ref:"#/$defs/localizable",description:"Shown when the value is not a multiple of `multipleOf`"},enum:{$ref:"#/$defs/localizable",description:"Shown when the value is not in the `enum` list"},const:{$ref:"#/$defs/localizable",description:"Shown when the value does not equal `const`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},booleanValidator:{type:"object",properties:{type:{const:"boolean"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the value is absent or false (e.g. an unchecked required checkbox)"},const:{description:"The value must equal exactly this boolean (e.g. `true` to require acceptance)"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},const:{$ref:"#/$defs/localizable",description:"Shown when the value does not equal `const`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},arrayValidator:{type:"object",properties:{type:{const:"array"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the array is empty or absent"},minItems:{type:"number",description:"The array must contain at least this many items"},maxItems:{type:"number",description:"The array must contain no more than this many items"},uniqueItems:{type:"boolean",description:"When true, all items in the array must be distinct values"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},required:{$ref:"#/$defs/localizable",description:"Shown when a required array is empty or absent"},minItems:{$ref:"#/$defs/localizable",description:"Shown when the array has fewer items than `minItems`"},maxItems:{$ref:"#/$defs/localizable",description:"Shown when the array has more items than `maxItems`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},fileValidator:{type:"object",description:"Validates the file envelope stored by the `fileUpload` widget (`{ id, name, size, type, status, error?, data? }` or `null`).",properties:{type:{const:"file"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if no file is set"},blockPendingUploads:{type:"boolean",description:"When true (the default), validation fails while the file's status is `uploading` or `error`, so an unfinished upload cannot be submitted"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value is not a file envelope"},required:{$ref:"#/$defs/localizable",description:"Shown when a required field has no file"},pendingUploads:{$ref:"#/$defs/localizable",description:"Shown while the file is still uploading or failed to upload"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},filesValidator:{type:"object",description:"Validates the array of file envelopes stored by the `multiFileUpload` widget.",properties:{type:{const:"files"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the array is empty or absent"},minItems:{type:"number",description:"The array must contain at least this many files"},maxItems:{type:"number",description:"The array must contain no more than this many files"},blockPendingUploads:{type:"boolean",description:"When true (the default), validation fails while any file's status is `uploading` or `error`, so an unfinished upload cannot be submitted"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value is not an array of file envelopes"},required:{$ref:"#/$defs/localizable",description:"Shown when a required array is empty or absent"},minItems:{$ref:"#/$defs/localizable",description:"Shown when the array has fewer files than `minItems`"},maxItems:{$ref:"#/$defs/localizable",description:"Shown when the array has more files than `maxItems`"},pendingUploads:{$ref:"#/$defs/localizable",description:"Shown while any file is still uploading or failed to upload"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},customValidator:{type:"object",description:"A custom validator that delegates validation to application code. The registered validator function receives these constraint keys and must return a Standard Schema V1-compliant schema object (https://standardschema.dev). Add arbitrary constraint keys alongside `type` - they are passed as-is to the registered validator function.",properties:{type:{const:"custom"},required:{type:"boolean",description:"Makes the field mandatory - evaluation is still delegated to the custom validator"}},required:["type"],additionalProperties:!0},validator:{description:"Validation rules for this field. The `type` discriminant selects the validator: `string`, `number`, `integer`, `boolean`, `array`, `file`, `files`, or `custom`.",oneOf:[{$ref:"#/$defs/stringValidator"},{$ref:"#/$defs/numberValidator"},{$ref:"#/$defs/booleanValidator"},{$ref:"#/$defs/arrayValidator"},{$ref:"#/$defs/fileValidator"},{$ref:"#/$defs/filesValidator"},{$ref:"#/$defs/customValidator"}]}},X={$schema:G,$id:H,$comment:K,title:Z,$defs:Q};function y(e){return`${e}-input`}function ee(e,t){const i=e.replace(/__IMPLEMENTATION__/g,t.implementation).replace(/__ID_BASE__/g,t.idBase),a=i.match(/__[A-Z_]+__/);if(a!==null)throw new Error(`Unsubstituted placeholder ${a[0]} in a starter template.`);return i}function c(e,t){return ee(JSON.stringify(e,null,2),t)+`
|
|
3
|
-
`}function te(e){return{"schemas.config.mjs":ie(e),"src/lib/validators.schema.json":c(X,e),"src/lib/components/flex.schema.json":c(Y,e),"src/lib/components/example-input.schema.json":c(L,e),"examples/example.form.json":c(S,e),"test/schemas.spec.ts":ae(e)}}function ie(e){return`// The manifest and configuration for this implementation's JSON schema tree.
|
|
2
|
+
"use strict";const d=require("node:fs"),s=require("node:path"),N=require("node:readline/promises"),w=require("node:url"),M=require("./generate-implementation-schemas-D57DzM2J.cjs"),z=require("node:module"),T=require("./json-schema-Dp0MTcM8.cjs"),R=["json-schema.config.mjs","json-schema.config.json"];async function k(e,t){const r=process.cwd(),{config:o,baseDir:i}=await L(h(e,"config"),r),a=U(h(e,"fail-on")),l=t[0]?s.resolve(r,t[0]):o.input!==void 0?s.resolve(i,o.input):void 0;if(l===void 0)throw new Error("Missing the JSON Schema file. Pass it as `convert <schema.json>`, or set `input` in the config.");const c=h(e,"out"),n=c?s.resolve(r,c):o.output!==void 0?s.resolve(i,o.output):void 0;if(n!==void 0&&d.existsSync(n)&&e.get("force")!==!0)throw new Error(`${s.relative(r,n)} exists. A generated form is often edited by hand, so it is only replaced with --force.`);const f=_(l),v=h(e,"pointer")??o.pointer,b=h(e,"preset"),O=await D(b??o.preset,o.presetOptions,b!==void 0?r:i),{formDefinition:A,diagnostics:x}=T.fromJsonSchema(v===void 0?f:F(f,v),{preset:O,rules:o.rules,overrides:o.overrides,refRoot:f,vendorKeyword:o.vendorKeyword,maxRefDepth:o.maxRefDepth,maxNodes:o.maxNodes,transform:o.transform}),j=`${JSON.stringify(A,null,2)}
|
|
3
|
+
`;n===void 0?process.stdout.write(j):(d.mkdirSync(s.dirname(n),{recursive:!0}),d.writeFileSync(n,j,"utf-8"),console.log(`Wrote ${s.relative(r,n)}`));for(const $ of x)console.error(V($));return x.some($=>a.includes($.severity))?1:0}async function L(e,t){const r=e!==void 0?s.resolve(t,e):R.map(i=>s.join(t,i)).find(i=>d.existsSync(i));if(r===void 0)return{config:{},baseDir:t};if(!d.existsSync(r))throw new Error(`Config file ${e} not found.`);const o=r.endsWith(".json")?_(r):(await import(w.pathToFileURL(r).href)).default;return{config:C(o,s.relative(t,r)),baseDir:s.dirname(r)}}const S={input:e=>typeof e=="string",output:e=>typeof e=="string",pointer:e=>typeof e=="string",preset:e=>typeof e=="string"||W(e),presetOptions:m,rules:e=>Array.isArray(e)&&e.every(m),overrides:e=>m(e)&&Object.values(e).every(m),vendorKeyword:e=>typeof e=="string"||e===!1,maxRefDepth:E,maxNodes:E,transform:e=>typeof e=="function"};function C(e,t){if(!m(e))throw new Error(`${t} must hold a configuration object (the default export of a .mjs).`);for(const[r,o]of Object.entries(e)){const i=S[r];if(i===void 0)throw new Error(`${t}: unknown field \`${r}\`. The fields are ${Object.keys(S).join(", ")}.`);if(o!==void 0&&!i(o))throw new Error(`${t}: \`${r}\` has the wrong type.`)}return e}async function D(e,t,r){if(e===void 0)throw new Error("No preset. Pass --preset with the preset module of a widget set, e.g. --preset @golemui/gui-schemas/json-schema, or set `preset` in the config.");if(typeof e!="string")return e;let o;try{o=z.createRequire(s.join(r,"noop.js")).resolve(e)}catch{throw new Error(`Cannot find the preset module "${e}" from ${r}. Is it installed?`)}const i=await import(w.pathToFileURL(o).href),a=i.preset??i.default?.preset;if(typeof a!="function")throw new Error(`"${e}" has no \`preset\` export. A preset module exports \`preset(options)\`, which returns the preset.`);return a(t??{})}function F(e,t){const r=T.resolveRef(`#${t.replace(/^#/,"")}`,e);if("error"in r)throw new Error(`--pointer ${t}: ${r.error}`);if(!m(r.target))throw new Error(`--pointer ${t} does not point to a schema object.`);return r.target}function _(e){if(!d.existsSync(e))throw new Error(`${e} not found.`);try{return JSON.parse(d.readFileSync(e,"utf-8"))}catch(t){throw new Error(`${e} is not valid JSON: ${t instanceof Error?t.message:t}`)}}function U(e){switch(e){case void 0:return[];case"error":return["error"];case"warning":return["error","warning"];default:throw new Error(`Invalid --fail-on "${e}". Use error or warning.`)}}function V(e){const t=`${e.path===""?"(root)":e.path} (${e.pointer||"/"})`;return`${e.severity} ${e.code} at ${t}: ${e.message}`}function h(e,t){const r=e.get(t);return typeof r=="string"?r:void 0}function W(e){return m(e)&&Array.isArray(e.rules)&&typeof e.root=="function"}function E(e){return typeof e=="number"&&Number.isInteger(e)&&e>0}function m(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}const B="../src/lib/form.editor.schema.json",J=[{kind:"layout",type:"flex",uid:"#root",props:{direction:"column",gap:16},children:[{kind:"input",type:"__IMPLEMENTATION__-input",uid:"email",path:"email",label:"Email address",validator:{type:"string",required:!0,format:"email"},props:{placeholder:"you@example.com"}}]}],G={$schema:B,form:J},Y="https://json-schema.org/draft/2020-12/schema",K="__ID_BASE__components/example-input.schema.json",H="STARTER TEMPLATE, owned by you. One component schema per widget type. Copy this file for each widget, then list it in schemas.config.mjs. The pattern to keep: allOf the baseWidget, `kind` and `type` consts, one `$defs` entry per prop, a `.state` suffixed pattern for every suffixable key, `additionalProperties: false` inside `props`, and `unevaluatedProperties: false` at the root.",Z="__IMPLEMENTATION__ example input widget",Q="object",X={labelDef:{$ref:"../core/common.schema.json#/$defs/localizable"},disabledDef:{$ref:"../core/common.schema.json#/$defs/boolOrWhen"},readonlyDef:{$ref:"../core/common.schema.json#/$defs/boolOrWhen"},hintProp:{type:"string"},placeholderProp:{type:"string"},maxlengthProp:{type:"number"}},ee=[{$ref:"../core/common.schema.json#/$defs/baseWidget"}],te={kind:{const:"input"},type:{const:"__IMPLEMENTATION__-input"},path:{$ref:"../core/common.schema.json#/$defs/dotPath"},label:{$ref:"#/$defs/labelDef"},disabled:{$ref:"#/$defs/disabledDef"},readonly:{$ref:"#/$defs/readonlyDef"},on:{$ref:"../core/common.schema.json#/$defs/on"},validator:{$ref:"../validators.schema.json#/$defs/validator"},defaultValue:{type:"string"},props:{type:"object",properties:{hint:{$ref:"#/$defs/hintProp"},placeholder:{$ref:"#/$defs/placeholderProp"},maxlength:{$ref:"#/$defs/maxlengthProp"}},patternProperties:{"^hint\\.[^.]+$":{$ref:"#/$defs/hintProp"},"^placeholder\\.[^.]+$":{$ref:"#/$defs/placeholderProp"},"^maxlength\\.[^.]+$":{$ref:"#/$defs/maxlengthProp"}},additionalProperties:!1}},re={"^label\\.[^.]+$":{$ref:"#/$defs/labelDef"},"^disabled\\.[^.]+$":{$ref:"#/$defs/disabledDef"},"^readonly\\.[^.]+$":{$ref:"#/$defs/readonlyDef"},"^validator\\.[^.]+$":{$ref:"../validators.schema.json#/$defs/validator"}},oe=["kind","type","path"],ie=!1,se={$schema:Y,$id:K,$comment:H,title:Z,type:Q,$defs:X,allOf:ee,properties:te,patternProperties:re,required:oe,unevaluatedProperties:ie},ne="https://json-schema.org/draft/2020-12/schema",ae="__ID_BASE__components/flex.schema.json",le="STARTER TEMPLATE, owned by you. `flex` is a reserved widget type: keep the name and the `children` array, change the props to match your layout widget.",de="__IMPLEMENTATION__ flex widget",ce="object",me={directionProp:{enum:["row","row-reverse","column","column-reverse"]},justifyProp:{enum:["start","center","end","stretch","space-between","space-around"]},alignProp:{enum:["start","center","end","stretch","baseline"]},wrapProp:{type:"boolean"},gapProp:{type:"number"},paddingProp:{type:"number"}},pe=[{$ref:"../core/common.schema.json#/$defs/baseWidget"}],fe={kind:{const:"layout"},type:{const:"flex"},children:{type:"array",minItems:1,items:{$ref:"../widgets.schema.json#/$defs/formWidget"}},props:{type:"object",properties:{direction:{$ref:"#/$defs/directionProp"},justify:{$ref:"#/$defs/justifyProp"},align:{$ref:"#/$defs/alignProp"},wrap:{$ref:"#/$defs/wrapProp"},gap:{$ref:"#/$defs/gapProp"},padding:{$ref:"#/$defs/paddingProp"}},patternProperties:{"^direction\\.[^.]+$":{$ref:"#/$defs/directionProp"},"^justify\\.[^.]+$":{$ref:"#/$defs/justifyProp"},"^align\\.[^.]+$":{$ref:"#/$defs/alignProp"},"^wrap\\.[^.]+$":{$ref:"#/$defs/wrapProp"},"^gap\\.[^.]+$":{$ref:"#/$defs/gapProp"},"^padding\\.[^.]+$":{$ref:"#/$defs/paddingProp"}},additionalProperties:!1}},he=["kind","type","children"],ue=!1,ye={$schema:ne,$id:ae,$comment:le,title:de,type:ce,$defs:me,allOf:pe,properties:fe,required:he,unevaluatedProperties:ue},$e="https://json-schema.org/draft/2020-12/schema",ge="__ID_BASE__validators.schema.json",we="STARTER TEMPLATE, owned by you. Edit it freely. The only contract is that it exposes #/$defs/validator, which every component schema references. Copied from the golemui gui validators when the tree was scaffolded, it does not track them afterwards.",ve="__IMPLEMENTATION__ validator definitions",be={localizable:{$ref:"./core/common.schema.json#/$defs/localizable"},stringValidator:{type:"object",properties:{type:{const:"string"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the value is empty or absent"},minLength:{type:"number",description:"Minimum number of characters the string must contain"},maxLength:{type:"number",description:"Maximum number of characters the string may contain"},pattern:{type:"string",description:"A regular expression pattern the value must match"},format:{type:"string",description:"A named format the string must conform to (e.g. `email`, `url`, `date`, `uuid`)",enum:["email","hostname","ipv4","ipv6","url","uuid","date","time","date-time","duration"]},const:{description:"The string must equal exactly this value"},enum:{type:"array",description:"The string must be one of these allowed values"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},required:{$ref:"#/$defs/localizable",description:"Shown when a required field is empty"},minLength:{$ref:"#/$defs/localizable",description:"Shown when the value is shorter than `minLength`"},maxLength:{$ref:"#/$defs/localizable",description:"Shown when the value is longer than `maxLength`"},pattern:{$ref:"#/$defs/localizable",description:"Shown when the value does not match the `pattern` regex"},format:{$ref:"#/$defs/localizable",description:"Shown when the value does not conform to the specified `format`"},enum:{$ref:"#/$defs/localizable",description:"Shown when the value is not in the `enum` list"},const:{$ref:"#/$defs/localizable",description:"Shown when the value does not equal `const`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},numberValidator:{type:"object",properties:{type:{type:"string",enum:["number","integer"]},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the value is absent"},minimum:{type:"number",description:"The value must be greater than or equal to this number (inclusive)"},maximum:{type:"number",description:"The value must be less than or equal to this number (inclusive)"},exclusiveMinimum:{type:"number",description:"The value must be strictly greater than this number (exclusive)"},exclusiveMaximum:{type:"number",description:"The value must be strictly less than this number (exclusive)"},multipleOf:{type:"number",description:"The value must be a multiple of this number"},const:{description:"The value must equal exactly this number"},enum:{type:"array",description:"The value must be one of these allowed numbers"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},minimum:{$ref:"#/$defs/localizable",description:"Shown when the value is below `minimum`"},maximum:{$ref:"#/$defs/localizable",description:"Shown when the value exceeds `maximum`"},exclusiveMinimum:{$ref:"#/$defs/localizable",description:"Shown when the value is not strictly above `exclusiveMinimum`"},exclusiveMaximum:{$ref:"#/$defs/localizable",description:"Shown when the value is not strictly below `exclusiveMaximum`"},multipleOf:{$ref:"#/$defs/localizable",description:"Shown when the value is not a multiple of `multipleOf`"},enum:{$ref:"#/$defs/localizable",description:"Shown when the value is not in the `enum` list"},const:{$ref:"#/$defs/localizable",description:"Shown when the value does not equal `const`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},booleanValidator:{type:"object",properties:{type:{const:"boolean"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the value is absent or false (e.g. an unchecked required checkbox)"},const:{description:"The value must equal exactly this boolean (e.g. `true` to require acceptance)"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},const:{$ref:"#/$defs/localizable",description:"Shown when the value does not equal `const`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},arrayValidator:{type:"object",properties:{type:{const:"array"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the array is empty or absent"},minItems:{type:"number",description:"The array must contain at least this many items"},maxItems:{type:"number",description:"The array must contain no more than this many items"},uniqueItems:{type:"boolean",description:"When true, all items in the array must be distinct values"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value fails general type validation"},required:{$ref:"#/$defs/localizable",description:"Shown when a required array is empty or absent"},minItems:{$ref:"#/$defs/localizable",description:"Shown when the array has fewer items than `minItems`"},maxItems:{$ref:"#/$defs/localizable",description:"Shown when the array has more items than `maxItems`"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},fileValidator:{type:"object",description:"Validates the file envelope stored by the `fileUpload` widget (`{ id, name, size, type, status, error?, data? }` or `null`).",properties:{type:{const:"file"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if no file is set"},blockPendingUploads:{type:"boolean",description:"When true (the default), validation fails while the file's status is `uploading` or `error`, so an unfinished upload cannot be submitted"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value is not a file envelope"},required:{$ref:"#/$defs/localizable",description:"Shown when a required field has no file"},pendingUploads:{$ref:"#/$defs/localizable",description:"Shown while the file is still uploading or failed to upload"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},filesValidator:{type:"object",description:"Validates the array of file envelopes stored by the `multiFileUpload` widget.",properties:{type:{const:"files"},required:{type:"boolean",description:"Makes the field mandatory - validation fails if the array is empty or absent"},minItems:{type:"number",description:"The array must contain at least this many files"},maxItems:{type:"number",description:"The array must contain no more than this many files"},blockPendingUploads:{type:"boolean",description:"When true (the default), validation fails while any file's status is `uploading` or `error`, so an unfinished upload cannot be submitted"},messages:{type:"object",description:"Custom error messages that override the default text for each constraint violation",properties:{invalid:{$ref:"#/$defs/localizable",description:"Shown when the value is not an array of file envelopes"},required:{$ref:"#/$defs/localizable",description:"Shown when a required array is empty or absent"},minItems:{$ref:"#/$defs/localizable",description:"Shown when the array has fewer files than `minItems`"},maxItems:{$ref:"#/$defs/localizable",description:"Shown when the array has more files than `maxItems`"},pendingUploads:{$ref:"#/$defs/localizable",description:"Shown while any file is still uploading or failed to upload"}},additionalProperties:!1}},required:["type"],additionalProperties:!1},customValidator:{type:"object",description:"A custom validator that delegates validation to application code. The registered validator function receives these constraint keys and must return a Standard Schema V1-compliant schema object (https://standardschema.dev). Add arbitrary constraint keys alongside `type` - they are passed as-is to the registered validator function.",properties:{type:{const:"custom"},required:{type:"boolean",description:"Makes the field mandatory - evaluation is still delegated to the custom validator"}},required:["type"],additionalProperties:!0},validator:{description:"Validation rules for this field. The `type` discriminant selects the validator: `string`, `number`, `integer`, `boolean`, `array`, `file`, `files`, or `custom`.",oneOf:[{$ref:"#/$defs/stringValidator"},{$ref:"#/$defs/numberValidator"},{$ref:"#/$defs/booleanValidator"},{$ref:"#/$defs/arrayValidator"},{$ref:"#/$defs/fileValidator"},{$ref:"#/$defs/filesValidator"},{$ref:"#/$defs/customValidator"}]}},xe={$schema:$e,$id:ge,$comment:we,title:ve,$defs:be};function I(e){return`${e}-input`}function je(e,t){const r=e.replace(/__IMPLEMENTATION__/g,t.implementation).replace(/__ID_BASE__/g,t.idBase),o=r.match(/__[A-Z_]+__/);if(o!==null)throw new Error(`Unsubstituted placeholder ${o[0]} in a starter template.`);return r}function u(e,t){return je(JSON.stringify(e,null,2),t)+`
|
|
4
|
+
`}function Se(e){return{"schemas.config.mjs":Ee(e),"src/lib/validators.schema.json":u(xe,e),"src/lib/components/flex.schema.json":u(ye,e),"src/lib/components/example-input.schema.json":u(se,e),"examples/example.form.json":u(G,e),"test/schemas.spec.ts":Pe(e)}}function Ee(e){return`// The manifest and configuration for this implementation's JSON schema tree.
|
|
4
5
|
// You own this file. Every file marked GENERATED is rebuilt from it by
|
|
5
6
|
// \`npx @golemui/schemas generate\`, so add a widget by writing its component schema
|
|
6
7
|
// under src/lib/components/ and listing it in \`manifest\` below.
|
|
@@ -24,7 +25,7 @@ export default {
|
|
|
24
25
|
manifest: [
|
|
25
26
|
{ type: 'flex', schemaFile: 'flex.schema.json', kind: 'layout' },
|
|
26
27
|
{
|
|
27
|
-
type: '${
|
|
28
|
+
type: '${I(e.implementation)}',
|
|
28
29
|
schemaFile: 'example-input.schema.json',
|
|
29
30
|
kind: 'input',
|
|
30
31
|
},
|
|
@@ -35,7 +36,7 @@ export default {
|
|
|
35
36
|
includeCustomWidgetFallback: false,
|
|
36
37
|
emitEditorBundle: true,
|
|
37
38
|
};
|
|
38
|
-
`}function
|
|
39
|
+
`}function Pe(e){return`// Registers the whole schema tree into one Ajv 2020 instance and validates the
|
|
39
40
|
// example form against it. Run it after every \`npx @golemui/schemas generate\`.
|
|
40
41
|
//
|
|
41
42
|
// Needs \`ajv\` and a test runner. This file is written for vitest, the assertions are
|
|
@@ -103,11 +104,13 @@ describe('${e.implementation} schema tree', () => {
|
|
|
103
104
|
expect(validate(form)).toBe(false);
|
|
104
105
|
});
|
|
105
106
|
});
|
|
106
|
-
`}const
|
|
107
|
+
`}const p="schemas.config.mjs",P=`golemui-schemas - JSON schema trees for GolemUI implementations
|
|
107
108
|
|
|
108
109
|
Usage:
|
|
109
110
|
npx @golemui/schemas init [options] scaffold a schema tree
|
|
110
111
|
npx @golemui/schemas generate [options] rebuild the generated files
|
|
112
|
+
npx @golemui/schemas convert <schema.json> [options]
|
|
113
|
+
convert a JSON Schema into a form definition
|
|
111
114
|
|
|
112
115
|
Options for init:
|
|
113
116
|
--name <name> implementation name, lowercase, e.g. kendo
|
|
@@ -116,21 +119,31 @@ Options for init:
|
|
|
116
119
|
--force overwrite existing starter files
|
|
117
120
|
|
|
118
121
|
Options for generate:
|
|
119
|
-
--dir <path> directory holding ${
|
|
122
|
+
--dir <path> directory holding ${p} (default: the current directory)
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
Options for convert:
|
|
125
|
+
--preset <module> preset module of the widget set, e.g. @golemui/gui-schemas/json-schema
|
|
126
|
+
--out <file> file to write the form definition to (default: standard output)
|
|
127
|
+
--force replace an existing --out file
|
|
128
|
+
--config <file> json-schema.config.mjs or .json (default: the one in the current directory)
|
|
129
|
+
--pointer <pointer> convert the subschema at this JSON pointer, e.g. /components/schemas/User
|
|
130
|
+
--fail-on <level> exit with 1 on an error diagnostic, or with warning on either
|
|
131
|
+
Diagnostics are written to standard error. The config holds the same settings plus
|
|
132
|
+
rules, overrides and presetOptions, see the @golemui/schemas README.
|
|
133
|
+
|
|
134
|
+
Every command accepts --help.`;function Te(e){const t=new Map,r=[];let o;for(let i=0;i<e.length;i+=1){const a=e[i];if(!a.startsWith("--")){o===void 0?o=a:r.push(a);continue}const l=a.slice(2),c=l.indexOf("=");if(c!==-1){t.set(l.slice(0,c),l.slice(c+1));continue}const n=e[i+1];n===void 0||n.startsWith("--")?t.set(l,!0):(t.set(l,n),i+=1)}return{command:o,positionals:r,flags:t}}function y(e,t){const r=e.get(t);return typeof r=="string"?r:void 0}async function g(e,t,r){if(process.stdin.isTTY!==!0)throw new Error(`Missing --${t}. There is no terminal to ask on.`);const o=N.createInterface({input:process.stdin,output:process.stdout});try{const i=(await o.question(`${e} [${r}] `)).trim();return i===""?r:i}finally{o.close()}}function _e(e){if(!/^[a-z][a-z0-9-]*$/.test(e))throw new Error(`Invalid implementation name "${e}". Use lowercase letters, digits and hyphens, starting with a letter.`)}function Ie(e){let t;try{t=new URL(e)}catch{throw new Error(`Invalid --id-base "${e}". It must be an absolute URL.`)}if(t.protocol!=="http:"&&t.protocol!=="https:")throw new Error(`Invalid --id-base "${e}". It must be an http or https URL.`);return e.endsWith("/")?e:`${e}/`}function qe(e,t,r){const o=s.join(e,t);d.mkdirSync(s.dirname(o),{recursive:!0}),d.writeFileSync(o,r,"utf-8"),console.log(`Wrote ${t}`)}async function Oe(e){const t=y(e,"name")??await g("Implementation name?","name","my-widgets");_e(t);const r=Ie(y(e,"id-base")??await g("Base URL of the published tree?","id-base",`https://example.com/schemas/${t}/`)),o=y(e,"dir")??await g("Directory?","dir","schemas"),i=s.resolve(process.cwd(),o),a=Se({implementation:t,idBase:r}),l=Object.keys(a).filter(n=>d.existsSync(s.join(i,n)));if(l.length>0&&e.get("force")!==!0)throw new Error(`Refusing to overwrite starter files you may have edited: ${l.join(", ")}. Pass --force to replace them.`);for(const[n,f]of Object.entries(a))qe(i,n,f);await q(i);const c=s.relative(process.cwd(),i)||".";console.log(`
|
|
135
|
+
Scaffolded the ${t} schema tree in ${c}.
|
|
123
136
|
|
|
124
137
|
Next:
|
|
125
138
|
1. Add @golemui/schemas to the project's dependencies.
|
|
126
139
|
2. Write one component schema per widget under src/lib/components/, and list each one
|
|
127
|
-
in ${
|
|
128
|
-
examples/example.form.json uses the starter type ${
|
|
140
|
+
in ${p}. src/lib/components/example-input.schema.json is the pattern to copy.
|
|
141
|
+
examples/example.form.json uses the starter type ${I(t)}, so
|
|
129
142
|
replace it together with the manifest or test/schemas.spec.ts fails.
|
|
130
143
|
3. Rerun \`npx @golemui/schemas generate\` after every edit to a component schema, to
|
|
131
|
-
${
|
|
144
|
+
${p} or to validators.schema.json. A CI step that regenerates and then runs
|
|
132
145
|
\`git diff --exit-code\` catches a forgotten rerun.
|
|
133
146
|
4. Point a form file's "$schema" at src/lib/form.editor.schema.json, as
|
|
134
|
-
examples/example.form.json does. Editors need that bundle, Ajv uses the per-file tree.`)}function
|
|
147
|
+
examples/example.form.json does. Editors need that bundle, Ajv uses the per-file tree.`)}function Ae(e,t){if(e===null||typeof e!="object")throw new Error(`${t} must export a configuration object as its default export.`);const r=e,i=["implementation","idBase","generatorPath","formTitle"].filter(a=>typeof r[a]!="string");if(i.length>0)throw new Error(`${t} is missing required string fields: ${i.join(", ")}.`);if(!Array.isArray(r.manifest)||r.manifest.length===0)throw new Error(`${t} must declare a non-empty \`manifest\` array.`);if(!r.idBase.endsWith("/"))throw new Error(`${t}: \`idBase\` must end with a slash.`);return e}async function q(e){const t=s.join(e,p);if(!d.existsSync(t))throw new Error(`No ${p} in ${e}. Run this from the directory holding it, pass --dir, or scaffold a new tree with \`npx @golemui/schemas init\`.`);const r=await import(w.pathToFileURL(t).href),o=Ae(r.default,p);await M.generateImplementationSchemas(o,e)}async function Ne(e){const{command:t,positionals:r,flags:o}=Te(e);if(o.get("help")===!0||t==="help"||t===void 0)return console.log(P),t===void 0&&o.get("help")!==!0?1:0;try{switch(t){case"init":return await Oe(o),0;case"generate":return await q(s.resolve(process.cwd(),y(o,"dir")??".")),0;case"convert":return await k(o,r);default:return console.error(`Unknown command "${t}".
|
|
135
148
|
|
|
136
|
-
${
|
|
149
|
+
${P}`),1}}catch(i){return console.error(i instanceof Error?i.message:i),1}}Ne(process.argv.slice(2)).then(e=>{process.exitCode=e});
|