@cratis/scene.engine 4.24.0 → 4.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/cjs/index.js +52 -0
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/sampleData/SampleRandom.js +77 -0
  4. package/dist/cjs/sampleData/SampleRandom.js.map +1 -0
  5. package/dist/cjs/sampleData/describeSampleDataCatalog.js +61 -0
  6. package/dist/cjs/sampleData/describeSampleDataCatalog.js.map +1 -0
  7. package/dist/cjs/sampleData/findSampleDataSet.js +18 -0
  8. package/dist/cjs/sampleData/findSampleDataSet.js.map +1 -0
  9. package/dist/cjs/sampleData/findSampleRowLiterals.js +73 -0
  10. package/dist/cjs/sampleData/findSampleRowLiterals.js.map +1 -0
  11. package/dist/cjs/sampleData/generateSample.js +188 -0
  12. package/dist/cjs/sampleData/generateSample.js.map +1 -0
  13. package/dist/cjs/sampleData/inferSampleFieldKind.js +176 -0
  14. package/dist/cjs/sampleData/inferSampleFieldKind.js.map +1 -0
  15. package/dist/cjs/sampleData/resolveSampleData.js +107 -0
  16. package/dist/cjs/sampleData/resolveSampleData.js.map +1 -0
  17. package/dist/cjs/sampleData/sampleDataRules.js +26 -0
  18. package/dist/cjs/sampleData/sampleDataRules.js.map +1 -0
  19. package/dist/cjs/sampleData/sampleLocales.js +359 -0
  20. package/dist/cjs/sampleData/sampleLocales.js.map +1 -0
  21. package/dist/cjs/sampleData/validateSampleDataPack.js +44 -0
  22. package/dist/cjs/sampleData/validateSampleDataPack.js.map +1 -0
  23. package/dist/cjs/sampleData/validateSampleDataPackages.js +71 -0
  24. package/dist/cjs/sampleData/validateSampleDataPackages.js.map +1 -0
  25. package/dist/cjs/sampleData/validateSampleDataSelection.js +52 -0
  26. package/dist/cjs/sampleData/validateSampleDataSelection.js.map +1 -0
  27. package/dist/cjs/templateApplicability.js.map +1 -1
  28. package/dist/cjs/templates/TemplateReferenceResolution.js +15 -0
  29. package/dist/cjs/templates/TemplateReferenceResolution.js.map +1 -0
  30. package/dist/cjs/templates/applicableTemplates.js +199 -0
  31. package/dist/cjs/templates/applicableTemplates.js.map +1 -0
  32. package/dist/cjs/templates/describeTemplateCatalog.js +11 -6
  33. package/dist/cjs/templates/describeTemplateCatalog.js.map +1 -1
  34. package/dist/cjs/templates/resolveTemplateReference.js +89 -0
  35. package/dist/cjs/templates/resolveTemplateReference.js.map +1 -0
  36. package/dist/cjs/templates/validateTemplateMetadata.js +9 -0
  37. package/dist/cjs/templates/validateTemplateMetadata.js.map +1 -1
  38. package/dist/esm/index.d.ts +1 -0
  39. package/dist/esm/index.d.ts.map +1 -1
  40. package/dist/esm/index.js +16 -1
  41. package/dist/esm/index.js.map +1 -1
  42. package/dist/esm/sampleData/SampleDataPackSource.d.ts +6 -0
  43. package/dist/esm/sampleData/SampleDataPackSource.d.ts.map +1 -0
  44. package/dist/esm/sampleData/SampleDataPackSource.js +2 -0
  45. package/dist/esm/sampleData/SampleDataPackSource.js.map +1 -0
  46. package/dist/esm/sampleData/SampleDataResolution.d.ts +29 -0
  47. package/dist/esm/sampleData/SampleDataResolution.d.ts.map +1 -0
  48. package/dist/esm/sampleData/SampleDataResolution.js +2 -0
  49. package/dist/esm/sampleData/SampleDataResolution.js.map +1 -0
  50. package/dist/esm/sampleData/SampleRandom.d.ts +14 -0
  51. package/dist/esm/sampleData/SampleRandom.d.ts.map +1 -0
  52. package/dist/esm/sampleData/SampleRandom.js +72 -0
  53. package/dist/esm/sampleData/SampleRandom.js.map +1 -0
  54. package/dist/esm/sampleData/describeSampleDataCatalog.d.ts +24 -0
  55. package/dist/esm/sampleData/describeSampleDataCatalog.d.ts.map +1 -0
  56. package/dist/esm/sampleData/describeSampleDataCatalog.js +57 -0
  57. package/dist/esm/sampleData/describeSampleDataCatalog.js.map +1 -0
  58. package/dist/esm/sampleData/findSampleDataSet.d.ts +3 -0
  59. package/dist/esm/sampleData/findSampleDataSet.d.ts.map +1 -0
  60. package/dist/esm/sampleData/findSampleDataSet.js +16 -0
  61. package/dist/esm/sampleData/findSampleDataSet.js.map +1 -0
  62. package/dist/esm/sampleData/findSampleRowLiterals.d.ts +11 -0
  63. package/dist/esm/sampleData/findSampleRowLiterals.d.ts.map +1 -0
  64. package/dist/esm/sampleData/findSampleRowLiterals.js +69 -0
  65. package/dist/esm/sampleData/findSampleRowLiterals.js.map +1 -0
  66. package/dist/esm/sampleData/generateSample.d.ts +6 -0
  67. package/dist/esm/sampleData/generateSample.d.ts.map +1 -0
  68. package/dist/esm/sampleData/generateSample.js +183 -0
  69. package/dist/esm/sampleData/generateSample.js.map +1 -0
  70. package/dist/esm/sampleData/index.d.ts +15 -0
  71. package/dist/esm/sampleData/index.d.ts.map +1 -0
  72. package/dist/esm/sampleData/index.js +15 -0
  73. package/dist/esm/sampleData/index.js.map +1 -0
  74. package/dist/esm/sampleData/inferSampleFieldKind.d.ts +3 -0
  75. package/dist/esm/sampleData/inferSampleFieldKind.d.ts.map +1 -0
  76. package/dist/esm/sampleData/inferSampleFieldKind.js +174 -0
  77. package/dist/esm/sampleData/inferSampleFieldKind.js.map +1 -0
  78. package/dist/esm/sampleData/resolveSampleData.d.ts +3 -0
  79. package/dist/esm/sampleData/resolveSampleData.d.ts.map +1 -0
  80. package/dist/esm/sampleData/resolveSampleData.js +105 -0
  81. package/dist/esm/sampleData/resolveSampleData.js.map +1 -0
  82. package/dist/esm/sampleData/sampleDataRules.d.ts +7 -0
  83. package/dist/esm/sampleData/sampleDataRules.d.ts.map +1 -0
  84. package/dist/esm/sampleData/sampleDataRules.js +19 -0
  85. package/dist/esm/sampleData/sampleDataRules.js.map +1 -0
  86. package/dist/esm/sampleData/sampleLocales.d.ts +13 -0
  87. package/dist/esm/sampleData/sampleLocales.d.ts.map +1 -0
  88. package/dist/esm/sampleData/sampleLocales.js +355 -0
  89. package/dist/esm/sampleData/sampleLocales.js.map +1 -0
  90. package/dist/esm/sampleData/validateSampleDataPack.d.ts +3 -0
  91. package/dist/esm/sampleData/validateSampleDataPack.d.ts.map +1 -0
  92. package/dist/esm/sampleData/validateSampleDataPack.js +42 -0
  93. package/dist/esm/sampleData/validateSampleDataPack.js.map +1 -0
  94. package/dist/esm/sampleData/validateSampleDataPackages.d.ts +3 -0
  95. package/dist/esm/sampleData/validateSampleDataPackages.d.ts.map +1 -0
  96. package/dist/esm/sampleData/validateSampleDataPackages.js +69 -0
  97. package/dist/esm/sampleData/validateSampleDataPackages.js.map +1 -0
  98. package/dist/esm/sampleData/validateSampleDataSelection.d.ts +3 -0
  99. package/dist/esm/sampleData/validateSampleDataSelection.d.ts.map +1 -0
  100. package/dist/esm/sampleData/validateSampleDataSelection.js +50 -0
  101. package/dist/esm/sampleData/validateSampleDataSelection.js.map +1 -0
  102. package/dist/esm/templateApplicability.d.ts +1 -2
  103. package/dist/esm/templateApplicability.d.ts.map +1 -1
  104. package/dist/esm/templateApplicability.js.map +1 -1
  105. package/dist/esm/templates/ApplicableTemplateSet.d.ts +27 -0
  106. package/dist/esm/templates/ApplicableTemplateSet.d.ts.map +1 -0
  107. package/dist/esm/templates/ApplicableTemplateSet.js +2 -0
  108. package/dist/esm/templates/ApplicableTemplateSet.js.map +1 -0
  109. package/dist/esm/templates/TemplateCatalogEntry.d.ts +3 -1
  110. package/dist/esm/templates/TemplateCatalogEntry.d.ts.map +1 -1
  111. package/dist/esm/templates/TemplateLibrary.d.ts +6 -0
  112. package/dist/esm/templates/TemplateLibrary.d.ts.map +1 -0
  113. package/dist/esm/templates/TemplateLibrary.js +2 -0
  114. package/dist/esm/templates/TemplateLibrary.js.map +1 -0
  115. package/dist/esm/templates/TemplateReferenceResolution.d.ts +16 -0
  116. package/dist/esm/templates/TemplateReferenceResolution.d.ts.map +1 -0
  117. package/dist/esm/templates/TemplateReferenceResolution.js +13 -0
  118. package/dist/esm/templates/TemplateReferenceResolution.js.map +1 -0
  119. package/dist/esm/templates/TemplateTarget.d.ts +9 -0
  120. package/dist/esm/templates/TemplateTarget.d.ts.map +1 -0
  121. package/dist/esm/templates/TemplateTarget.js +2 -0
  122. package/dist/esm/templates/TemplateTarget.js.map +1 -0
  123. package/dist/esm/templates/applicableTemplates.d.ts +9 -0
  124. package/dist/esm/templates/applicableTemplates.d.ts.map +1 -0
  125. package/dist/esm/templates/applicableTemplates.js +194 -0
  126. package/dist/esm/templates/applicableTemplates.js.map +1 -0
  127. package/dist/esm/templates/describeTemplateCatalog.d.ts.map +1 -1
  128. package/dist/esm/templates/describeTemplateCatalog.js +12 -7
  129. package/dist/esm/templates/describeTemplateCatalog.js.map +1 -1
  130. package/dist/esm/templates/index.d.ts +6 -0
  131. package/dist/esm/templates/index.d.ts.map +1 -1
  132. package/dist/esm/templates/index.js +6 -0
  133. package/dist/esm/templates/index.js.map +1 -1
  134. package/dist/esm/templates/resolveTemplateReference.d.ts +5 -0
  135. package/dist/esm/templates/resolveTemplateReference.d.ts.map +1 -0
  136. package/dist/esm/templates/resolveTemplateReference.js +87 -0
  137. package/dist/esm/templates/resolveTemplateReference.js.map +1 -0
  138. package/dist/esm/templates/validateTemplateMetadata.d.ts +2 -0
  139. package/dist/esm/templates/validateTemplateMetadata.d.ts.map +1 -1
  140. package/dist/esm/templates/validateTemplateMetadata.js +8 -1
  141. package/dist/esm/templates/validateTemplateMetadata.js.map +1 -1
  142. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  143. package/index.ts +1 -0
  144. package/package.json +3 -3
  145. package/sampleData/SampleDataPackSource.ts +13 -0
  146. package/sampleData/SampleDataResolution.ts +93 -0
  147. package/sampleData/SampleRandom.ts +85 -0
  148. package/sampleData/describeSampleDataCatalog.ts +108 -0
  149. package/sampleData/findSampleDataSet.ts +18 -0
  150. package/sampleData/findSampleRowLiterals.ts +86 -0
  151. package/sampleData/generateSample.ts +156 -0
  152. package/sampleData/index.ts +17 -0
  153. package/sampleData/inferSampleFieldKind.ts +64 -0
  154. package/sampleData/resolveSampleData.ts +96 -0
  155. package/sampleData/sampleDataRules.ts +30 -0
  156. package/sampleData/sampleLocales.ts +91 -0
  157. package/sampleData/validateSampleDataPack.ts +48 -0
  158. package/sampleData/validateSampleDataPackages.ts +79 -0
  159. package/sampleData/validateSampleDataSelection.ts +55 -0
  160. package/templateApplicability.ts +2 -1
  161. package/templates/ApplicableTemplateSet.ts +54 -0
  162. package/templates/TemplateCatalogEntry.ts +5 -1
  163. package/templates/TemplateLibrary.ts +16 -0
  164. package/templates/TemplateReferenceResolution.ts +35 -0
  165. package/templates/TemplateTarget.ts +23 -0
  166. package/templates/applicableTemplates.ts +148 -0
  167. package/templates/describeTemplateCatalog.ts +11 -7
  168. package/templates/index.ts +6 -0
  169. package/templates/resolveTemplateReference.ts +79 -0
  170. package/templates/validateTemplateMetadata.ts +11 -0
