@golemui/schemas 1.6.0 → 2.0.0-rc.1
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 +80 -0
- package/README.md +1 -1
- package/cli.cjs +7 -7
- package/cli.js +29 -29
- package/lib/generator/editor-bundle.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,83 @@
|
|
|
1
|
+
## 2.0.0-rc.1 (2026-10-09)
|
|
2
|
+
|
|
3
|
+
This was a version bump only for schemas to align it with other projects, there were no code changes.
|
|
4
|
+
|
|
5
|
+
## 2.0.0-rc.0 (2026-10-08)
|
|
6
|
+
|
|
7
|
+
### 🚀 Features
|
|
8
|
+
|
|
9
|
+
- ⚠️ **gui-components:** Make @golemui/gui-components a standalone project ([#406](https://github.com/golemui/golemui/pull/406))
|
|
10
|
+
|
|
11
|
+
### ⚠️ Breaking Changes
|
|
12
|
+
|
|
13
|
+
- **gui-components:** Make @golemui/gui-components a standalone project ([#406](https://github.com/golemui/golemui/pull/406))
|
|
14
|
+
Deprecate Flex layout
|
|
15
|
+
* fix(gui-components): add migration guide to v2.0
|
|
16
|
+
BREAKING CHANGE: GolemUI 2.0 makes @golemui/gui-components a standalone project.
|
|
17
|
+
- Styles: index.css no longer includes the form container, repeater and
|
|
18
|
+
invalid submit button styles. Import @golemui/gui-shared/forms.css after it.
|
|
19
|
+
- Styles: every rule is in a golemui.* cascade layer, so unlayered app CSS,
|
|
20
|
+
including global resets, now overrides component styles.
|
|
21
|
+
- Styles: component selectors are no longer nested under .gui-form, and
|
|
22
|
+
narrow layouts query the gui-container container instead of gui-form.
|
|
23
|
+
- Layouts: a grid without direction is now a stack instead of a row. Add
|
|
24
|
+
direction: 'row' to keep a row.
|
|
25
|
+
- Layouts: flex is deprecated and renders as a grid. Grid gaps are tokens
|
|
26
|
+
(none to xl) instead of pixels, justify places children along a row, and
|
|
27
|
+
autoFit, columnGap, rowGap and align are deprecated. The root layout is grid.
|
|
28
|
+
- Markup: tabs, accordions and alerts render gui-tabs, gui-accordion and
|
|
29
|
+
gui-alert. Their old classes (.gui-accordion__section,
|
|
30
|
+
.gui-alert-notification--*, .active) are gone, as are the .gui-flex and
|
|
31
|
+
.gui-grid__widget classes. Errors render as divs instead of a ul.
|
|
32
|
+
- Elements: every event is prefixed with gui- (input/change -> gui-input and
|
|
33
|
+
gui-change, blur -> gui-blur, inputError -> gui-input-error, and so on),
|
|
34
|
+
bubbles and is composed.
|
|
35
|
+
- Elements: multi-word attributes are kebab-case (autogrow -> auto-grow).
|
|
36
|
+
- Elements: gui-button's actionType is now type, and invalid is removed.
|
|
37
|
+
- Elements: touched is unset by default, so errors show as soon as they are set.
|
|
38
|
+
- Packages: @golemui/gui-components only peers on lit, and @golemui/gui-shared
|
|
39
|
+
now depends on it. @golemui/gui-components/internals no longer exports
|
|
40
|
+
createIntersectionObserver or NO_AVAILABLE_TIMES_MESSAGE.
|
|
41
|
+
* chore: update gitignore
|
|
42
|
+
* refactor(lit): move ssr to lit-utils
|
|
43
|
+
move ssr to lit-utils
|
|
44
|
+
* docs: update readme
|
|
45
|
+
* refactor(lit): move ssr to lit-utils, add ssr to components
|
|
46
|
+
* fix(gui-components): report missing upload service when dependencies are set
|
|
47
|
+
* fix(gui-components): avoid duplicated ids in all pickers
|
|
48
|
+
* fix(gui-components): set caret icon as a CSS property
|
|
49
|
+
* test: check ssr parity across frameworks with components
|
|
50
|
+
* ci: add workflow to test lit-labs/ssr against our implementation
|
|
51
|
+
* feat(gui-components): add timeLabel prop to date-time inputs
|
|
52
|
+
* chore: fix lint errors
|
|
53
|
+
* chore: improve ssr performance
|
|
54
|
+
* docs: move components ssr docs to website
|
|
55
|
+
* fix(gui-components): send value in numeric inputs
|
|
56
|
+
* feat(gui-components): add nitro plugin for nuxt users
|
|
57
|
+
* fix(gui-components): avoid ssr rendering for time lists
|
|
58
|
+
* chore: reset harnesses
|
|
59
|
+
* docs: update components readme
|
|
60
|
+
* fix(gui-components): misc issues related with required, aria and styling
|
|
61
|
+
* test(gui-components): fix tests
|
|
62
|
+
* test(gui-components): fix firefox test failing by 0.000001
|
|
63
|
+
* fix(gui-components): fix layout issues with pickers
|
|
64
|
+
* fix(schemas): make grid a the reserved root
|
|
65
|
+
* fix(schemas): make grid a the reserved root
|
|
66
|
+
* docs: fix astro template, update migration guide
|
|
67
|
+
* fix(gui-components): avoid double hints in pickers
|
|
68
|
+
* fix(gui-react): fix singleOpen in react accordion
|
|
69
|
+
* fix(gui-components): fix currency stepping
|
|
70
|
+
* fix(gui-components): reset value on form reset
|
|
71
|
+
* fix(gui-components): Segmented date and time inputs keep an outdated validity
|
|
72
|
+
* fix(gui-components): block pickers submissions with partly typed or impossible dates
|
|
73
|
+
* fix(gui-components): Dates and times outside their bounds now block a native submit
|
|
74
|
+
* fix(gui-components): block native submit with pending uploads in progress
|
|
75
|
+
* test: fix typing issues with specs
|
|
76
|
+
|
|
77
|
+
### ❤️ Thank You
|
|
78
|
+
|
|
79
|
+
- Raúl Jiménez @Elecash
|
|
80
|
+
|
|
1
81
|
## 1.6.0 (2026-10-03)
|
|
2
82
|
|
|
3
83
|
### 🚀 Features
|
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ npx @golemui/schemas generate
|
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
`init` writes `schemas.config.mjs` (the manifest and config), a starter validators schema, a
|
|
117
|
-
starter `
|
|
117
|
+
starter `grid` and example input component schema, an example form and a test skeleton, then
|
|
118
118
|
runs `generate`. Run it with no flags for prompts.
|
|
119
119
|
|
|
120
120
|
`schemas.config.mjs` and the component schemas are the implementer's to edit. Everything
|
package/cli.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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"),
|
|
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/
|
|
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"),q=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:q,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 g of x)console.error(V(g));return x.some(g=>a.includes(g.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:"grid",uid:"#root",props:{gap:"md"},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},K="https://json-schema.org/draft/2020-12/schema",Y="__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:K,$id:Y,$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/grid.schema.json",le="STARTER TEMPLATE, owned by you. `grid` is a reserved widget type: core wraps every array-shaped form in a `grid` root. Keep the name and the `children` array, change the props to match your layout widget.",de="__IMPLEMENTATION__ grid widget",ce="object",me={directionProp:{enum:["row","column"]},columnsProp:{oneOf:[{type:"integer",minimum:1,maximum:12},{const:"auto"}]},gapProp:{enum:["none","xs","sm","md","lg","xl"]},justifyProp:{enum:["stretch","start","center","end","space-between"]}},pe=[{$ref:"../core/common.schema.json#/$defs/baseWidget"}],fe={kind:{const:"layout"},type:{const:"grid"},children:{type:"array",minItems:1,items:{$ref:"../widgets.schema.json#/$defs/formWidget"}},props:{type:"object",properties:{direction:{$ref:"#/$defs/directionProp"},columns:{$ref:"#/$defs/columnsProp"},gap:{$ref:"#/$defs/gapProp"},justify:{$ref:"#/$defs/justifyProp"}},patternProperties:{"^direction\\.[^.]+$":{$ref:"#/$defs/directionProp"},"^columns\\.[^.]+$":{$ref:"#/$defs/columnsProp"},"^gap\\.[^.]+$":{$ref:"#/$defs/gapProp"},"^justify\\.[^.]+$":{$ref:"#/$defs/justifyProp"}},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},ge="https://json-schema.org/draft/2020-12/schema",$e="__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:ge,$id:$e,$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/grid.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.
|
|
5
5
|
// You own this file. Every file marked GENERATED is rebuilt from it by
|
|
6
6
|
// \`npx @golemui/schemas generate\`, so add a widget by writing its component schema
|
|
7
7
|
// under src/lib/components/ and listing it in \`manifest\` below.
|
|
@@ -20,10 +20,10 @@ export default {
|
|
|
20
20
|
'Named boolean conditions keyed by state name, each mapping to a reactive expression. ' +
|
|
21
21
|
'Root-level widget props such as \`label\`, \`disabled\`, \`readonly\` and \`validator\` accept ' +
|
|
22
22
|
'a \`.stateName\` suffix, as does any key inside \`props\`.',
|
|
23
|
-
// One entry per widget type. \`
|
|
23
|
+
// One entry per widget type. \`grid\` and \`repeater\` are reserved names: use them for
|
|
24
24
|
// your layout and repeat widgets, and do not give another widget those types.
|
|
25
25
|
manifest: [
|
|
26
|
-
{ type: '
|
|
26
|
+
{ type: 'grid', schemaFile: 'grid.schema.json', kind: 'layout' },
|
|
27
27
|
{
|
|
28
28
|
type: '${I(e.implementation)}',
|
|
29
29
|
schemaFile: 'example-input.schema.json',
|
|
@@ -131,7 +131,7 @@ Options for convert:
|
|
|
131
131
|
Diagnostics are written to standard error. The config holds the same settings plus
|
|
132
132
|
rules, overrides and presetOptions, see the @golemui/schemas README.
|
|
133
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
|
|
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 $(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 Oe(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 qe(e){const t=y(e,"name")??await $("Implementation name?","name","my-widgets");_e(t);const r=Ie(y(e,"id-base")??await $("Base URL of the published tree?","id-base",`https://example.com/schemas/${t}/`)),o=y(e,"dir")??await $("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))Oe(i,n,f);await O(i);const c=s.relative(process.cwd(),i)||".";console.log(`
|
|
135
135
|
Scaffolded the ${t} schema tree in ${c}.
|
|
136
136
|
|
|
137
137
|
Next:
|
|
@@ -144,6 +144,6 @@ Next:
|
|
|
144
144
|
${p} or to validators.schema.json. A CI step that regenerates and then runs
|
|
145
145
|
\`git diff --exit-code\` catches a forgotten rerun.
|
|
146
146
|
4. Point a form file's "$schema" at src/lib/form.editor.schema.json, as
|
|
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
|
|
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 O(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 qe(o),0;case"generate":return await O(s.resolve(process.cwd(),y(o,"dir")??".")),0;case"convert":return await k(o,r);default:return console.error(`Unknown command "${t}".
|
|
148
148
|
|
|
149
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});
|
package/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync as p, mkdirSync as O, writeFileSync as A, readFileSync as
|
|
3
|
-
import { resolve as d, relative as
|
|
2
|
+
import { existsSync as p, mkdirSync as O, writeFileSync as A, readFileSync as k } from "node:fs";
|
|
3
|
+
import { resolve as d, relative as $, dirname as b, join as u } from "node:path";
|
|
4
4
|
import { createInterface as C } from "node:readline/promises";
|
|
5
5
|
import { pathToFileURL as x } from "node:url";
|
|
6
6
|
import { g as L } from "./generate-implementation-schemas-DxCUi0Jr.js";
|
|
@@ -8,7 +8,7 @@ import { createRequire as D } from "node:module";
|
|
|
8
8
|
import { f as F, r as V } from "./json-schema-Bdnmzg_d.js";
|
|
9
9
|
const U = ["json-schema.config.mjs", "json-schema.config.json"];
|
|
10
10
|
async function W(e, t) {
|
|
11
|
-
const r = process.cwd(), { config: o, baseDir: i } = await B(h(e, "config"), r), n =
|
|
11
|
+
const r = process.cwd(), { config: o, baseDir: i } = await B(h(e, "config"), r), n = Y(h(e, "fail-on")), a = t[0] ? d(r, t[0]) : o.input !== void 0 ? d(i, o.input) : void 0;
|
|
12
12
|
if (a === void 0)
|
|
13
13
|
throw new Error(
|
|
14
14
|
"Missing the JSON Schema file. Pass it as `convert <schema.json>`, or set `input` in the config."
|
|
@@ -16,14 +16,14 @@ async function W(e, t) {
|
|
|
16
16
|
const l = h(e, "out"), s = l ? d(r, l) : o.output !== void 0 ? d(i, o.output) : void 0;
|
|
17
17
|
if (s !== void 0 && p(s) && e.get("force") !== !0)
|
|
18
18
|
throw new Error(
|
|
19
|
-
`${
|
|
19
|
+
`${$(r, s)} exists. A generated form is often edited by hand, so it is only replaced with --force.`
|
|
20
20
|
);
|
|
21
21
|
const f = q(a), j = h(e, "pointer") ?? o.pointer, S = h(e, "preset"), z = await G(
|
|
22
22
|
S ?? o.preset,
|
|
23
23
|
o.presetOptions,
|
|
24
24
|
S !== void 0 ? r : i
|
|
25
|
-
), { formDefinition:
|
|
26
|
-
j === void 0 ? f :
|
|
25
|
+
), { formDefinition: R, diagnostics: E } = F(
|
|
26
|
+
j === void 0 ? f : K(f, j),
|
|
27
27
|
{
|
|
28
28
|
preset: z,
|
|
29
29
|
rules: o.rules,
|
|
@@ -35,9 +35,9 @@ async function W(e, t) {
|
|
|
35
35
|
maxNodes: o.maxNodes,
|
|
36
36
|
transform: o.transform
|
|
37
37
|
}
|
|
38
|
-
), P = `${JSON.stringify(
|
|
38
|
+
), P = `${JSON.stringify(R, null, 2)}
|
|
39
39
|
`;
|
|
40
|
-
s === void 0 ? process.stdout.write(P) : (O(b(s), { recursive: !0 }), A(s, P, "utf-8"), console.log(`Wrote ${
|
|
40
|
+
s === void 0 ? process.stdout.write(P) : (O(b(s), { recursive: !0 }), A(s, P, "utf-8"), console.log(`Wrote ${$(r, s)}`));
|
|
41
41
|
for (const w of E)
|
|
42
42
|
console.error(H(w));
|
|
43
43
|
return E.some((w) => n.includes(w.severity)) ? 1 : 0;
|
|
@@ -52,7 +52,7 @@ async function B(e, t) {
|
|
|
52
52
|
/* @vite-ignore */
|
|
53
53
|
x(r).href
|
|
54
54
|
)).default;
|
|
55
|
-
return { config: J(o,
|
|
55
|
+
return { config: J(o, $(t, r)), baseDir: b(r) };
|
|
56
56
|
}
|
|
57
57
|
const T = {
|
|
58
58
|
input: (e) => typeof e == "string",
|
|
@@ -106,7 +106,7 @@ async function G(e, t, r) {
|
|
|
106
106
|
);
|
|
107
107
|
return n(t ?? {});
|
|
108
108
|
}
|
|
109
|
-
function
|
|
109
|
+
function K(e, t) {
|
|
110
110
|
const r = V(`#${t.replace(/^#/, "")}`, e);
|
|
111
111
|
if ("error" in r)
|
|
112
112
|
throw new Error(`--pointer ${t}: ${r.error}`);
|
|
@@ -118,12 +118,12 @@ function q(e) {
|
|
|
118
118
|
if (!p(e))
|
|
119
119
|
throw new Error(`${e} not found.`);
|
|
120
120
|
try {
|
|
121
|
-
return JSON.parse(
|
|
121
|
+
return JSON.parse(k(e, "utf-8"));
|
|
122
122
|
} catch (t) {
|
|
123
123
|
throw new Error(`${e} is not valid JSON: ${t instanceof Error ? t.message : t}`);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
function
|
|
126
|
+
function Y(e) {
|
|
127
127
|
switch (e) {
|
|
128
128
|
case void 0:
|
|
129
129
|
return [];
|
|
@@ -152,7 +152,7 @@ function I(e) {
|
|
|
152
152
|
function c(e) {
|
|
153
153
|
return e !== null && typeof e == "object" && !Array.isArray(e);
|
|
154
154
|
}
|
|
155
|
-
const Q = "../src/lib/form.editor.schema.json", X = [{ kind: "layout", type: "
|
|
155
|
+
const Q = "../src/lib/form.editor.schema.json", X = [{ kind: "layout", type: "grid", uid: "#root", props: { gap: "md" }, 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" } }] }], ee = {
|
|
156
156
|
$schema: Q,
|
|
157
157
|
form: X
|
|
158
158
|
}, te = "https://json-schema.org/draft/2020-12/schema", re = "__ID_BASE__components/example-input.schema.json", oe = "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.", ie = "__IMPLEMENTATION__ example input widget", se = "object", ne = { 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" } }, ae = [{ $ref: "../core/common.schema.json#/$defs/baseWidget" }], le = { 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 } }, de = { "^label\\.[^.]+$": { $ref: "#/$defs/labelDef" }, "^disabled\\.[^.]+$": { $ref: "#/$defs/disabledDef" }, "^readonly\\.[^.]+$": { $ref: "#/$defs/readonlyDef" }, "^validator\\.[^.]+$": { $ref: "../validators.schema.json#/$defs/validator" } }, ce = ["kind", "type", "path"], me = !1, pe = {
|
|
@@ -167,13 +167,13 @@ const Q = "../src/lib/form.editor.schema.json", X = [{ kind: "layout", type: "fl
|
|
|
167
167
|
patternProperties: de,
|
|
168
168
|
required: ce,
|
|
169
169
|
unevaluatedProperties: me
|
|
170
|
-
}, fe = "https://json-schema.org/draft/2020-12/schema", he = "__ID_BASE__components/
|
|
170
|
+
}, fe = "https://json-schema.org/draft/2020-12/schema", he = "__ID_BASE__components/grid.schema.json", ue = "STARTER TEMPLATE, owned by you. `grid` is a reserved widget type: core wraps every array-shaped form in a `grid` root. Keep the name and the `children` array, change the props to match your layout widget.", ye = "__IMPLEMENTATION__ grid widget", ge = "object", $e = { directionProp: { enum: ["row", "column"] }, columnsProp: { oneOf: [{ type: "integer", minimum: 1, maximum: 12 }, { const: "auto" }] }, gapProp: { enum: ["none", "xs", "sm", "md", "lg", "xl"] }, justifyProp: { enum: ["stretch", "start", "center", "end", "space-between"] } }, we = [{ $ref: "../core/common.schema.json#/$defs/baseWidget" }], ve = { kind: { const: "layout" }, type: { const: "grid" }, children: { type: "array", minItems: 1, items: { $ref: "../widgets.schema.json#/$defs/formWidget" } }, props: { type: "object", properties: { direction: { $ref: "#/$defs/directionProp" }, columns: { $ref: "#/$defs/columnsProp" }, gap: { $ref: "#/$defs/gapProp" }, justify: { $ref: "#/$defs/justifyProp" } }, patternProperties: { "^direction\\.[^.]+$": { $ref: "#/$defs/directionProp" }, "^columns\\.[^.]+$": { $ref: "#/$defs/columnsProp" }, "^gap\\.[^.]+$": { $ref: "#/$defs/gapProp" }, "^justify\\.[^.]+$": { $ref: "#/$defs/justifyProp" } }, additionalProperties: !1 } }, be = ["kind", "type", "children"], xe = !1, je = {
|
|
171
171
|
$schema: fe,
|
|
172
172
|
$id: he,
|
|
173
173
|
$comment: ue,
|
|
174
174
|
title: ye,
|
|
175
|
-
type:
|
|
176
|
-
$defs:
|
|
175
|
+
type: ge,
|
|
176
|
+
$defs: $e,
|
|
177
177
|
allOf: we,
|
|
178
178
|
properties: ve,
|
|
179
179
|
required: be,
|
|
@@ -202,7 +202,7 @@ function Ae(e) {
|
|
|
202
202
|
return {
|
|
203
203
|
"schemas.config.mjs": qe(e),
|
|
204
204
|
"src/lib/validators.schema.json": y(_e, e),
|
|
205
|
-
"src/lib/components/
|
|
205
|
+
"src/lib/components/grid.schema.json": y(je, e),
|
|
206
206
|
"src/lib/components/example-input.schema.json": y(
|
|
207
207
|
pe,
|
|
208
208
|
e
|
|
@@ -231,10 +231,10 @@ export default {
|
|
|
231
231
|
'Named boolean conditions keyed by state name, each mapping to a reactive expression. ' +
|
|
232
232
|
'Root-level widget props such as \`label\`, \`disabled\`, \`readonly\` and \`validator\` accept ' +
|
|
233
233
|
'a \`.stateName\` suffix, as does any key inside \`props\`.',
|
|
234
|
-
// One entry per widget type. \`
|
|
234
|
+
// One entry per widget type. \`grid\` and \`repeater\` are reserved names: use them for
|
|
235
235
|
// your layout and repeat widgets, and do not give another widget those types.
|
|
236
236
|
manifest: [
|
|
237
|
-
{ type: '
|
|
237
|
+
{ type: 'grid', schemaFile: 'grid.schema.json', kind: 'layout' },
|
|
238
238
|
{
|
|
239
239
|
type: '${N(e.implementation)}',
|
|
240
240
|
schemaFile: 'example-input.schema.json',
|
|
@@ -367,7 +367,7 @@ function Me(e) {
|
|
|
367
367
|
}
|
|
368
368
|
return { command: o, positionals: r, flags: t };
|
|
369
369
|
}
|
|
370
|
-
function
|
|
370
|
+
function g(e, t) {
|
|
371
371
|
const r = e.get(t);
|
|
372
372
|
return typeof r == "string" ? r : void 0;
|
|
373
373
|
}
|
|
@@ -388,7 +388,7 @@ function ze(e) {
|
|
|
388
388
|
`Invalid implementation name "${e}". Use lowercase letters, digits and hyphens, starting with a letter.`
|
|
389
389
|
);
|
|
390
390
|
}
|
|
391
|
-
function
|
|
391
|
+
function Re(e) {
|
|
392
392
|
let t;
|
|
393
393
|
try {
|
|
394
394
|
t = new URL(e);
|
|
@@ -399,28 +399,28 @@ function ke(e) {
|
|
|
399
399
|
throw new Error(`Invalid --id-base "${e}". It must be an http or https URL.`);
|
|
400
400
|
return e.endsWith("/") ? e : `${e}/`;
|
|
401
401
|
}
|
|
402
|
-
function
|
|
402
|
+
function ke(e, t, r) {
|
|
403
403
|
const o = u(e, t);
|
|
404
404
|
O(b(o), { recursive: !0 }), A(o, r, "utf-8"), console.log(`Wrote ${t}`);
|
|
405
405
|
}
|
|
406
406
|
async function Ce(e) {
|
|
407
|
-
const t =
|
|
407
|
+
const t = g(e, "name") ?? await v("Implementation name?", "name", "my-widgets");
|
|
408
408
|
ze(t);
|
|
409
|
-
const r =
|
|
410
|
-
|
|
409
|
+
const r = Re(
|
|
410
|
+
g(e, "id-base") ?? await v(
|
|
411
411
|
"Base URL of the published tree?",
|
|
412
412
|
"id-base",
|
|
413
413
|
`https://example.com/schemas/${t}/`
|
|
414
414
|
)
|
|
415
|
-
), o =
|
|
415
|
+
), o = g(e, "dir") ?? await v("Directory?", "dir", "schemas"), i = d(process.cwd(), o), n = Ae({ implementation: t, idBase: r }), a = Object.keys(n).filter((s) => p(u(i, s)));
|
|
416
416
|
if (a.length > 0 && e.get("force") !== !0)
|
|
417
417
|
throw new Error(
|
|
418
418
|
`Refusing to overwrite starter files you may have edited: ${a.join(", ")}. Pass --force to replace them.`
|
|
419
419
|
);
|
|
420
420
|
for (const [s, f] of Object.entries(n))
|
|
421
|
-
|
|
421
|
+
ke(i, s, f);
|
|
422
422
|
await M(i);
|
|
423
|
-
const l =
|
|
423
|
+
const l = $(process.cwd(), i) || ".";
|
|
424
424
|
console.log(`
|
|
425
425
|
Scaffolded the ${t} schema tree in ${l}.
|
|
426
426
|
|
|
@@ -469,7 +469,7 @@ async function De(e) {
|
|
|
469
469
|
case "init":
|
|
470
470
|
return await Ce(o), 0;
|
|
471
471
|
case "generate":
|
|
472
|
-
return await M(d(process.cwd(),
|
|
472
|
+
return await M(d(process.cwd(), g(o, "dir") ?? ".")), 0;
|
|
473
473
|
case "convert":
|
|
474
474
|
return await W(o, r);
|
|
475
475
|
default:
|
|
@@ -8,7 +8,7 @@ export declare const EDITOR_BUNDLE_FILE = "form.editor.schema.json";
|
|
|
8
8
|
* Builds the self-contained editor schema by walking the `$ref` graph from the form
|
|
9
9
|
* envelope. Every reachable file is inlined, so the result loads with no network access.
|
|
10
10
|
* @param config - The implementation's schema configuration.
|
|
11
|
-
* @param readSchema - Returns the parsed schema at a lib-relative path, e.g. `components/
|
|
11
|
+
* @param readSchema - Returns the parsed schema at a lib-relative path, e.g. `components/grid.schema.json`.
|
|
12
12
|
* @returns The bundle, ready to be serialized.
|
|
13
13
|
* @example
|
|
14
14
|
* const bundle = buildEditorBundle(config, (path) =>
|