@crombolo/schema 0.13.2 → 0.14.4
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/package.json +3 -3
- package/src/export.ts +2 -0
- package/src/model.ts +5 -1
- package/src/parse.ts +26 -2
- package/src/sync.ts +32 -5
- package/src/write.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crombolo/schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "Schema-as-code for crombolo: TOML document, media and member type definitions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Crombolo",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@crombolo/core": "0.
|
|
32
|
-
"@crombolo/data": "0.
|
|
31
|
+
"@crombolo/core": "0.14.4",
|
|
32
|
+
"@crombolo/data": "0.14.4"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/export.ts
CHANGED
|
@@ -69,6 +69,7 @@ function toSchemaProperty(p: PropertyTypeModel, ctx: ExportContext): SchemaPrope
|
|
|
69
69
|
if (p.memberCanEdit) property.memberCanEdit = true
|
|
70
70
|
if (p.isSensitive) property.sensitive = true
|
|
71
71
|
if (p.searchable === false) property.searchable = false
|
|
72
|
+
if (p.siteSearch !== undefined && p.siteSearch !== 'normal') property.siteSearch = p.siteSearch
|
|
72
73
|
return property
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -109,6 +110,7 @@ export function toSchemaContentType(
|
|
|
109
110
|
tabs: [],
|
|
110
111
|
}
|
|
111
112
|
if (aggregate.description) type.description = aggregate.description
|
|
113
|
+
if (aggregate.siteSearch === false) type.siteSearch = false
|
|
112
114
|
if (aggregate.defaultComponentKey) {
|
|
113
115
|
const alias = ctx.componentAlias(aggregate.defaultComponentKey)
|
|
114
116
|
if (alias) type.defaultComponent = alias
|
package/src/model.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SchemaForm, SchemaWorkflow } from '@crombolo/core'
|
|
1
|
+
import type { SchemaForm, SchemaWorkflow, SiteSearchLevel } from '@crombolo/core'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The schema-as-code model: what a site's `schema/*.toml` files describe.
|
|
@@ -28,6 +28,8 @@ export interface SchemaProperty {
|
|
|
28
28
|
sensitive?: boolean
|
|
29
29
|
/** False keeps the property's values out of backoffice search; searchable when absent. */
|
|
30
30
|
searchable?: boolean
|
|
31
|
+
/** Its weight in site search, or false to leave it out; `normal` when absent. */
|
|
32
|
+
siteSearch?: SiteSearchLevel | false
|
|
31
33
|
/** Fills existing content when a required property is added to a type with pages. */
|
|
32
34
|
default?: unknown
|
|
33
35
|
since?: string
|
|
@@ -73,6 +75,8 @@ export interface SchemaContentType {
|
|
|
73
75
|
folder?: string
|
|
74
76
|
cleanup: SchemaCleanup
|
|
75
77
|
since?: string
|
|
78
|
+
/** False leaves every page of the type out of site search. */
|
|
79
|
+
siteSearch?: false
|
|
76
80
|
/** Properties outside any tab. */
|
|
77
81
|
properties: SchemaProperty[]
|
|
78
82
|
/** Groups outside any tab — `[[group]]`; Umbraco's built-in media types use one. */
|
package/src/parse.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* the most common mistake a non-developer makes and silently ignoring it would
|
|
4
4
|
* hide the thing they were trying to do.
|
|
5
5
|
*/
|
|
6
|
+
import { SITE_SEARCH_LEVELS, type SiteSearchLevel } from '@crombolo/core'
|
|
6
7
|
import {
|
|
7
8
|
type SchemaContentType,
|
|
8
9
|
type SchemaDataType,
|
|
@@ -23,6 +24,7 @@ const PROPERTY_KEYS = [
|
|
|
23
24
|
'member-can-edit',
|
|
24
25
|
'sensitive',
|
|
25
26
|
'searchable',
|
|
27
|
+
'site-search',
|
|
26
28
|
'key',
|
|
27
29
|
'alias',
|
|
28
30
|
'name',
|
|
@@ -61,6 +63,7 @@ const TYPE_KEYS = [
|
|
|
61
63
|
'folder',
|
|
62
64
|
'cleanup',
|
|
63
65
|
'since',
|
|
66
|
+
'site-search',
|
|
64
67
|
] as const
|
|
65
68
|
const CLEANUP_KEYS = [
|
|
66
69
|
'prevent',
|
|
@@ -104,11 +107,22 @@ function property(r: Reader, t: Toml, path: string): SchemaProperty {
|
|
|
104
107
|
: {}),
|
|
105
108
|
...(t.sensitive !== undefined ? { sensitive: r.bool(t, 'sensitive', path, false) } : {}),
|
|
106
109
|
...(t.searchable !== undefined ? { searchable: r.bool(t, 'searchable', path, true) } : {}),
|
|
110
|
+
...(t['site-search'] !== undefined ? { siteSearch: siteSearchLevel(r, t, path) } : {}),
|
|
107
111
|
default: t.default,
|
|
108
112
|
since: r.str(t, 'since', path),
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
/** `site-search` on a property: a level, or false to leave it out. */
|
|
117
|
+
function siteSearchLevel(r: Reader, t: Toml, path: string): SiteSearchLevel | false {
|
|
118
|
+
const value = t['site-search']
|
|
119
|
+
if (value === false) return false
|
|
120
|
+
if (typeof value === 'string' && (SITE_SEARCH_LEVELS as readonly string[]).includes(value))
|
|
121
|
+
return value as SiteSearchLevel
|
|
122
|
+
r.problem(`${path}.site-search`, `expected false or one of ${SITE_SEARCH_LEVELS.join(', ')}`)
|
|
123
|
+
return 'normal'
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
function group(r: Reader, t: Toml, path: string): SchemaGroup {
|
|
113
127
|
r.unknown(t, GROUP_KEYS, path)
|
|
114
128
|
return {
|
|
@@ -132,9 +146,9 @@ function tab(r: Reader, t: Toml, path: string): SchemaTab {
|
|
|
132
146
|
}
|
|
133
147
|
}
|
|
134
148
|
|
|
135
|
-
/** Media types have no templates
|
|
149
|
+
/** Media types have no templates, no version cleanup policy, and are not pages to search. */
|
|
136
150
|
const MEDIA_TYPE_KEYS = TYPE_KEYS.filter(
|
|
137
|
-
(k) => k !== 'components' && k !== 'default-component' && k !== 'cleanup',
|
|
151
|
+
(k) => k !== 'components' && k !== 'default-component' && k !== 'cleanup' && k !== 'site-search',
|
|
138
152
|
)
|
|
139
153
|
/** Member types, besides, allow no children: members are not a tree. */
|
|
140
154
|
const MEMBER_TYPE_KEYS = MEDIA_TYPE_KEYS.filter((k) => k !== 'allow-children')
|
|
@@ -190,6 +204,9 @@ export function parseContentType(
|
|
|
190
204
|
: undefined,
|
|
191
205
|
},
|
|
192
206
|
since: r.str(t, 'since', path),
|
|
207
|
+
...(t['site-search'] !== undefined && typeSiteSearch(r, t, path) === false
|
|
208
|
+
? { siteSearch: false as const }
|
|
209
|
+
: {}),
|
|
193
210
|
properties: r.tables(root.property, 'property').map((p, i) => property(r, p, `property[${i}]`)),
|
|
194
211
|
tabs: r.tables(root.tab, 'tab').map((x, i) => tab(r, x, `tab[${i}]`)),
|
|
195
212
|
groups: r.tables(root.group, 'group').map((g, i) => group(r, g, `group[${i}]`)),
|
|
@@ -197,6 +214,13 @@ export function parseContentType(
|
|
|
197
214
|
return { value: r.problems.length === 0 ? value : undefined, problems: r.problems }
|
|
198
215
|
}
|
|
199
216
|
|
|
217
|
+
/** `site-search` on a content type: only false, which leaves its pages out. */
|
|
218
|
+
function typeSiteSearch(r: Reader, t: Toml, path: string): false | undefined {
|
|
219
|
+
if (t['site-search'] === false) return false
|
|
220
|
+
r.problem(`${path}.site-search`, 'expected false; a page type is searchable unless it says so')
|
|
221
|
+
return undefined
|
|
222
|
+
}
|
|
223
|
+
|
|
200
224
|
export function parseMediaType(file: string, source: string): ParseResult<SchemaContentType> {
|
|
201
225
|
return parseContentType(file, source, 'media')
|
|
202
226
|
}
|
package/src/sync.ts
CHANGED
|
@@ -240,8 +240,15 @@ export async function syncSchema(
|
|
|
240
240
|
for (const d of set.dataTypes) {
|
|
241
241
|
const existing = await dataTypes.byAlias(d.alias)
|
|
242
242
|
const builtin = BUILTIN_DATA_TYPES.find((b) => b.alias === d.alias)
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
// A key this site has never had, for an alias it has, is that data
|
|
244
|
+
// type: the row keeps its key and the file is given it (story 105).
|
|
245
|
+
const rekeyed =
|
|
246
|
+
d.key !== undefined &&
|
|
247
|
+
existing !== undefined &&
|
|
248
|
+
existing.key.toLowerCase() !== d.key.toLowerCase() &&
|
|
249
|
+
!(await dataTypes.byKey(d.key))
|
|
250
|
+
const key = (rekeyed ? existing?.key : d.key) ?? existing?.key ?? crypto.randomUUID()
|
|
251
|
+
if ((!d.key && !existing) || rekeyed)
|
|
245
252
|
report.assignedKeys.push({
|
|
246
253
|
file: loaded.files.get(`data-type:${d.alias}`) ?? '',
|
|
247
254
|
typeAlias: d.alias,
|
|
@@ -370,17 +377,35 @@ export async function syncSchema(
|
|
|
370
377
|
type: SchemaContentType
|
|
371
378
|
aggregate: ContentTypeAggregate
|
|
372
379
|
}> = []
|
|
380
|
+
const fileTypeKeys = new Set(
|
|
381
|
+
kind.types.flatMap((t) => (t.key ? [t.key.toLowerCase()] : [])),
|
|
382
|
+
)
|
|
373
383
|
for (const t of inCompositionOrder(kind.types)) {
|
|
374
384
|
const file = loaded.files.get(typeFileKey(kind.name, t.alias)) ?? ''
|
|
375
|
-
|
|
385
|
+
let existing = t.key
|
|
376
386
|
? await kind.repo.byKey(t.key)
|
|
377
387
|
: await kind.repo.byAlias(t.alias, { includeRetired: true })
|
|
388
|
+
// A key this site has never had, for an alias it has and no other
|
|
389
|
+
// file claims, is that type — as a property's is: the row keeps its
|
|
390
|
+
// key, and the file is given it (story 105).
|
|
391
|
+
let rekeyed = false
|
|
392
|
+
if (!existing && t.key) {
|
|
393
|
+
const sameAlias = await kind.repo.byAlias(t.alias, { includeRetired: true })
|
|
394
|
+
if (sameAlias && !fileTypeKeys.has(sameAlias.key.toLowerCase())) {
|
|
395
|
+
existing = sameAlias
|
|
396
|
+
rekeyed = true
|
|
397
|
+
}
|
|
398
|
+
}
|
|
378
399
|
const shippedKey =
|
|
379
400
|
kind.name === 'document'
|
|
380
401
|
? SYSTEM_CONTENT_TYPES.find((d) => d.alias === t.alias)?.key
|
|
381
402
|
: undefined
|
|
382
|
-
const key =
|
|
383
|
-
|
|
403
|
+
const key =
|
|
404
|
+
(rekeyed ? existing?.key : t.key) ??
|
|
405
|
+
existing?.key ??
|
|
406
|
+
shippedKey ??
|
|
407
|
+
crypto.randomUUID()
|
|
408
|
+
if ((!t.key && !existing) || rekeyed)
|
|
384
409
|
report.assignedKeys.push({ file, typeAlias: t.alias, key, revived: false })
|
|
385
410
|
typeKeyByAlias.set(t.alias, key)
|
|
386
411
|
const retired = existing ? await kind.repo.retiredProperties(existing.key) : []
|
|
@@ -454,6 +479,7 @@ export async function syncSchema(
|
|
|
454
479
|
memberCanEdit: p.memberCanEdit === true,
|
|
455
480
|
isSensitive: p.sensitive === true,
|
|
456
481
|
searchable: p.searchable !== false,
|
|
482
|
+
siteSearch: p.siteSearch ?? 'normal',
|
|
457
483
|
}
|
|
458
484
|
}
|
|
459
485
|
// Groups have no key in the file; they match an existing group of the same name.
|
|
@@ -524,6 +550,7 @@ export async function syncSchema(
|
|
|
524
550
|
allowedInLibrary: t.allowInLibrary,
|
|
525
551
|
collectionKey: t.collection ? (dataTypeKeyByAlias.get(t.collection) ?? null) : null,
|
|
526
552
|
sinceVersion: t.since ?? null,
|
|
553
|
+
siteSearch: t.siteSearch !== false,
|
|
527
554
|
cleanup: {
|
|
528
555
|
preventCleanup: t.cleanup.prevent,
|
|
529
556
|
keepAllVersionsNewerThanDays: t.cleanup.keepAllNewerThanDays ?? null,
|
package/src/write.ts
CHANGED
|
@@ -67,6 +67,7 @@ function writeProperty(out: string[], header: string, p: SchemaProperty): void {
|
|
|
67
67
|
line(out, 'member-can-edit', true, p.memberCanEdit === true)
|
|
68
68
|
line(out, 'sensitive', true, p.sensitive === true)
|
|
69
69
|
line(out, 'searchable', false, p.searchable === false)
|
|
70
|
+
line(out, 'site-search', p.siteSearch, p.siteSearch !== 'normal')
|
|
70
71
|
line(out, 'default', p.default)
|
|
71
72
|
line(out, 'since', p.since)
|
|
72
73
|
}
|
|
@@ -91,6 +92,7 @@ export function writeContentType(t: SchemaContentType, kind: SchemaTypeKind = 'd
|
|
|
91
92
|
line(out, 'collection', t.collection)
|
|
92
93
|
line(out, 'folder', t.folder)
|
|
93
94
|
line(out, 'since', t.since)
|
|
95
|
+
line(out, 'site-search', false, t.siteSearch === false)
|
|
94
96
|
if (
|
|
95
97
|
t.cleanup.prevent ||
|
|
96
98
|
t.cleanup.keepAllNewerThanDays !== undefined ||
|