@@ -0,0 +1,148 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { DialogTemplate, Layout, ScreenTemplate, TemplateMetadata, TemplateReference, TemplateReferenceSource, TemplateScope, SliceKind } from '@cratis/scene.model';
5
+ import { resolveScreenTemplates } from '../resolveScreenTemplates';
6
+ import { validateDialogTemplateApplicability, validateLayoutApplicability, validateScreenTemplateApplicability } from '../templateApplicability';
7
+ import { ApplicableBlueprint, ApplicableTemplate, ApplicableTemplateGroup, ApplicableTemplateSet } from './ApplicableTemplateSet';
8
+ import { TemplateCatalogKind } from './TemplateCatalogKind';
9
+ import { TemplateLibrary } from './TemplateLibrary';
10
+ import { TemplateSource } from './TemplateSource';
11
+ import { TemplateTarget } from './TemplateTarget';
12
+
13
+ const screenScopes = [TemplateScope.Module, TemplateScope.Feature, TemplateScope.Subfeature];
14
+ const dialogSliceKinds = [SliceKind.StateChange];
15
+ const screenSliceKinds = [SliceKind.StateChange, SliceKind.StateView];
16
+
17
+ /**
18
+ * Decides which templates a picker offers at a target, so no host re-implements the rules: own templates by
19
+ * category, then each blueprint that has at least one applicable template. A blueprint with none is omitted.
20
+ *
21
+ * It composes the applicability validators (kind and scope, explicit scopes override, category never implies a
22
+ * scope, slot fit against the container) and `resolveScreenTemplates` (a template must find exactly one home and
23
+ * nothing may nest inside itself) with the defaults of each hierarchy level, used when a template declares no scopes:
24
+ *
25
+ * - Application - layouts.
26
+ * - Module, Feature, Subfeature - screen templates, fitting a slot of the container.
27
+ * - Slice, state change - dialog templates.
28
+ * - Slice, state view - screen templates that declare the Slice scope or state view slice kinds.
29
+ *
30
+ * Declared `scopes` and `sliceKinds` always override the defaults. The C# engine (`ApplicableTemplates.Compute`)
31
+ * returns identical results; both are asserted against `template-applicability-fixtures.json`.
32
+ */
33
+ export function applicableTemplates(library: TemplateLibrary, target: TemplateTarget): ApplicableTemplateSet {
34
+ const own = library.own ? groupByCategory(applicableIn(library.own, target, false)) : [];
35
+ const blueprints: ApplicableBlueprint[] = [];
36
+ for (const source of library.blueprints) {
37
+ const categories = groupByCategory(applicableIn(source, target, true));
38
+ if (categories.length > 0) {
39
+ blueprints.push({ blueprint: source.manifest.name, version: source.manifest.version, displayName: source.manifest.displayName, categories });
40
+ }
41
+ }
42
+ return { own, blueprints };
43
+ }
44
+
45
+ /** Why a layout does not apply at a target; empty when it does. */
46
+ export function validateLayoutForTarget(layout: Layout, target: TemplateTarget): string[] {
47
+ return withDefaults(validateLayoutApplicability(layout, target.scope), layout.name, TemplateCatalogKind.Layout, layout.metadata, target);
48
+ }
49
+
50
+ /** Why a screen template does not apply at a target, including whether its slot nesting resolves; empty when it does. */
51
+ export function validateScreenTemplateForTarget(template: ScreenTemplate, target: TemplateTarget): string[] {
52
+ const problems = withDefaults(
53
+ validateScreenTemplateApplicability(template, target.scope, target.container),
54
+ template.name,
55
+ TemplateCatalogKind.ScreenTemplate,
56
+ template.metadata,
57
+ target,
58
+ );
59
+ if (problems.length > 0) return problems;
60
+
61
+ if (target.chain?.includes(template.name)) {
62
+ return [`Template '${template.name}' is already chosen above this target; nesting it again would make the chain loop`];
63
+ }
64
+
65
+ if (template.fitsSlot !== undefined && target.container) {
66
+ const containerName = target.container.name;
67
+ const resolution = resolveScreenTemplates({ name: containerName, slots: target.container.slots } as Layout, [template]);
68
+ const valid = resolution.unplaced.length === 0 && resolution.cycles.length === 0;
69
+ if (!valid || !resolution.placements.some(placement => placement.template === template.name && placement.container === containerName)) {
70
+ return [`Template '${template.name}' cannot be placed in '${containerName}': its slot nesting does not resolve`];
71
+ }
72
+ }
73
+ return [];
74
+ }
75
+
76
+ /** Why a dialog template does not apply at a target; empty when it does. */
77
+ export function validateDialogTemplateForTarget(template: DialogTemplate, target: TemplateTarget): string[] {
78
+ return withDefaults(validateDialogTemplateApplicability(template, target.scope), template.name, TemplateCatalogKind.DialogTemplate, template.metadata, target);
79
+ }
80
+
81
+ function applicableIn(source: TemplateSource, target: TemplateTarget, isBlueprint: boolean): ApplicableTemplate[] {
82
+ const { manifest } = source;
83
+ const referenceTo = (name: string): TemplateReference => isBlueprint
84
+ ? { source: TemplateReferenceSource.Blueprint, id: name, blueprint: manifest.name, version: `^${manifest.version}` }
85
+ : { source: TemplateReferenceSource.Own, id: name };
86
+
87
+ const applicable: ApplicableTemplate[] = [];
88
+ for (const layout of source.layouts ?? []) {
89
+ if (validateLayoutForTarget(layout, target).length > 0) continue;
90
+ applicable.push({
91
+ package: manifest.name, packageVersion: manifest.version, kind: TemplateCatalogKind.Layout, name: layout.name,
92
+ metadata: layout.metadata, reference: referenceTo(layout.name),
93
+ });
94
+ }
95
+ for (const template of source.screenTemplates ?? []) {
96
+ if (validateScreenTemplateForTarget(template, target).length > 0) continue;
97
+ applicable.push({
98
+ package: manifest.name, packageVersion: manifest.version, kind: TemplateCatalogKind.ScreenTemplate, name: template.name,
99
+ displayName: template.displayName, description: template.description, metadata: template.metadata, reference: referenceTo(template.name),
100
+ });
101
+ }
102
+ for (const template of source.dialogTemplates ?? []) {
103
+ if (validateDialogTemplateForTarget(template, target).length > 0) continue;
104
+ applicable.push({
105
+ package: manifest.name, packageVersion: manifest.version, kind: TemplateCatalogKind.DialogTemplate, name: template.name,
106
+ displayName: template.displayName, description: template.description, metadata: template.metadata, reference: referenceTo(template.name),
107
+ });
108
+ }
109
+ return applicable;
110
+ }
111
+
112
+ function groupByCategory(templates: ApplicableTemplate[]): ApplicableTemplateGroup[] {
113
+ const categoryOf = (template: ApplicableTemplate) => template.metadata?.category?.trim() ? template.metadata.category : undefined;
114
+ const categories = [...new Set(templates.map(categoryOf).filter((category): category is string => category !== undefined))];
115
+ const groups: ApplicableTemplateGroup[] = categories.map(category => ({ category, templates: templates.filter(template => categoryOf(template) === category) }));
116
+ const uncategorized = templates.filter(template => categoryOf(template) === undefined);
117
+ if (uncategorized.length > 0) groups.push({ templates: uncategorized });
118
+ return groups;
119
+ }
120
+
121
+ function withDefaults(problems: string[], name: string, kind: TemplateCatalogKind, metadata: TemplateMetadata | undefined, target: TemplateTarget): string[] {
122
+ if (problems.length > 0 || !Object.values(TemplateScope).includes(target.scope)) return problems;
123
+
124
+ if (metadata?.scopes === undefined && !defaultScopes(kind, metadata).includes(target.scope)) {
125
+ return [`Template '${name}' is not offered at scope '${target.scope}' unless it declares that scope`];
126
+ }
127
+
128
+ if (target.scope === TemplateScope.Slice && target.sliceKind !== undefined) {
129
+ const sliceKind = target.sliceKind;
130
+ if (!Object.values(SliceKind).includes(sliceKind)) return [`Template '${name}' has unknown target slice kind '${sliceKind}'`];
131
+ if (sliceKind === SliceKind.Automation) return [`Template '${name}' cannot be applied to an Automation slice, which has no presentation`];
132
+ const kinds = metadata?.sliceKinds ?? defaultSliceKinds(kind);
133
+ if (!kinds.includes(sliceKind)) return [`Template '${name}' does not apply to slice kind '${sliceKind}'`];
134
+ }
135
+ return [];
136
+ }
137
+
138
+ function defaultScopes(kind: TemplateCatalogKind, metadata: TemplateMetadata | undefined): TemplateScope[] {
139
+ switch (kind) {
140
+ case TemplateCatalogKind.Layout: return [TemplateScope.Application];
141
+ case TemplateCatalogKind.DialogTemplate: return [TemplateScope.Slice];
142
+ default: return metadata?.sliceKinds === undefined ? screenScopes : [TemplateScope.Slice];
143
+ }
144
+ }
145
+
146
+ function defaultSliceKinds(kind: TemplateCatalogKind): SliceKind[] {
147
+ return kind === TemplateCatalogKind.DialogTemplate ? dialogSliceKinds : screenSliceKinds;
148
+ }
@@ -1,12 +1,12 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { ScenePackage, TemplateMetadata } from '@cratis/scene.model';
4
+ import { PackageKind, ScenePackage, TemplateMetadata } from '@cratis/scene.model';
5
5
  import { isVersionSatisfiedBy } from '../packageVersionRange';
