@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/src/write.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model → canonical TOML.
|
|
3
|
+
*
|
|
4
|
+
* Our own writer, for a fixed vocabulary: deterministic key order, one layout,
|
|
5
|
+
* comments stripped by construction (`description` and `notes` are the fields
|
|
6
|
+
* that survive), and none of the quote or blank-line churn of general-purpose
|
|
7
|
+
* TOML serialisers. `parse(write(x))` must equal `x` — the tests hold it to that.
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
type SchemaContentType,
|
|
11
|
+
type SchemaDataType,
|
|
12
|
+
type SchemaLanguage,
|
|
13
|
+
type SchemaProperty,
|
|
14
|
+
type SchemaTypeKind,
|
|
15
|
+
TYPE_KIND_FILES,
|
|
16
|
+
} from './model.ts'
|
|
17
|
+
|
|
18
|
+
function str(value: string): string {
|
|
19
|
+
return JSON.stringify(value)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scalar(value: unknown): string {
|
|
23
|
+
if (typeof value === 'string') return str(value)
|
|
24
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
|
25
|
+
if (Array.isArray(value)) return `[${value.map(scalar).join(', ')}]`
|
|
26
|
+
if (value === null || value === undefined) return '""'
|
|
27
|
+
return str(JSON.stringify(value))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A key as TOML will read it back: bare when the grammar allows, quoted and
|
|
32
|
+
* escaped when it does not.
|
|
33
|
+
*
|
|
34
|
+
* Every key in this file is a literal of ours but one — a data type's `config`
|
|
35
|
+
* keys are property-editor value aliases, which arrive from the API and from
|
|
36
|
+
* proposed TOML. An alias holding a newline, written bare, turns one line into
|
|
37
|
+
* several and the file stops parsing: the site then fails to boot on the next
|
|
38
|
+
* read, and with a schema store configured the broken file is published to the
|
|
39
|
+
* other nodes too.
|
|
40
|
+
*/
|
|
41
|
+
function keyOf(key: string): string {
|
|
42
|
+
return /^[A-Za-z0-9_-]+$/.test(key) ? key : str(key)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function line(out: string[], key: string, value: unknown, when = true): void {
|
|
46
|
+
if (!when || value === undefined) return
|
|
47
|
+
out.push(`${keyOf(key)} = ${scalar(value)}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writeProperty(out: string[], header: string, p: SchemaProperty): void {
|
|
51
|
+
out.push('', `[[${header}]]`)
|
|
52
|
+
line(out, 'key', p.key)
|
|
53
|
+
line(out, 'alias', p.alias)
|
|
54
|
+
line(out, 'name', p.name)
|
|
55
|
+
line(out, 'description', p.description)
|
|
56
|
+
line(out, 'notes', p.notes)
|
|
57
|
+
line(out, 'type', p.type)
|
|
58
|
+
line(out, 'mandatory', true, p.mandatory)
|
|
59
|
+
line(out, 'mandatory-message', p.mandatoryMessage)
|
|
60
|
+
line(out, 'regex', p.regex)
|
|
61
|
+
line(out, 'regex-message', p.regexMessage)
|
|
62
|
+
line(out, 'varies-by-culture', true, p.variesByCulture)
|
|
63
|
+
line(out, 'varies-by-segment', true, p.variesBySegment)
|
|
64
|
+
line(out, 'label-on-top', true, p.labelOnTop)
|
|
65
|
+
line(out, 'member-can-view', true, p.memberCanView === true)
|
|
66
|
+
line(out, 'member-can-edit', true, p.memberCanEdit === true)
|
|
67
|
+
line(out, 'sensitive', true, p.sensitive === true)
|
|
68
|
+
line(out, 'default', p.default)
|
|
69
|
+
line(out, 'since', p.since)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function writeContentType(t: SchemaContentType, kind: SchemaTypeKind = 'document'): string {
|
|
73
|
+
const out: string[] = [`[${TYPE_KIND_FILES[kind].header}]`]
|
|
74
|
+
line(out, 'key', t.key)
|
|
75
|
+
line(out, 'alias', t.alias)
|
|
76
|
+
line(out, 'name', t.name)
|
|
77
|
+
line(out, 'description', t.description)
|
|
78
|
+
line(out, 'notes', t.notes)
|
|
79
|
+
line(out, 'icon', t.icon, t.icon !== 'icon-document')
|
|
80
|
+
line(out, 'allow-at-root', true, t.allowAtRoot)
|
|
81
|
+
line(out, 'is-element', true, t.isElement)
|
|
82
|
+
line(out, 'allow-in-library', true, t.allowInLibrary)
|
|
83
|
+
line(out, 'varies-by-culture', true, t.variesByCulture)
|
|
84
|
+
line(out, 'varies-by-segment', true, t.variesBySegment)
|
|
85
|
+
line(out, 'compositions', t.compositions, t.compositions.length > 0)
|
|
86
|
+
line(out, 'allow-children', t.allowChildren, t.allowChildren.length > 0)
|
|
87
|
+
line(out, 'components', t.components, t.components.length > 0)
|
|
88
|
+
line(out, 'default-component', t.defaultComponent)
|
|
89
|
+
line(out, 'collection', t.collection)
|
|
90
|
+
line(out, 'folder', t.folder)
|
|
91
|
+
line(out, 'since', t.since)
|
|
92
|
+
if (
|
|
93
|
+
t.cleanup.prevent ||
|
|
94
|
+
t.cleanup.keepAllNewerThanDays !== undefined ||
|
|
95
|
+
t.cleanup.keepLatestPerDayForDays !== undefined
|
|
96
|
+
) {
|
|
97
|
+
out.push('', `[${TYPE_KIND_FILES.document.header}.cleanup]`)
|
|
98
|
+
line(out, 'prevent', true, t.cleanup.prevent)
|
|
99
|
+
line(out, 'keep-all-newer-than-days', t.cleanup.keepAllNewerThanDays)
|
|
100
|
+
line(out, 'keep-latest-per-day-for-days', t.cleanup.keepLatestPerDayForDays)
|
|
101
|
+
}
|
|
102
|
+
for (const p of t.properties) writeProperty(out, 'property', p)
|
|
103
|
+
for (const g of t.groups ?? []) {
|
|
104
|
+
out.push('', '[[group]]')
|
|
105
|
+
line(out, 'name', g.name)
|
|
106
|
+
line(out, 'alias', g.alias)
|
|
107
|
+
for (const p of g.properties) writeProperty(out, 'group.property', p)
|
|
108
|
+
}
|
|
109
|
+
for (const tab of t.tabs) {
|
|
110
|
+
out.push('', '[[tab]]')
|
|
111
|
+
line(out, 'name', tab.name)
|
|
112
|
+
line(out, 'alias', tab.alias)
|
|
113
|
+
for (const p of tab.properties) writeProperty(out, 'tab.property', p)
|
|
114
|
+
for (const g of tab.groups) {
|
|
115
|
+
out.push('', '[[tab.group]]')
|
|
116
|
+
line(out, 'name', g.name)
|
|
117
|
+
line(out, 'alias', g.alias)
|
|
118
|
+
for (const p of g.properties) writeProperty(out, 'tab.group.property', p)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return `${out.join('\n')}\n`
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function writeMediaType(t: SchemaContentType): string {
|
|
125
|
+
return writeContentType(t, 'media')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function writeMemberType(t: SchemaContentType): string {
|
|
129
|
+
return writeContentType(t, 'member')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function writeDataType(d: SchemaDataType): string {
|
|
133
|
+
const out: string[] = ['[data-type]']
|
|
134
|
+
line(out, 'key', d.key)
|
|
135
|
+
line(out, 'alias', d.alias)
|
|
136
|
+
line(out, 'name', d.name)
|
|
137
|
+
line(out, 'notes', d.notes)
|
|
138
|
+
line(out, 'editor', d.editor)
|
|
139
|
+
line(out, 'editor-ui', d.editorUi)
|
|
140
|
+
line(out, 'folder', d.folder)
|
|
141
|
+
line(out, 'since', d.since)
|
|
142
|
+
const keys = Object.keys(d.config).sort()
|
|
143
|
+
if (keys.length > 0) {
|
|
144
|
+
out.push('', '[data-type.config]')
|
|
145
|
+
for (const key of keys) line(out, key, d.config[key])
|
|
146
|
+
}
|
|
147
|
+
return `${out.join('\n')}\n`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function writeLanguages(languages: readonly SchemaLanguage[]): string {
|
|
151
|
+
const out: string[] = []
|
|
152
|
+
for (const l of languages) {
|
|
153
|
+
if (out.length > 0) out.push('')
|
|
154
|
+
out.push('[[language]]')
|
|
155
|
+
line(out, 'iso', l.iso)
|
|
156
|
+
line(out, 'name', l.name)
|
|
157
|
+
line(out, 'default', true, l.default)
|
|
158
|
+
line(out, 'mandatory', true, l.mandatory)
|
|
159
|
+
line(out, 'fallback', l.fallback)
|
|
160
|
+
}
|
|
161
|
+
return `${out.join('\n')}\n`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function writeSchemaVersion(version: string): string {
|
|
165
|
+
return `[schema]\nversion = ${str(version)}\n`
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The canonical filename for an alias: kebab-case. */
|
|
169
|
+
export function fileNameFor(alias: string): string {
|
|
170
|
+
const kebab = alias
|
|
171
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
172
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
173
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
174
|
+
.toLowerCase()
|
|
175
|
+
return `${kebab}.toml`
|
|
176
|
+
}
|
package/README.md
DELETED