@cratis/scene.engine 4.12.0 → 4.13.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.
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/navigation/NavigationDiagnosticCode.js +19 -0
- package/dist/cjs/navigation/NavigationDiagnosticCode.js.map +1 -0
- package/dist/cjs/navigation/collectOutlets.js +45 -0
- package/dist/cjs/navigation/collectOutlets.js.map +1 -0
- package/dist/cjs/navigation/diagnoseNavigation.js +116 -0
- package/dist/cjs/navigation/diagnoseNavigation.js.map +1 -0
- package/dist/cjs/navigation/findHostingCycles.js +43 -0
- package/dist/cjs/navigation/findHostingCycles.js.map +1 -0
- package/dist/cjs/navigation/navigationEntriesFrom.js +19 -0
- package/dist/cjs/navigation/navigationEntriesFrom.js.map +1 -0
- package/dist/cjs/navigation/navigationRoute.js +25 -0
- package/dist/cjs/navigation/navigationRoute.js.map +1 -0
- package/dist/cjs/packageVersionRange.js +16 -0
- package/dist/cjs/packageVersionRange.js.map +1 -1
- package/dist/cjs/templates/TemplateCatalogKind.js +15 -0
- package/dist/cjs/templates/TemplateCatalogKind.js.map +1 -0
- package/dist/cjs/templates/describeTemplateCatalog.js +89 -0
- package/dist/cjs/templates/describeTemplateCatalog.js.map +1 -0
- package/dist/cjs/templates/templateMetadataRules.js +32 -0
- package/dist/cjs/templates/templateMetadataRules.js.map +1 -0
- package/dist/cjs/templates/validateTemplateMetadata.js +50 -0
- package/dist/cjs/templates/validateTemplateMetadata.js.map +1 -0
- package/dist/cjs/validateNavigationDestinations.js +12 -38
- package/dist/cjs/validateNavigationDestinations.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +9 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/navigation/NavigationDiagnostic.d.ts +7 -0
- package/dist/esm/navigation/NavigationDiagnostic.d.ts.map +1 -0
- package/dist/esm/navigation/NavigationDiagnostic.js +2 -0
- package/dist/esm/navigation/NavigationDiagnostic.js.map +1 -0
- package/dist/esm/navigation/NavigationDiagnosticCode.d.ts +9 -0
- package/dist/esm/navigation/NavigationDiagnosticCode.d.ts.map +1 -0
- package/dist/esm/navigation/NavigationDiagnosticCode.js +17 -0
- package/dist/esm/navigation/NavigationDiagnosticCode.js.map +1 -0
- package/dist/esm/navigation/NavigationEntry.d.ts +6 -0
- package/dist/esm/navigation/NavigationEntry.d.ts.map +1 -0
- package/dist/esm/navigation/NavigationEntry.js +2 -0
- package/dist/esm/navigation/NavigationEntry.js.map +1 -0
- package/dist/esm/navigation/NavigationGraph.d.ts +10 -0
- package/dist/esm/navigation/NavigationGraph.d.ts.map +1 -0
- package/dist/esm/navigation/NavigationGraph.js +2 -0
- package/dist/esm/navigation/NavigationGraph.js.map +1 -0
- package/dist/esm/navigation/OutletOwner.d.ts +7 -0
- package/dist/esm/navigation/OutletOwner.d.ts.map +1 -0
- package/dist/esm/navigation/OutletOwner.js +2 -0
- package/dist/esm/navigation/OutletOwner.js.map +1 -0
- package/dist/esm/navigation/collectOutlets.d.ts +5 -0
- package/dist/esm/navigation/collectOutlets.d.ts.map +1 -0
- package/dist/esm/navigation/collectOutlets.js +43 -0
- package/dist/esm/navigation/collectOutlets.js.map +1 -0
- package/dist/esm/navigation/diagnoseNavigation.d.ts +5 -0
- package/dist/esm/navigation/diagnoseNavigation.d.ts.map +1 -0
- package/dist/esm/navigation/diagnoseNavigation.js +114 -0
- package/dist/esm/navigation/diagnoseNavigation.js.map +1 -0
- package/dist/esm/navigation/findHostingCycles.d.ts +2 -0
- package/dist/esm/navigation/findHostingCycles.d.ts.map +1 -0
- package/dist/esm/navigation/findHostingCycles.js +41 -0
- package/dist/esm/navigation/findHostingCycles.js.map +1 -0
- package/dist/esm/navigation/index.d.ts +8 -0
- package/dist/esm/navigation/index.d.ts.map +1 -0
- package/dist/esm/navigation/index.js +8 -0
- package/dist/esm/navigation/index.js.map +1 -0
- package/dist/esm/navigation/navigationEntriesFrom.d.ts +4 -0
- package/dist/esm/navigation/navigationEntriesFrom.d.ts.map +1 -0
- package/dist/esm/navigation/navigationEntriesFrom.js +17 -0
- package/dist/esm/navigation/navigationEntriesFrom.js.map +1 -0
- package/dist/esm/navigation/navigationRoute.d.ts +5 -0
- package/dist/esm/navigation/navigationRoute.d.ts.map +1 -0
- package/dist/esm/navigation/navigationRoute.js +22 -0
- package/dist/esm/navigation/navigationRoute.js.map +1 -0
- package/dist/esm/packageVersionRange.d.ts +2 -0
- package/dist/esm/packageVersionRange.d.ts.map +1 -1
- package/dist/esm/packageVersionRange.js +15 -1
- package/dist/esm/packageVersionRange.js.map +1 -1
- package/dist/esm/templates/TemplateCatalogEntry.d.ts +20 -0
- package/dist/esm/templates/TemplateCatalogEntry.d.ts.map +1 -0
- package/dist/esm/templates/TemplateCatalogEntry.js +2 -0
- package/dist/esm/templates/TemplateCatalogEntry.js.map +1 -0
- package/dist/esm/templates/TemplateCatalogKind.d.ts +6 -0
- package/dist/esm/templates/TemplateCatalogKind.d.ts.map +1 -0
- package/dist/esm/templates/TemplateCatalogKind.js +13 -0
- package/dist/esm/templates/TemplateCatalogKind.js.map +1 -0
- package/dist/esm/templates/TemplateSource.d.ts +8 -0
- package/dist/esm/templates/TemplateSource.d.ts.map +1 -0
- package/dist/esm/templates/TemplateSource.js +2 -0
- package/dist/esm/templates/TemplateSource.js.map +1 -0
- package/dist/esm/templates/describeTemplateCatalog.d.ts +4 -0
- package/dist/esm/templates/describeTemplateCatalog.d.ts.map +1 -0
- package/dist/esm/templates/describeTemplateCatalog.js +87 -0
- package/dist/esm/templates/describeTemplateCatalog.js.map +1 -0
- package/dist/esm/templates/index.d.ts +7 -0
- package/dist/esm/templates/index.d.ts.map +1 -0
- package/dist/esm/templates/index.js +7 -0
- package/dist/esm/templates/index.js.map +1 -0
- package/dist/esm/templates/templateMetadataRules.d.ts +3 -0
- package/dist/esm/templates/templateMetadataRules.d.ts.map +1 -0
- package/dist/esm/templates/templateMetadataRules.js +29 -0
- package/dist/esm/templates/templateMetadataRules.js.map +1 -0
- package/dist/esm/templates/validateTemplateMetadata.d.ts +3 -0
- package/dist/esm/templates/validateTemplateMetadata.d.ts.map +1 -0
- package/dist/esm/templates/validateTemplateMetadata.js +48 -0
- package/dist/esm/templates/validateTemplateMetadata.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/validateNavigationDestinations.d.ts +5 -5
- package/dist/esm/validateNavigationDestinations.d.ts.map +1 -1
- package/dist/esm/validateNavigationDestinations.js +12 -38
- package/dist/esm/validateNavigationDestinations.js.map +1 -1
- package/index.ts +2 -0
- package/navigation/NavigationDiagnostic.ts +18 -0
- package/navigation/NavigationDiagnosticCode.ts +26 -0
- package/navigation/NavigationEntry.ts +16 -0
- package/navigation/NavigationGraph.ts +26 -0
- package/navigation/OutletOwner.ts +13 -0
- package/navigation/collectOutlets.ts +36 -0
- package/navigation/diagnoseNavigation.ts +136 -0
- package/navigation/findHostingCycles.ts +41 -0
- package/navigation/index.ts +10 -0
- package/navigation/navigationEntriesFrom.ts +16 -0
- package/navigation/navigationRoute.ts +25 -0
- package/package.json +3 -3
- package/packageVersionRange.ts +18 -0
- package/templates/TemplateCatalogEntry.ts +35 -0
- package/templates/TemplateCatalogKind.ts +11 -0
- package/templates/TemplateSource.ts +15 -0
- package/templates/describeTemplateCatalog.ts +92 -0
- package/templates/index.ts +9 -0
- package/templates/templateMetadataRules.ts +25 -0
- package/templates/validateTemplateMetadata.ts +55 -0
- package/validateNavigationDestinations.ts +12 -32
|
@@ -0,0 +1,92 @@
|
|
|
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 { ScenePackage, TemplateMetadata } from '@cratis/scene.model';
|
|
5
|
+
import { isVersionSatisfiedBy } from '../packageVersionRange';
|
|
6
|
+
import { TemplateCatalogEntry } from './TemplateCatalogEntry';
|
|
7
|
+
import { TemplateCatalogKind } from './TemplateCatalogKind';
|
|
8
|
+
import { TemplateSource } from './TemplateSource';
|
|
9
|
+
import { validateTemplateMetadata } from './validateTemplateMetadata';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Describes every template the given packages provide, in package then layout, screen template, dialog
|
|
13
|
+
* template order, with its compatibility, attribution and license metadata and whether it can be used.
|
|
14
|
+
*
|
|
15
|
+
* A template is compatible when its metadata is complete and valid, the Scene version (when given) satisfies
|
|
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
|
|
18
|
+
* templates with their problems rather than hiding why they are unavailable.
|
|
19
|
+
*/
|
|
20
|
+
export function describeTemplateCatalog(sources: TemplateSource[], sceneVersion?: string): TemplateCatalogEntry[] {
|
|
21
|
+
const versions = new Map(sources.map(source => [source.manifest.name, source.manifest.version]));
|
|
22
|
+
const entries: TemplateCatalogEntry[] = [];
|
|
23
|
+
|
|
24
|
+
for (const source of sources) {
|
|
25
|
+
const templates = [
|
|
26
|
+
...(source.layouts ?? []).map(template => ({ kind: TemplateCatalogKind.Layout, template })),
|
|
27
|
+
...(source.screenTemplates ?? []).map(template => ({ kind: TemplateCatalogKind.ScreenTemplate, template })),
|
|
28
|
+
...(source.dialogTemplates ?? []).map(template => ({ kind: TemplateCatalogKind.DialogTemplate, template })),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
for (const { kind, template } of templates) {
|
|
32
|
+
const metadata = template.metadata;
|
|
33
|
+
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
|
+
];
|
|
38
|
+
|
|
39
|
+
entries.push({
|
|
40
|
+
package: source.manifest.name,
|
|
41
|
+
packageVersion: source.manifest.version,
|
|
42
|
+
kind,
|
|
43
|
+
name: template.name,
|
|
44
|
+
displayName: describable.displayName,
|
|
45
|
+
description: describable.description,
|
|
46
|
+
type: metadata?.type,
|
|
47
|
+
category: metadata?.category,
|
|
48
|
+
scopes: metadata?.scopes,
|
|
49
|
+
compatibility: metadata?.compatibility,
|
|
50
|
+
attribution: metadata?.attribution,
|
|
51
|
+
license: metadata?.license,
|
|
52
|
+
licenseUrl: metadata?.licenseUrl,
|
|
53
|
+
compatible: problems.length === 0,
|
|
54
|
+
problems,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return entries;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function requirementProblems(
|
|
63
|
+
name: string,
|
|
64
|
+
metadata: TemplateMetadata | undefined,
|
|
65
|
+
owner: ScenePackage,
|
|
66
|
+
versions: Map<string, string>,
|
|
67
|
+
sceneVersion: string | undefined,
|
|
68
|
+
): string[] {
|
|
69
|
+
const problems: string[] = [];
|
|
70
|
+
const compatibility = metadata?.compatibility;
|
|
71
|
+
if (!compatibility) return problems;
|
|
72
|
+
|
|
73
|
+
if (sceneVersion !== undefined && compatibility.scene?.trim() && !isVersionSatisfiedBy(sceneVersion, compatibility.scene)) {
|
|
74
|
+
problems.push(`Template '${name}' requires Scene ${compatibility.scene} but the host runs ${sceneVersion}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const requirement of compatibility.packages ?? []) {
|
|
78
|
+
if (!requirement.name?.trim()) continue;
|
|
79
|
+
if (requirement.name !== owner.name && !owner.dependencies.some(dependency => dependency.name === requirement.name)) {
|
|
80
|
+
problems.push(`Template '${name}' requires package '${requirement.name}', which its package '${owner.name}' does not depend on`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const version = versions.get(requirement.name);
|
|
84
|
+
if (version === undefined) {
|
|
85
|
+
problems.push(`Template '${name}' requires package '${requirement.name}', which is not available`);
|
|
86
|
+
} else if (!isVersionSatisfiedBy(version, requirement.versionRange)) {
|
|
87
|
+
problems.push(`Template '${name}' requires package '${requirement.name}' ${requirement.versionRange} but version ${version} is available`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return problems;
|
|
92
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export * from './TemplateCatalogEntry';
|
|
5
|
+
export * from './TemplateCatalogKind';
|
|
6
|
+
export * from './TemplateSource';
|
|
7
|
+
export * from './describeTemplateCatalog';
|
|
8
|
+
export * from './templateMetadataRules';
|
|
9
|
+
export * from './validateTemplateMetadata';
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* Whether a value is an absolute `https` URL with a host - the only kind of link a template browser
|
|
6
|
+
* should follow for attribution or license terms. Kept to plain string rules so the C# twin matches exactly.
|
|
7
|
+
*/
|
|
8
|
+
export function isAbsoluteHttpsUrl(value: string): boolean {
|
|
9
|
+
const prefix = 'https://';
|
|
10
|
+
return value.startsWith(prefix) && value.length > prefix.length && value[prefix.length] !== '/' && !/\s/.test(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether a value is a simple SPDX license expression: identifiers such as `MIT`, `Apache-2.0` or
|
|
15
|
+
* `LicenseRef-Acme`, optionally joined by `AND`, `OR` or `WITH`. Parentheses are not supported.
|
|
16
|
+
*/
|
|
17
|
+
export function isSpdxExpression(value: string): boolean {
|
|
18
|
+
const tokens = value.split(' ');
|
|
19
|
+
if (tokens.length % 2 === 0) return false;
|
|
20
|
+
return tokens.every((token, index) => (index % 2 === 1 ? ['AND', 'OR', 'WITH'].includes(token) : isSpdxIdentifier(token)));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isSpdxIdentifier(token: string): boolean {
|
|
24
|
+
return token.length > 0 && [...token].every(character => /[A-Za-z0-9.+-]/.test(character));
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { TemplateMetadata } from '@cratis/scene.model';
|
|
5
|
+
import { isValidVersionRange } from '../packageVersionRange';
|
|
6
|
+
import { isAbsoluteHttpsUrl, isSpdxExpression } from './templateMetadataRules';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks that a template carries the compatibility, attribution and license metadata every shipped template
|
|
10
|
+
* needs, and that each value is well formed. The C# engine (`TemplateMetadataValidation.Validate`) reports the
|
|
11
|
+
* same problems in the same order; both are asserted against `template-metadata-fixtures.json`.
|
|
12
|
+
*/
|
|
13
|
+
export function validateTemplateMetadata(name: string, metadata: TemplateMetadata | undefined): string[] {
|
|
14
|
+
const problems: string[] = [];
|
|
15
|
+
const compatibility = metadata?.compatibility;
|
|
16
|
+
|
|
17
|
+
if (!compatibility) {
|
|
18
|
+
problems.push(`Template '${name}' declares no compatibility`);
|
|
19
|
+
} else {
|
|
20
|
+
if (!compatibility.scene?.trim()) {
|
|
21
|
+
problems.push(`Template '${name}' declares no Scene version range`);
|
|
22
|
+
} else if (!isValidVersionRange(compatibility.scene)) {
|
|
23
|
+
problems.push(`Template '${name}' declares an invalid Scene version range '${compatibility.scene}'`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const requirement of compatibility.packages ?? []) {
|
|
27
|
+
if (!requirement.name?.trim()) {
|
|
28
|
+
problems.push(`Template '${name}' declares a package requirement without a name`);
|
|
29
|
+
} else if (!isValidVersionRange(requirement.versionRange)) {
|
|
30
|
+
problems.push(`Template '${name}' declares an invalid version range '${requirement.versionRange}' for package '${requirement.name}'`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const attribution = metadata?.attribution;
|
|
36
|
+
if (!attribution?.author?.trim()) {
|
|
37
|
+
problems.push(`Template '${name}' declares no author`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (attribution?.url !== undefined && !isAbsoluteHttpsUrl(attribution.url)) {
|
|
41
|
+
problems.push(`Template '${name}' declares an attribution URL '${attribution.url}' that is not an absolute https URL`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!metadata?.license?.trim()) {
|
|
45
|
+
problems.push(`Template '${name}' declares no license`);
|
|
46
|
+
} else if (!isSpdxExpression(metadata.license)) {
|
|
47
|
+
problems.push(`Template '${name}' declares '${metadata.license}', which is not an SPDX license expression`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (metadata?.licenseUrl !== undefined && !isAbsoluteHttpsUrl(metadata.licenseUrl)) {
|
|
51
|
+
problems.push(`Template '${name}' declares a license URL '${metadata.licenseUrl}' that is not an absolute https URL`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return problems;
|
|
55
|
+
}
|
|
@@ -1,43 +1,23 @@
|
|
|
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 { DestinationReference, Layout, ScreenTemplate } from '@cratis/scene.model';
|
|
4
|
+
import { DestinationReference, DialogTemplate, Layout, Screen, ScreenTemplate } from '@cratis/scene.model';
|
|
5
|
+
import { NavigationDiagnostic, diagnoseNavigation } from './navigation';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A navigation problem as {@link validateNavigationDestinations} reports it.
|
|
9
|
+
*/
|
|
10
|
+
export type NavigationDestinationDiagnostic = NavigationDiagnostic;
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Validates destinations against the outlet/route declarations owned by layouts and templates
|
|
13
|
+
* Validates destinations against the outlet/route declarations owned by layouts and templates, and - when
|
|
14
|
+
* the screens and dialog templates are given - against the targets that actually exist.
|
|
15
|
+
*
|
|
16
|
+
* A shorthand for {@link diagnoseNavigation} over destinations without entry identities.
|
|
13
17
|
*/
|
|
14
18
|
export function validateNavigationDestinations(
|
|
15
19
|
destinations: DestinationReference[],
|
|
16
|
-
surfaces: { layouts?: Layout[]; screenTemplates?: ScreenTemplate[] } = {},
|
|
20
|
+
surfaces: { layouts?: Layout[]; screenTemplates?: ScreenTemplate[]; screens?: Screen[]; dialogTemplates?: DialogTemplate[] } = {},
|
|
17
21
|
): NavigationDestinationDiagnostic[] {
|
|
18
|
-
|
|
19
|
-
const outlets = new Set<string>();
|
|
20
|
-
const routes = new Set<string>();
|
|
21
|
-
|
|
22
|
-
for (const surface of [...(surfaces.layouts ?? []), ...(surfaces.screenTemplates ?? [])]) {
|
|
23
|
-
for (const outlet of surface.outlets ?? []) {
|
|
24
|
-
if (outlets.has(outlet.name)) diagnostics.push({ code: 'duplicateOutlet', message: `Outlet '${outlet.name}' is declared more than once.` });
|
|
25
|
-
outlets.add(outlet.name);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (const destination of destinations) {
|
|
30
|
-
if (destination.outlet && !outlets.has(destination.outlet)) {
|
|
31
|
-
diagnostics.push({ code: 'missingOutlet', message: `Destination targets missing outlet '${destination.outlet}'.` });
|
|
32
|
-
}
|
|
33
|
-
if (destination.route) {
|
|
34
|
-
if (routes.has(destination.route)) diagnostics.push({ code: 'duplicateRoute', message: `Route '${destination.route}' is used by more than one destination.` });
|
|
35
|
-
routes.add(destination.route);
|
|
36
|
-
}
|
|
37
|
-
if (!destination.screen && !destination.module && !destination.route && !destination.dialog) {
|
|
38
|
-
diagnostics.push({ code: 'unavailableTarget', message: 'Destination must name a screen, stable identity, route or dialog.' });
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return diagnostics;
|
|
22
|
+
return diagnoseNavigation({ entries: destinations.map(destination => ({ destination })), ...surfaces });
|
|
43
23
|
}
|