6
6
  import { TemplateCatalogEntry } from './TemplateCatalogEntry';
7
7
  import { TemplateCatalogKind } from './TemplateCatalogKind';
8
8
  import { TemplateSource } from './TemplateSource';
9
- import { validateTemplateMetadata } from './validateTemplateMetadata';
9
+ import { validateTemplateCategory, validateTemplateMetadata } from './validateTemplateMetadata';
10
10
 
11
11
  /**
12
12
  * Describes every template the given packages provide, in package then layout, screen template, dialog
@@ -14,7 +14,8 @@ import { validateTemplateMetadata } from './validateTemplateMetadata';
14
14
  *
15
15
  * A template is compatible when its metadata is complete and valid, the Scene version (when given) satisfies
16
16
  * its Scene range, and every package it requires is among the given packages at a satisfying version and is
17
- * either its own package or one that package depends on. Nothing is filtered out: a browser shows incompatible
17
+ * either its own package or one that package depends on. A blueprint template without a category is a problem; an
18
+ * own template without one is a warning. Nothing is filtered out: a browser shows incompatible
18
19
  * templates with their problems rather than hiding why they are unavailable.
19
20
  */
20
21
  export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?: string): TemplateCatalogEntry[] {
@@ -31,10 +32,11 @@ export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?
31
32
  for (const { kind, template } of templates) {
32
33
  const metadata = template.metadata;
33
34
  const describable = template as { displayName?: string; description?: string };
34
- const problems = [
35
- ...validateTemplateMetadata(template.name, metadata),
36
- ...requirementProblems(template.name, metadata, source.manifest, versions, sceneVersion),
37
- ];
35
+ const problems = validateTemplateMetadata(template.name, metadata);
36
+ const warnings: string[] = [];
37
+ const missingCategory = validateTemplateCategory(template.name, metadata);
38
+ if (missingCategory) (source.manifest.kind === PackageKind.Blueprint ? problems : warnings).push(missingCategory);
39
+ problems.push(...requirementProblems(template.name, metadata, source.manifest, versions, sceneVersion));
38
40
 
39
41
  entries.push({
40
42
  package: source.manifest.name,
@@ -46,12 +48,14 @@ export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?
46
48
  type: metadata?.type,
47
49
  category: metadata?.category,
48
50
  scopes: metadata?.scopes,
51
+ sliceKinds: metadata?.sliceKinds,
49
52
  compatibility: metadata?.compatibility,
50
53
  attribution: metadata?.attribution,
51
54
  license: metadata?.license,
52
55
  licenseUrl: metadata?.licenseUrl,
53
56
  compatible: problems.length === 0,
54
57
  problems,
58
+ warnings,
55
59
  });
56
60
  }
57
61
  }
