@crombolo/schema 0.0.0-stage → 0.9.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/LICENSE +21 -0
- package/NOTICE +69 -0
- package/package.json +32 -4
- package/src/builtin.ts +215 -0
- package/src/check.ts +502 -0
- package/src/export.ts +419 -0
- package/src/forms.ts +328 -0
- package/src/generate.ts +149 -0
- package/src/index.ts +19 -0
- package/src/load.ts +137 -0
- package/src/model.ts +156 -0
- package/src/parse-forms.ts +229 -0
- package/src/parse.ts +260 -0
- package/src/reader.ts +99 -0
- package/src/scaffold.ts +172 -0
- package/src/sync.ts +615 -0
- package/src/upgrade.ts +296 -0
- package/src/validate-forms.ts +346 -0
- package/src/validate.ts +242 -0
- package/src/value-migrations.ts +122 -0
- package/src/version.ts +53 -0
- package/src/write-forms.ts +139 -0
- package/src/write.ts +176 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kineco
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
crombolo
|
|
2
|
+
Copyright (c) 2026 Kineco
|
|
3
|
+
|
|
4
|
+
This product includes third-party software. crombolo is an independent project.
|
|
5
|
+
It is not affiliated with, endorsed by, or sponsored by Umbraco A/S, which owns
|
|
6
|
+
the Umbraco trademark.
|
|
7
|
+
|
|
8
|
+
There are three places third-party material is redistributed, rather than merely
|
|
9
|
+
depended on:
|
|
10
|
+
|
|
11
|
+
@crombolo/backoffice-dist the built Umbraco backoffice and the browser
|
|
12
|
+
packages bundled into it, produced by
|
|
13
|
+
`bun run vendor:backoffice`
|
|
14
|
+
@crombolo/backoffice-dist three CSS files copied unmodified from the Umbraco
|
|
15
|
+
client source tree
|
|
16
|
+
@crombolo/contracts the Umbraco Management API OpenAPI document,
|
|
17
|
+
copied unmodified
|
|
18
|
+
|
|
19
|
+
No Umbraco trademark is redistributed. The Umbraco wordmark, the "U" roundel
|
|
20
|
+
favicon and the installer illustration are deliberately not carried in this
|
|
21
|
+
repository: MIT grants copyright permission and says nothing about trademarks, so
|
|
22
|
+
crombolo's own marks are served at those paths instead. See `BRANDED_ASSETS` in
|
|
23
|
+
`@crombolo/backoffice-host`.
|
|
24
|
+
|
|
25
|
+
The Umbraco backoffice
|
|
26
|
+
----------------------
|
|
27
|
+
@umbraco-cms/backoffice 18.2.0 — MIT — https://github.com/umbraco/Umbraco-CMS
|
|
28
|
+
@umbraco-ui/uui 2.0.2 — MIT — https://github.com/umbraco/Umbraco.UI
|
|
29
|
+
|
|
30
|
+
Static files from the Umbraco client source tree
|
|
31
|
+
------------------------------------------------
|
|
32
|
+
css/umb-css.css, css/rte-content.css, css/umbraco-blockgridlayout.css
|
|
33
|
+
— copied unmodified from Umbraco-CMS release-18.2.0 — MIT
|
|
34
|
+
— https://github.com/umbraco/Umbraco-CMS
|
|
35
|
+
Shipped in @crombolo/backoffice-dist under `upstream-static/`.
|
|
36
|
+
|
|
37
|
+
`rte-content.css` itself carries Tiptap's default editor styles, copied from
|
|
38
|
+
tiptap v2.11.7 (MIT); the file records this in its own header.
|
|
39
|
+
|
|
40
|
+
The Umbraco Management API contract
|
|
41
|
+
-----------------------------------
|
|
42
|
+
OpenApi.json — copied unmodified from Umbraco-CMS release-18.2.0
|
|
43
|
+
(src/Umbraco.Cms.Api.Management/OpenApi.json) — MIT
|
|
44
|
+
— https://github.com/umbraco/Umbraco-CMS
|
|
45
|
+
Shipped in @crombolo/contracts, which also ships the TypeScript types generated
|
|
46
|
+
from it.
|
|
47
|
+
|
|
48
|
+
Browser runtime dependencies bundled into the backoffice build
|
|
49
|
+
--------------------------------------------------------------
|
|
50
|
+
@heximal/expressions 0.1.5 — BSD-3-Clause
|
|
51
|
+
@microsoft/signalr 10.0.11 — MIT
|
|
52
|
+
@tiptap/* 3.31.3 — MIT
|
|
53
|
+
diff 9.0.0 — BSD-3-Clause
|
|
54
|
+
dompurify 3.4.16 — Apache-2.0, elected from "MPL-2.0 OR Apache-2.0"
|
|
55
|
+
element-internals-polyfill 3.0.2 — MIT
|
|
56
|
+
lit 3.3.3 — BSD-3-Clause
|
|
57
|
+
luxon 3.7.2 — MIT
|
|
58
|
+
marked 18.0.14 — MIT
|
|
59
|
+
monaco-editor 0.57.0 — MIT
|
|
60
|
+
rxjs 7.8.2 — Apache-2.0
|
|
61
|
+
uuid 14.0.2 — MIT
|
|
62
|
+
|
|
63
|
+
dompurify is offered under either licence; Apache-2.0 is elected here, so no
|
|
64
|
+
MPL-2.0 obligation attaches to the copy bundled into the backoffice build. Of the
|
|
65
|
+
Apache-2.0 packages above, none ships a NOTICE file of its own, so Apache-2.0
|
|
66
|
+
section 4(d) adds nothing to this file.
|
|
67
|
+
|
|
68
|
+
Each package's own licence text ships inside its npm distribution and applies
|
|
69
|
+
unchanged to the copies bundled here.
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crombolo/schema",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.9.1",
|
|
4
|
+
"description": "Schema-as-code for crombolo: TOML document, media and member type definitions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Crombolo",
|
|
7
|
+
"homepage": "https://github.com/kineco-au/crombolo-cms#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kineco-au/crombolo-cms.git",
|
|
11
|
+
"directory": "packages/schema"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/kineco-au/crombolo-cms/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"bun": ">=1.3.0"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./src/index.ts"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"NOTICE"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@crombolo/core": "0.9.1",
|
|
32
|
+
"@crombolo/data": "0.9.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/builtin.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data types that ship built in and need no file. A site file with the same
|
|
3
|
+
* alias overrides the built-in. Mirrors the seeded set in @crombolo/data.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
ALL_SEEDED_DATA_TYPES,
|
|
7
|
+
BUILT_IN_MEDIA_TYPES,
|
|
8
|
+
type BuiltInContentType,
|
|
9
|
+
type BuiltInMediaType,
|
|
10
|
+
SYSTEM_CONTENT_TYPES,
|
|
11
|
+
SYSTEM_MEDIA_TYPES,
|
|
12
|
+
} from '@crombolo/data'
|
|
13
|
+
import type { SchemaContentType, SchemaDataType, SchemaProperty } from './model.ts'
|
|
14
|
+
import { fileNameFor, writeMediaType } from './write.ts'
|
|
15
|
+
|
|
16
|
+
/** Every seeded data type, in the file vocabulary; the seed is the one definition. */
|
|
17
|
+
export const BUILTIN_DATA_TYPES: readonly SchemaDataType[] = ALL_SEEDED_DATA_TYPES.map(
|
|
18
|
+
(seeded) => ({
|
|
19
|
+
alias: seeded.alias,
|
|
20
|
+
name: seeded.name,
|
|
21
|
+
editor: seeded.editorAlias,
|
|
22
|
+
editorUi: seeded.editorUiAlias,
|
|
23
|
+
config: { ...(seeded.config ?? {}) },
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
/** The `property_value` column family an editor stores into; Umbraco's ValueStorageType. */
|
|
28
|
+
/** Folder, Image and File: always present, never deleted, alias fixed. */
|
|
29
|
+
export const SYSTEM_MEDIA_TYPE_ALIASES: ReadonlySet<string> = new Set(
|
|
30
|
+
SYSTEM_MEDIA_TYPES.map((t) => t.alias),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A built-in media type in the file vocabulary: what `crombolo init` writes
|
|
35
|
+
* for the site-owned four, and what export compares a system type against.
|
|
36
|
+
* Folder allows only the built-ins in `present`.
|
|
37
|
+
*/
|
|
38
|
+
export function builtInMediaTypeSchema(
|
|
39
|
+
def: BuiltInMediaType,
|
|
40
|
+
present: ReadonlySet<string> = new Set(BUILT_IN_MEDIA_TYPES.map((t) => t.alias)),
|
|
41
|
+
): SchemaContentType {
|
|
42
|
+
const type: SchemaContentType = {
|
|
43
|
+
key: def.key,
|
|
44
|
+
alias: def.alias,
|
|
45
|
+
name: def.name,
|
|
46
|
+
icon: def.icon,
|
|
47
|
+
allowAtRoot: true,
|
|
48
|
+
isElement: false,
|
|
49
|
+
allowInLibrary: false,
|
|
50
|
+
variesByCulture: false,
|
|
51
|
+
variesBySegment: false,
|
|
52
|
+
compositions: [],
|
|
53
|
+
allowChildren: def.allowChildren.filter((a) => present.has(a)),
|
|
54
|
+
components: [],
|
|
55
|
+
cleanup: { prevent: false },
|
|
56
|
+
properties: [],
|
|
57
|
+
tabs: [],
|
|
58
|
+
}
|
|
59
|
+
if (def.collection) type.collection = def.collection
|
|
60
|
+
if (def.group)
|
|
61
|
+
type.groups = [
|
|
62
|
+
{
|
|
63
|
+
name: def.group.name,
|
|
64
|
+
alias: def.group.alias,
|
|
65
|
+
properties: def.properties.map((p) => ({
|
|
66
|
+
key: p.key,
|
|
67
|
+
alias: p.alias,
|
|
68
|
+
name: p.name,
|
|
69
|
+
type: p.dataType,
|
|
70
|
+
mandatory: p.mandatory,
|
|
71
|
+
variesByCulture: false,
|
|
72
|
+
variesBySegment: false,
|
|
73
|
+
labelOnTop: false,
|
|
74
|
+
})),
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
return type
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Content types the framework ships: always present, never deleted, alias fixed. */
|
|
81
|
+
export const SYSTEM_CONTENT_TYPE_ALIASES: ReadonlySet<string> = new Set(
|
|
82
|
+
SYSTEM_CONTENT_TYPES.map((t) => t.alias),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const dataTypeAliasOf = (key: string): string => {
|
|
86
|
+
const found = ALL_SEEDED_DATA_TYPES.find((d) => d.key === key)
|
|
87
|
+
if (!found) throw new Error(`system content types reference unknown data type "${key}"`)
|
|
88
|
+
return found.alias
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A system content type in the file vocabulary: what export compares a site's copy against. */
|
|
92
|
+
export function builtInContentTypeSchema(def: BuiltInContentType): SchemaContentType {
|
|
93
|
+
return {
|
|
94
|
+
key: def.key,
|
|
95
|
+
alias: def.alias,
|
|
96
|
+
name: def.name,
|
|
97
|
+
description: def.description,
|
|
98
|
+
icon: def.icon,
|
|
99
|
+
allowAtRoot: false,
|
|
100
|
+
isElement: false,
|
|
101
|
+
allowInLibrary: false,
|
|
102
|
+
variesByCulture: false,
|
|
103
|
+
variesBySegment: false,
|
|
104
|
+
compositions: [],
|
|
105
|
+
allowChildren: [],
|
|
106
|
+
components: [],
|
|
107
|
+
cleanup: { prevent: false },
|
|
108
|
+
properties: [],
|
|
109
|
+
tabs: [
|
|
110
|
+
{
|
|
111
|
+
name: def.tab.name,
|
|
112
|
+
alias: def.tab.alias,
|
|
113
|
+
groups: [],
|
|
114
|
+
properties: def.properties.map((p) => ({
|
|
115
|
+
key: p.key,
|
|
116
|
+
alias: p.alias,
|
|
117
|
+
name: p.name,
|
|
118
|
+
description: p.description,
|
|
119
|
+
type: dataTypeAliasOf(p.dataTypeKey),
|
|
120
|
+
mandatory: false,
|
|
121
|
+
variesByCulture: false,
|
|
122
|
+
variesBySegment: false,
|
|
123
|
+
labelOnTop: false,
|
|
124
|
+
})),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Every property a type file declares, wherever it sits. */
|
|
131
|
+
export function declaredProperties(t: SchemaContentType): SchemaProperty[] {
|
|
132
|
+
return [
|
|
133
|
+
...t.properties,
|
|
134
|
+
...(t.groups ?? []).flatMap((g) => g.properties),
|
|
135
|
+
...t.tabs.flatMap((tab) => [...tab.properties, ...tab.groups.flatMap((g) => g.properties)]),
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* What is wrong with a site's file for a system content type: it may add
|
|
141
|
+
* fields, relabel and move them, and vary them by culture, but every shipped
|
|
142
|
+
* field keeps its key, alias and data type.
|
|
143
|
+
*/
|
|
144
|
+
export function checkSystemContentTypeFile(t: SchemaContentType): string[] {
|
|
145
|
+
const def = SYSTEM_CONTENT_TYPES.find((d) => d.alias === t.alias)
|
|
146
|
+
if (!def) return []
|
|
147
|
+
const findings: string[] = []
|
|
148
|
+
if (t.key && t.key.toLowerCase() !== def.key)
|
|
149
|
+
findings.push(`"${t.alias}" is provided by Crombolo; its key must be ${def.key}`)
|
|
150
|
+
if (t.isElement)
|
|
151
|
+
findings.push(`"${t.alias}" is provided by Crombolo and cannot be an element type`)
|
|
152
|
+
const declared = declaredProperties(t)
|
|
153
|
+
for (const shipped of def.properties) {
|
|
154
|
+
const found = declared.find((p) => p.alias === shipped.alias)
|
|
155
|
+
const type = dataTypeAliasOf(shipped.dataTypeKey)
|
|
156
|
+
if (!found)
|
|
157
|
+
findings.push(`the "${shipped.alias}" field is provided by Crombolo and cannot be removed`)
|
|
158
|
+
else if (found.type !== type)
|
|
159
|
+
findings.push(
|
|
160
|
+
`the "${shipped.alias}" field is provided by Crombolo; its type must stay "${type}"`,
|
|
161
|
+
)
|
|
162
|
+
else if (found.key && found.key.toLowerCase() !== shipped.key)
|
|
163
|
+
findings.push(
|
|
164
|
+
`the "${shipped.alias}" field is provided by Crombolo; its key must be ${shipped.key}`,
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
return findings
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A system content type's shipped property keys by alias, for a file that omits them. */
|
|
171
|
+
export function systemPropertyKeys(alias: string): ReadonlyMap<string, string> {
|
|
172
|
+
const def = SYSTEM_CONTENT_TYPES.find((d) => d.alias === alias)
|
|
173
|
+
return new Map((def?.properties ?? []).map((p) => [p.alias, p.key]))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function storageTypeFor(
|
|
177
|
+
editorAlias: string,
|
|
178
|
+
): 'Ntext' | 'Nvarchar' | 'Integer' | 'Date' | 'Decimal' {
|
|
179
|
+
switch (editorAlias) {
|
|
180
|
+
case 'Umbraco.TextBox':
|
|
181
|
+
case 'Umbraco.ContentPicker':
|
|
182
|
+
case 'Umbraco.UploadField':
|
|
183
|
+
case 'Umbraco.EmailAddress':
|
|
184
|
+
// One form key, which is a UUID and never long.
|
|
185
|
+
case 'Crombolo.FormPicker':
|
|
186
|
+
return 'Nvarchar'
|
|
187
|
+
case 'Umbraco.Integer':
|
|
188
|
+
case 'Umbraco.TrueFalse':
|
|
189
|
+
return 'Integer'
|
|
190
|
+
case 'Umbraco.Decimal':
|
|
191
|
+
return 'Decimal'
|
|
192
|
+
case 'Umbraco.DateTime':
|
|
193
|
+
return 'Date'
|
|
194
|
+
default:
|
|
195
|
+
return 'Ntext'
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const BUILTIN_DATA_TYPE_ALIASES: ReadonlySet<string> = new Set(
|
|
200
|
+
BUILTIN_DATA_TYPES.map((d) => d.alias),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The media types a new site owns as files: Umbraco's Video, Audio, Article
|
|
205
|
+
* and Vector Graphics, keyed as Umbraco keys them. `crombolo init` writes these
|
|
206
|
+
* to `schema/media-types/`; relative path → canonical TOML.
|
|
207
|
+
*/
|
|
208
|
+
export function siteMediaTypeFiles(): Record<string, string> {
|
|
209
|
+
const files: Record<string, string> = {}
|
|
210
|
+
for (const def of BUILT_IN_MEDIA_TYPES.filter((t) => !t.system))
|
|
211
|
+
files[`schema/media-types/${fileNameFor(def.alias)}`] = writeMediaType(
|
|
212
|
+
builtInMediaTypeSchema(def),
|
|
213
|
+
)
|
|
214
|
+
return files
|
|
215
|
+
}
|