@@ -1,9 +1,15 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
+ export * from './ApplicableTemplateSet';
4
5
  export * from './TemplateCatalogEntry';
5
6
  export * from './TemplateCatalogKind';
7
+ export * from './TemplateLibrary';
8
+ export * from './TemplateReferenceResolution';
6
9
  export * from './TemplateSource';
10
+ export * from './TemplateTarget';
11
+ export * from './applicableTemplates';
7
12
  export * from './describeTemplateCatalog';
13
+ export * from './resolveTemplateReference';
8
14
  export * from './templateMetadataRules';
9
15
  export * from './validateTemplateMetadata';
@@ -0,0 +1,79 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { TemplateReference, TemplateReferenceSource } from '@cratis/scene.model';
5
+ import { isValidVersionRange, isVersionSatisfiedBy } from '../packageVersionRange';
6
+ import { TemplateCatalogKind } from './TemplateCatalogKind';
7
+ import { TemplateLibrary } from './TemplateLibrary';
8
+ import { TemplateReferenceResolution, TemplateReferenceStatus } from './TemplateReferenceResolution';
9
+ import { TemplateSource } from './TemplateSource';
10
+
11
+ /**
12
+ * Resolves a template reference against the templates that exist, including the blueprint version pin. The C#
13
+ * engine (`TemplateReferenceResolver.Resolve`) reports the same outcomes and messages; both are asserted against
14
+ * `template-applicability-fixtures.json`.
15
+ */
16
+ export function resolveTemplateReference(reference: TemplateReference, library: TemplateLibrary): TemplateReferenceResolution {
17
+ if (!reference.id?.trim()) {
18
+ return { status: TemplateReferenceStatus.Malformed, message: 'A template reference must name a template' };
19
+ }
20
+
21
+ if (reference.source === TemplateReferenceSource.Own) {
22
+ if (reference.blueprint !== undefined || reference.version !== undefined) {
23
+ return { status: TemplateReferenceStatus.Malformed, message: `Own template reference '${reference.id}' must not name a blueprint or a version` };
24
+ }
25
+ return library.own ? find(reference.id, library.own, reference) : unknown(reference, undefined);
26
+ }
27
+
28
+ if (!reference.blueprint?.trim()) {
29
+ return { status: TemplateReferenceStatus.Malformed, message: `Blueprint template reference '${reference.id}' must name its blueprint` };
30
+ }
31
+
32
+ const blueprint = library.blueprints.find(source => source.manifest.name === reference.blueprint);
33
+ if (!blueprint) {
34
+ return { status: TemplateReferenceStatus.UnknownBlueprint, message: `Blueprint '${reference.blueprint}' referenced by template '${reference.id}' is not available` };
35
+ }
36
+
37
+ const installed = blueprint.manifest.version;
38
+ if (reference.version !== undefined) {
39
+ if (!isValidVersionRange(reference.version)) {
40
+ return {
41
+ status: TemplateReferenceStatus.Malformed, package: blueprint.manifest.name, packageVersion: installed,
42
+ message: `Template reference '${reference.id}' declares an invalid version range '${reference.version}'`,
43
+ };
44
+ }
45
+ if (!isVersionSatisfiedBy(installed, reference.version)) {
46
+ return {
47
+ status: TemplateReferenceStatus.VersionNotSatisfied, package: blueprint.manifest.name, packageVersion: installed,
48
+ message: `Template '${reference.id}' was chosen from blueprint '${blueprint.manifest.name}' ${reference.version} but version ${installed} is installed`,
49
+ };
50
+ }
51
+ }
52
+
53
+ return find(reference.id, blueprint, reference);
54
+ }
55
+
56
+ function find(name: string, source: TemplateSource, reference: TemplateReference): TemplateReferenceResolution {
57
+ const kind = kindOf(name, source);
58
+ return kind === undefined
59
+ ? unknown(reference, source)
60
+ : { status: TemplateReferenceStatus.Resolved, package: source.manifest.name, packageVersion: source.manifest.version, kind };
61
+ }
62
+
63
+ function unknown(reference: TemplateReference, source: TemplateSource | undefined): TemplateReferenceResolution {
64
+ return {
65
+ status: TemplateReferenceStatus.UnknownTemplate,
66
+ package: source?.manifest.name,
67
+ packageVersion: source?.manifest.version,
68
+ message: reference.source === TemplateReferenceSource.Own
69
+ ? `Own template '${reference.id}' does not exist`
70
+ : `Blueprint '${reference.blueprint}' has no template '${reference.id}'`,
71
+ };
72
+ }
73
+
74
+ function kindOf(name: string, source: TemplateSource): TemplateCatalogKind | undefined {
75
+ if (source.screenTemplates?.some(template => template.name === name)) return TemplateCatalogKind.ScreenTemplate;
76
+ if (source.dialogTemplates?.some(template => template.name === name)) return TemplateCatalogKind.DialogTemplate;
77
+ if (source.layouts?.some(layout => layout.name === name)) return TemplateCatalogKind.Layout;
78
+ return undefined;
79
+ }
@@ -53,3 +53,14 @@ export function validateTemplateMetadata(name: string, metadata: TemplateMetadat
53
53
 
54
54
  return problems;
55
55
  }
56
+
57
+ /**
58
+ * The diagnostic code reported for a template without a category. Category is required for blueprint templates
59
+ * (an error) and recommended for own templates (a warning).
60
+ */
61
+ export const templateCategoryMissingCode = 'templateCategoryMissing';
62
+
63
+ /** Checks that a template declares a browse category; returns the problem, or undefined when it does. */
64
+ export function validateTemplateCategory(name: string, metadata: TemplateMetadata | undefined): string | undefined {
65
+ return metadata?.category?.trim() ? undefined : `Template '${name}' declares no category`;
66
+ }