@topogram/cli 0.3.64 → 0.3.65
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 +1 -1
- package/src/adoption/plan/index.js +703 -0
- package/src/adoption/plan.js +12 -703
- package/src/agent-ops/query-builders/auth.js +375 -0
- package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
- package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
- package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
- package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
- package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
- package/src/agent-ops/query-builders/change-risk.js +25 -0
- package/src/agent-ops/query-builders/common.js +149 -0
- package/src/agent-ops/query-builders/maintained-risk.js +539 -0
- package/src/agent-ops/query-builders/maintained-shared.js +120 -0
- package/src/agent-ops/query-builders/multi-agent.js +547 -0
- package/src/agent-ops/query-builders/projection-impacts.js +514 -0
- package/src/agent-ops/query-builders/work-packets.js +417 -0
- package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
- package/src/agent-ops/query-builders/workflow-context.js +398 -0
- package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
- package/src/agent-ops/query-builders/workflow-presets.js +341 -0
- package/src/agent-ops/query-builders.d.ts +26 -26
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/catalog/constants.js +10 -0
- package/src/catalog/copy.js +60 -0
- package/src/catalog/diagnostics.js +15 -0
- package/src/catalog/entries.js +42 -0
- package/src/catalog/files.js +67 -0
- package/src/catalog/provenance.js +122 -0
- package/src/catalog/source.js +150 -0
- package/src/catalog/validation.js +252 -0
- package/src/catalog.d.ts +2 -0
- package/src/catalog.js +18 -746
- package/src/cli/commands/catalog/check.js +31 -0
- package/src/cli/commands/catalog/copy.js +59 -0
- package/src/cli/commands/catalog/doctor.js +248 -0
- package/src/cli/commands/catalog/help.js +21 -0
- package/src/cli/commands/catalog/list.js +52 -0
- package/src/cli/commands/catalog/runner.js +92 -0
- package/src/cli/commands/catalog/shared.js +17 -0
- package/src/cli/commands/catalog/show.js +134 -0
- package/src/cli/commands/catalog.js +30 -615
- package/src/cli/commands/generator-policy/package-info.js +162 -0
- package/src/cli/commands/generator-policy/payloads.js +372 -0
- package/src/cli/commands/generator-policy/printers.js +159 -0
- package/src/cli/commands/generator-policy/runner.js +81 -0
- package/src/cli/commands/generator-policy/shared.js +39 -0
- package/src/cli/commands/generator-policy.js +15 -783
- package/src/cli/commands/import/adopt.js +170 -0
- package/src/cli/commands/import/check.js +91 -0
- package/src/cli/commands/import/diff.js +84 -0
- package/src/cli/commands/import/help.js +47 -0
- package/src/cli/commands/import/paths.js +277 -0
- package/src/cli/commands/import/plan.js +284 -0
- package/src/cli/commands/import/refresh.js +470 -0
- package/src/cli/commands/import/status-history.js +196 -0
- package/src/cli/commands/import/workspace.js +230 -0
- package/src/cli/commands/import.js +33 -1732
- package/src/cli/commands/package/constants.js +17 -0
- package/src/cli/commands/package/doctor.js +240 -0
- package/src/cli/commands/package/help.js +27 -0
- package/src/cli/commands/package/lockfile.js +135 -0
- package/src/cli/commands/package/npm.js +97 -0
- package/src/cli/commands/package/reporting.js +35 -0
- package/src/cli/commands/package/runner.js +33 -0
- package/src/cli/commands/package/shared.js +9 -0
- package/src/cli/commands/package/update-cli.js +252 -0
- package/src/cli/commands/package/versions.js +35 -0
- package/src/cli/commands/package.js +29 -813
- package/src/cli/commands/query/change-plan.js +68 -0
- package/src/cli/commands/query/definitions.js +202 -0
- package/src/cli/commands/query/import-adopt.js +121 -0
- package/src/cli/commands/query/runner/artifacts.js +102 -0
- package/src/cli/commands/query/runner/boundaries.js +211 -0
- package/src/cli/commands/query/runner/change.js +182 -0
- package/src/cli/commands/query/runner/import-adopt.js +111 -0
- package/src/cli/commands/query/runner/index.js +31 -0
- package/src/cli/commands/query/runner/output.js +12 -0
- package/src/cli/commands/query/runner/workflow.js +241 -0
- package/src/cli/commands/query/runner.js +3 -0
- package/src/cli/commands/query/workflow-context.js +5 -0
- package/src/cli/commands/query/workspace.js +274 -0
- package/src/cli/commands/query.js +9 -1300
- package/src/cli/commands/template/baseline.js +100 -0
- package/src/cli/commands/template/check.js +466 -0
- package/src/cli/commands/template/constants.js +8 -0
- package/src/cli/commands/template/diagnostics.js +26 -0
- package/src/cli/commands/template/help.js +28 -0
- package/src/cli/commands/template/lifecycle.js +404 -0
- package/src/cli/commands/template/list-show.js +287 -0
- package/src/cli/commands/template/policy.js +422 -0
- package/src/cli/commands/template/shared.js +127 -0
- package/src/cli/commands/template/updates.js +352 -0
- package/src/cli/commands/template.js +41 -2143
- package/src/generator/api/contracts.js +497 -0
- package/src/generator/api/metadata.js +221 -0
- package/src/generator/api/openapi.js +559 -0
- package/src/generator/api/schema.js +124 -0
- package/src/generator/api/types.d.ts +98 -0
- package/src/generator/api.js +3 -1195
- package/src/generator/context/shared/domain-sdlc.js +282 -0
- package/src/generator/context/shared/maintained-boundary.js +665 -0
- package/src/generator/context/shared/metrics.js +85 -0
- package/src/generator/context/shared/primitives.js +64 -0
- package/src/generator/context/shared/relationships.js +453 -0
- package/src/generator/context/shared/summaries.js +263 -0
- package/src/generator/context/shared/types.d.ts +207 -0
- package/src/generator/context/shared.d.ts +42 -0
- package/src/generator/context/shared.js +80 -1390
- package/src/generator/context/slice/core.js +397 -0
- package/src/generator/context/slice/sdlc.js +417 -0
- package/src/generator/context/slice/ui-packets.js +183 -0
- package/src/generator/context/slice.js +2 -859
- package/src/generator/registry/index.js +507 -0
- package/src/generator/registry.js +18 -504
- package/src/generator/runtime/environment/index.js +666 -0
- package/src/generator/runtime/environment.js +4 -666
- package/src/generator/runtime/runtime-check/index.js +554 -0
- package/src/generator/runtime/runtime-check.js +4 -554
- package/src/generator/runtime/shared/index.js +572 -0
- package/src/generator/runtime/shared.js +19 -570
- package/src/generator/shared.d.ts +2 -0
- package/src/generator/surfaces/shared.d.ts +3 -0
- package/src/generator/widget-conformance/behavior-report.js +258 -0
- package/src/generator/widget-conformance/checks.js +371 -0
- package/src/generator/widget-conformance/projection-context.js +200 -0
- package/src/generator/widget-conformance/report.js +166 -0
- package/src/generator/widget-conformance/types.d.ts +121 -0
- package/src/generator/widget-conformance.js +3 -824
- package/src/import/core/context.d.ts +3 -0
- package/src/import/core/contracts.d.ts +1 -0
- package/src/import/core/registry.d.ts +4 -0
- package/src/import/core/runner/candidates.js +217 -0
- package/src/import/core/runner/options.js +22 -0
- package/src/import/core/runner/reports.js +50 -0
- package/src/import/core/runner/run.js +79 -0
- package/src/import/core/runner/tracks.js +150 -0
- package/src/import/core/runner/ui-drafts.js +337 -0
- package/src/import/core/runner.js +3 -698
- package/src/import/core/shared/api-routes.js +221 -0
- package/src/import/core/shared/candidates.js +97 -0
- package/src/import/core/shared/files.js +177 -0
- package/src/import/core/shared/next-app.js +389 -0
- package/src/import/core/shared/types.d.ts +51 -0
- package/src/import/core/shared/ui-routes.js +230 -0
- package/src/import/core/shared.js +60 -861
- package/src/new-project/constants.js +128 -0
- package/src/new-project/create.js +83 -0
- package/src/new-project/json.js +28 -0
- package/src/new-project/metadata.js +96 -0
- package/src/new-project/package-spec.js +161 -0
- package/src/new-project/project-files.js +348 -0
- package/src/new-project/template-policy.js +269 -0
- package/src/new-project/template-resolution.js +368 -0
- package/src/new-project/template-snapshots.js +430 -0
- package/src/new-project/template-updates.js +512 -0
- package/src/new-project/types.d.ts +83 -0
- package/src/new-project.js +6 -2277
- package/src/parser.d.ts +87 -1
- package/src/parser.js +118 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -561
- package/src/resolver/enrich/acceptance-criterion.js +2 -0
- package/src/resolver/enrich/bug.js +2 -0
- package/src/resolver/enrich/pitch.js +2 -0
- package/src/resolver/enrich/requirement.js +2 -0
- package/src/resolver/enrich/task.js +2 -0
- package/src/resolver/index.js +19 -2089
- package/src/resolver/normalize.js +384 -1
- package/src/resolver/plans.js +168 -0
- package/src/resolver/projections-api.js +494 -0
- package/src/resolver/projections-db.js +133 -0
- package/src/resolver/projections-ui.js +317 -0
- package/src/resolver/shapes.js +251 -0
- package/src/resolver/shared.js +278 -0
- package/src/resolver/widgets.js +132 -0
- package/src/template-trust/constants.js +62 -0
- package/src/template-trust/content.js +258 -0
- package/src/template-trust/diff.js +92 -0
- package/src/template-trust/policy.js +61 -0
- package/src/template-trust/record.js +90 -0
- package/src/template-trust/status.js +182 -0
- package/src/template-trust.js +24 -687
- package/src/text-helpers.d.ts +1 -0
- package/src/topogram-types.d.ts +69 -0
- package/src/validator/common.js +488 -0
- package/src/validator/data-model.js +237 -0
- package/src/validator/docs.js +167 -0
- package/src/validator/expressions.js +146 -1
- package/src/validator/index.d.ts +23 -0
- package/src/validator/index.js +32 -3585
- package/src/validator/kinds.d.ts +41 -0
- package/src/validator/kinds.js +2 -0
- package/src/validator/model-helpers.js +46 -0
- package/src/validator/per-kind/acceptance-criterion.js +5 -0
- package/src/validator/per-kind/bug.js +6 -0
- package/src/validator/per-kind/domain.js +15 -2
- package/src/validator/per-kind/pitch.js +7 -0
- package/src/validator/per-kind/requirement.js +5 -0
- package/src/validator/per-kind/task.js +7 -0
- package/src/validator/per-kind/widget.js +14 -0
- package/src/validator/projections/api-http-async.js +410 -0
- package/src/validator/projections/api-http-authz.js +88 -0
- package/src/validator/projections/api-http-core.js +205 -0
- package/src/validator/projections/api-http-policies.js +339 -0
- package/src/validator/projections/api-http-responses.js +233 -0
- package/src/validator/projections/api-http.js +44 -0
- package/src/validator/projections/db.js +353 -0
- package/src/validator/projections/generator-defaults.js +45 -0
- package/src/validator/projections/helpers.js +87 -0
- package/src/validator/projections/ui-helpers.js +214 -0
- package/src/validator/projections/ui-navigation.js +344 -0
- package/src/validator/projections/ui-structure.js +364 -0
- package/src/validator/projections/ui-widgets.js +493 -0
- package/src/validator/projections/ui.js +46 -0
- package/src/validator/registry.js +48 -1
- package/src/validator/utils.d.ts +20 -0
- package/src/validator/utils.js +115 -12
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/import-app/api/collect.js +221 -0
- package/src/workflows/import-app/api/openapi.js +257 -0
- package/src/workflows/import-app/api/routes.js +327 -0
- package/src/workflows/import-app/api/sources.js +22 -0
- package/src/workflows/import-app/api.js +2 -797
- package/src/workflows/reconcile/adoption-plan/build.js +208 -0
- package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
- package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
- package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
- package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
- package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
- package/src/workflows/reconcile/adoption-plan.js +30 -740
- package/src/workflows/reconcile/auth/closures.js +115 -0
- package/src/workflows/reconcile/auth/formatters.js +142 -0
- package/src/workflows/reconcile/auth/inference.js +330 -0
- package/src/workflows/reconcile/auth/roles.js +122 -0
- package/src/workflows/reconcile/auth.js +35 -690
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +12 -598
- package/src/workflows/reconcile/canonical-surface.js +1 -1
- package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
- package/src/workflows/reconcile/impacts/indexes.js +101 -0
- package/src/workflows/reconcile/impacts/patches.js +252 -0
- package/src/workflows/reconcile/impacts/reports.js +80 -0
- package/src/workflows/reconcile/impacts.js +14 -623
- package/src/workspace-docs.d.ts +29 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
blockSymbolItems,
|
|
5
|
+
getFieldValue,
|
|
6
|
+
pushError,
|
|
7
|
+
symbolValues
|
|
8
|
+
} from "../utils.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveShapeBaseFieldNames,
|
|
11
|
+
statementFieldNames
|
|
12
|
+
} from "../model-helpers.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {TopogramRegistry} registry
|
|
16
|
+
* @param {string} capabilityId
|
|
17
|
+
* @param {string} direction
|
|
18
|
+
* @returns {Set<string>}
|
|
19
|
+
*/
|
|
20
|
+
export function resolveCapabilityContractFields(registry, capabilityId, direction) {
|
|
21
|
+
const capability = registry.get(capabilityId);
|
|
22
|
+
if (!capability || capability.kind !== "capability") {
|
|
23
|
+
return new Set();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const refsField = direction === "input" ? getFieldValue(capability, "input") : getFieldValue(capability, "output");
|
|
27
|
+
const shapeId = symbolValues(refsField)[0];
|
|
28
|
+
if (!shapeId) {
|
|
29
|
+
return new Set();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const shape = registry.get(shapeId);
|
|
33
|
+
if (!shape || shape.kind !== "shape") {
|
|
34
|
+
return new Set();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const explicitFields = statementFieldNames(shape);
|
|
38
|
+
if (explicitFields.length > 0) {
|
|
39
|
+
return new Set(explicitFields);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return new Set(resolveShapeBaseFieldNames(shape, registry));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {TopogramRegistry} registry
|
|
47
|
+
* @param {string} capabilityId
|
|
48
|
+
* @returns {TopogramStatement | null}
|
|
49
|
+
*/
|
|
50
|
+
export function resolveCapabilityOutputShape(registry, capabilityId) {
|
|
51
|
+
const capability = registry.get(capabilityId);
|
|
52
|
+
if (!capability || capability.kind !== "capability") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const shapeId = symbolValues(getFieldValue(capability, "output"))[0];
|
|
57
|
+
const shape = shapeId ? registry.get(shapeId) : null;
|
|
58
|
+
return shape?.kind === "shape" ? shape : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {string[]} tokens
|
|
63
|
+
* @param {number} startIndex
|
|
64
|
+
* @param {ValidationErrors} errors
|
|
65
|
+
* @param {TopogramStatement} statement
|
|
66
|
+
* @param {TopogramBlockEntry} entry
|
|
67
|
+
* @param {string} context
|
|
68
|
+
* @returns {Map<string, string>}
|
|
69
|
+
*/
|
|
70
|
+
export function parseUiDirectiveMap(tokens, startIndex, errors, statement, entry, context) {
|
|
71
|
+
const directives = new Map();
|
|
72
|
+
|
|
73
|
+
for (let i = startIndex; i < tokens.length; i += 2) {
|
|
74
|
+
const key = tokens[i];
|
|
75
|
+
const value = tokens[i + 1];
|
|
76
|
+
if (!key) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!value) {
|
|
80
|
+
pushError(errors, `Projection ${statement.id} ${context} is missing a value for '${key}'`, entry.loc);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
directives.set(key, value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return directives;
|
|
87
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
blockEntries,
|
|
5
|
+
blockSymbolItems,
|
|
6
|
+
getFieldValue,
|
|
7
|
+
symbolValues
|
|
8
|
+
} from "../utils.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveShapeBaseFieldNames,
|
|
11
|
+
statementFieldNames
|
|
12
|
+
} from "../model-helpers.js";
|
|
13
|
+
import {
|
|
14
|
+
parseUiDirectiveMap,
|
|
15
|
+
resolveCapabilityContractFields
|
|
16
|
+
} from "./helpers.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {TopogramStatement} statement
|
|
20
|
+
* @param {TopogramFieldMap} fieldMap
|
|
21
|
+
* @returns {Map<string, TopogramBlockEntry>}
|
|
22
|
+
*/
|
|
23
|
+
export function collectProjectionUiScreens(statement, fieldMap) {
|
|
24
|
+
const screensField = fieldMap.get("screens")?.[0];
|
|
25
|
+
if (!screensField || screensField.value.type !== "block") {
|
|
26
|
+
return new Map();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const screens = new Map();
|
|
30
|
+
for (const entry of screensField.value.entries) {
|
|
31
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
32
|
+
if (tokens[0] === "screen" && tokens[1]) {
|
|
33
|
+
screens.set(tokens[1], entry);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return screens;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {TopogramRegistry} registry
|
|
41
|
+
* @param {TopogramBlockEntry} screenEntry
|
|
42
|
+
* @param {TopogramStatement} statement
|
|
43
|
+
* @returns {Set<string>}
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
export function resolveProjectionUiScreenFieldNames(registry, screenEntry, statement) {
|
|
47
|
+
const tokens = blockSymbolItems(screenEntry).map((item) => item.value);
|
|
48
|
+
const directives = parseUiDirectiveMap(tokens, 2, [], statement, screenEntry, "");
|
|
49
|
+
const kind = directives.get("kind");
|
|
50
|
+
|
|
51
|
+
if (kind === "form") {
|
|
52
|
+
const shapeId = directives.get("input_shape");
|
|
53
|
+
const shape = shapeId ? registry.get(shapeId) : null;
|
|
54
|
+
if (!shape || shape.kind !== "shape") {
|
|
55
|
+
return new Set();
|
|
56
|
+
}
|
|
57
|
+
const explicitFields = statementFieldNames(shape);
|
|
58
|
+
return new Set(explicitFields.length > 0 ? explicitFields : resolveShapeBaseFieldNames(shape, registry));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (kind === "list") {
|
|
62
|
+
const loadCapabilityId = directives.get("load");
|
|
63
|
+
return loadCapabilityId ? resolveCapabilityContractFields(registry, loadCapabilityId, "input") : new Set();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (kind === "detail" || kind === "job_status") {
|
|
67
|
+
const loadCapabilityId = directives.get("load");
|
|
68
|
+
return loadCapabilityId ? resolveCapabilityContractFields(registry, loadCapabilityId, "output") : new Set();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return new Set();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {TopogramStatement} statement
|
|
76
|
+
* @returns {Set<string>}
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
export function screenIdsFromProjectionStatement(statement) {
|
|
80
|
+
const screens = new Set();
|
|
81
|
+
for (const entry of blockEntries(getFieldValue(statement, "screens"))) {
|
|
82
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
83
|
+
if (tokens[0] === "screen" && tokens[1]) {
|
|
84
|
+
screens.add(tokens[1]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return screens;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {TopogramStatement} statement
|
|
92
|
+
* @param {TopogramFieldMap} fieldMap
|
|
93
|
+
* @param {TopogramRegistry} registry
|
|
94
|
+
* @returns {Set<string>}
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
export function collectAvailableUiScreenIds(statement, fieldMap, registry) {
|
|
98
|
+
const available = new Set(collectProjectionUiScreens(statement, fieldMap).keys());
|
|
99
|
+
for (const targetId of symbolValues(getFieldValue(statement, "realizes"))) {
|
|
100
|
+
const target = registry.get(targetId);
|
|
101
|
+
if (target?.kind === "projection") {
|
|
102
|
+
for (const screenId of screenIdsFromProjectionStatement(target)) {
|
|
103
|
+
available.add(screenId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return available;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {TopogramStatement} statement
|
|
112
|
+
* @returns {Set<string>}
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
export function collectProjectionUiRegionKeys(statement) {
|
|
116
|
+
const keys = new Set();
|
|
117
|
+
for (const entry of blockEntries(getFieldValue(statement, "screen_regions"))) {
|
|
118
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
119
|
+
if (tokens[0] === "screen" && tokens[1] && tokens[2] === "region" && tokens[3]) {
|
|
120
|
+
keys.add(`${tokens[1]}:${tokens[3]}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return keys;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {TopogramStatement} statement
|
|
128
|
+
* @param {TopogramRegistry} registry
|
|
129
|
+
* @returns {Set<string>}
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
export function collectAvailableUiRegionKeys(statement, registry) {
|
|
133
|
+
const available = collectProjectionUiRegionKeys(statement);
|
|
134
|
+
for (const targetId of symbolValues(getFieldValue(statement, "realizes"))) {
|
|
135
|
+
const target = registry.get(targetId);
|
|
136
|
+
if (target?.kind === "projection") {
|
|
137
|
+
for (const key of collectProjectionUiRegionKeys(target)) {
|
|
138
|
+
available.add(key);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return available;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @param {TopogramStatement} statement
|
|
147
|
+
* @returns {Map<string, string>}
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
export function collectProjectionUiRegionPatterns(statement) {
|
|
151
|
+
const patterns = new Map();
|
|
152
|
+
for (const entry of blockEntries(getFieldValue(statement, "screen_regions"))) {
|
|
153
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
154
|
+
if (tokens[0] !== "screen" || !tokens[1] || tokens[2] !== "region" || !tokens[3]) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
for (let i = 4; i < tokens.length; i += 2) {
|
|
158
|
+
if (tokens[i] === "pattern" && tokens[i + 1]) {
|
|
159
|
+
patterns.set(`${tokens[1]}:${tokens[3]}`, tokens[i + 1]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return patterns;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @param {TopogramStatement} statement
|
|
168
|
+
* @param {TopogramRegistry} registry
|
|
169
|
+
* @returns {Map<string, string>}
|
|
170
|
+
*/
|
|
171
|
+
|
|
172
|
+
export function collectAvailableUiRegionPatterns(statement, registry) {
|
|
173
|
+
const patterns = collectProjectionUiRegionPatterns(statement);
|
|
174
|
+
for (const targetId of symbolValues(getFieldValue(statement, "realizes"))) {
|
|
175
|
+
const target = registry.get(targetId);
|
|
176
|
+
if (target?.kind !== "projection") {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
for (const [key, pattern] of collectProjectionUiRegionPatterns(target)) {
|
|
180
|
+
if (!patterns.has(key)) {
|
|
181
|
+
patterns.set(key, pattern);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return patterns;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {Map<string, string>} directives
|
|
190
|
+
* @param {string} key
|
|
191
|
+
* @returns {string}
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
export function directiveValue(directives, key) {
|
|
195
|
+
return directives.get(key) || "";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const SHARED_UI_SEMANTIC_BLOCKS = [
|
|
199
|
+
"screens",
|
|
200
|
+
"collection_views",
|
|
201
|
+
"screen_actions",
|
|
202
|
+
"visibility_rules",
|
|
203
|
+
"field_lookups",
|
|
204
|
+
"app_shell",
|
|
205
|
+
"navigation",
|
|
206
|
+
"screen_regions"
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @param {ValidationErrors} errors
|
|
211
|
+
* @param {TopogramStatement} statement
|
|
212
|
+
* @param {TopogramFieldMap} fieldMap
|
|
213
|
+
* @returns {void}
|
|
214
|
+
*/
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
IDENTIFIER_PATTERN,
|
|
5
|
+
UI_APP_SHELL_KINDS,
|
|
6
|
+
UI_COLLECTION_PRESENTATIONS,
|
|
7
|
+
UI_DESIGN_ACCESSIBILITY_VALUES,
|
|
8
|
+
UI_DESIGN_ACTION_ROLES,
|
|
9
|
+
UI_DESIGN_COLOR_ROLES,
|
|
10
|
+
UI_DESIGN_DENSITIES,
|
|
11
|
+
UI_DESIGN_RADIUS_SCALES,
|
|
12
|
+
UI_DESIGN_TONES,
|
|
13
|
+
UI_DESIGN_TYPOGRAPHY_ROLES,
|
|
14
|
+
UI_NAVIGATION_PATTERNS,
|
|
15
|
+
UI_PATTERN_KINDS,
|
|
16
|
+
UI_REGION_KINDS,
|
|
17
|
+
UI_SCREEN_KINDS,
|
|
18
|
+
UI_STATE_KINDS,
|
|
19
|
+
UI_WINDOWING_MODES
|
|
20
|
+
} from "../kinds.js";
|
|
21
|
+
import {
|
|
22
|
+
blockSymbolItems,
|
|
23
|
+
getFieldValue,
|
|
24
|
+
pushError,
|
|
25
|
+
symbolValue,
|
|
26
|
+
symbolValues
|
|
27
|
+
} from "../utils.js";
|
|
28
|
+
import {
|
|
29
|
+
collectAvailableUiScreenIds,
|
|
30
|
+
directiveValue
|
|
31
|
+
} from "./ui-helpers.js";
|
|
32
|
+
import { parseUiDirectiveMap } from "./helpers.js";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {ValidationErrors} errors
|
|
36
|
+
* @param {TopogramStatement} statement
|
|
37
|
+
* @param {TopogramFieldMap} fieldMap
|
|
38
|
+
* @param {TopogramRegistry} registry
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*/
|
|
41
|
+
export function validateProjectionUiNavigation(errors, statement, fieldMap, registry) {
|
|
42
|
+
if (statement.kind !== "projection") {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const navigationField = fieldMap.get("navigation")?.[0];
|
|
47
|
+
if (!navigationField || navigationField.value.type !== "block") {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
|
|
52
|
+
const groups = new Set();
|
|
53
|
+
|
|
54
|
+
for (const entry of navigationField.value.entries) {
|
|
55
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
56
|
+
const [targetKind, targetId] = tokens;
|
|
57
|
+
|
|
58
|
+
if (targetKind === "group") {
|
|
59
|
+
if (!targetId || !IDENTIFIER_PATTERN.test(targetId)) {
|
|
60
|
+
pushError(errors, `Projection ${statement.id} navigation group entries must include a valid group id`, entry.loc);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
groups.add(targetId);
|
|
64
|
+
const directives = parseUiDirectiveMap(tokens, 2, errors, statement, entry, `navigation group '${targetId}'`);
|
|
65
|
+
for (const key of directives.keys()) {
|
|
66
|
+
if (!["label", "placement", "icon", "order", "pattern"].includes(key)) {
|
|
67
|
+
pushError(errors, `Projection ${statement.id} navigation group '${targetId}' has unknown directive '${key}'`, entry.loc);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (directives.has("placement") && !["primary", "secondary", "utility"].includes(directiveValue(directives, "placement"))) {
|
|
71
|
+
pushError(errors, `Projection ${statement.id} navigation group '${targetId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
|
|
72
|
+
}
|
|
73
|
+
if (directives.has("pattern") && !UI_NAVIGATION_PATTERNS.has(directiveValue(directives, "pattern"))) {
|
|
74
|
+
pushError(errors, `Projection ${statement.id} navigation group '${targetId}' has invalid pattern '${directives.get("pattern")}'`, entry.loc);
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (targetKind === "screen") {
|
|
80
|
+
if (!availableScreens.has(targetId)) {
|
|
81
|
+
pushError(errors, `Projection ${statement.id} navigation references unknown screen '${targetId}'`, entry.loc);
|
|
82
|
+
}
|
|
83
|
+
const directives = parseUiDirectiveMap(tokens, 2, errors, statement, entry, `navigation screen '${targetId}'`);
|
|
84
|
+
for (const key of directives.keys()) {
|
|
85
|
+
if (!["group", "label", "order", "visible", "default", "breadcrumb", "sitemap", "placement", "pattern"].includes(key)) {
|
|
86
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has unknown directive '${key}'`, entry.loc);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (directives.has("visible") && !["true", "false"].includes(directiveValue(directives, "visible"))) {
|
|
90
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid visible '${directives.get("visible")}'`, entry.loc);
|
|
91
|
+
}
|
|
92
|
+
if (directives.has("default") && !["true", "false"].includes(directiveValue(directives, "default"))) {
|
|
93
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid default '${directives.get("default")}'`, entry.loc);
|
|
94
|
+
}
|
|
95
|
+
if (directives.has("placement") && !["primary", "secondary", "utility"].includes(directiveValue(directives, "placement"))) {
|
|
96
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
|
|
97
|
+
}
|
|
98
|
+
if (directives.has("sitemap") && !["include", "exclude"].includes(directiveValue(directives, "sitemap"))) {
|
|
99
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid sitemap '${directives.get("sitemap")}'`, entry.loc);
|
|
100
|
+
}
|
|
101
|
+
if (directives.has("pattern") && !UI_NAVIGATION_PATTERNS.has(directiveValue(directives, "pattern"))) {
|
|
102
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' has invalid pattern '${directives.get("pattern")}'`, entry.loc);
|
|
103
|
+
}
|
|
104
|
+
const breadcrumb = directives.get("breadcrumb");
|
|
105
|
+
if (breadcrumb && breadcrumb !== "none" && !availableScreens.has(breadcrumb)) {
|
|
106
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${targetId}' references unknown breadcrumb screen '${breadcrumb}'`, entry.loc);
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pushError(errors, `Projection ${statement.id} navigation entries must start with 'group' or 'screen'`, entry.loc);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const entry of navigationField.value.entries) {
|
|
115
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
116
|
+
if (tokens[0] !== "screen") {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const directives = parseUiDirectiveMap(tokens, 2, [], statement, entry, "");
|
|
120
|
+
if (directives.has("group") && !groups.has(directives.get("group"))) {
|
|
121
|
+
pushError(errors, `Projection ${statement.id} navigation screen '${tokens[1]}' references unknown group '${directives.get("group")}'`, entry.loc);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {ValidationErrors} errors
|
|
128
|
+
* @param {TopogramStatement} statement
|
|
129
|
+
* @param {TopogramFieldMap} fieldMap
|
|
130
|
+
* @param {TopogramRegistry} registry
|
|
131
|
+
* @returns {void}
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
export function validateProjectionUiScreenRegions(errors, statement, fieldMap, registry) {
|
|
135
|
+
if (statement.kind !== "projection") {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const regionField = fieldMap.get("screen_regions")?.[0];
|
|
140
|
+
if (!regionField || regionField.value.type !== "block") {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
|
|
145
|
+
for (const entry of regionField.value.entries) {
|
|
146
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
147
|
+
const [keyword, screenId, regionKeyword, regionName] = tokens;
|
|
148
|
+
|
|
149
|
+
if (keyword !== "screen") {
|
|
150
|
+
pushError(errors, `Projection ${statement.id} screen_regions entries must start with 'screen'`, entry.loc);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!availableScreens.has(screenId)) {
|
|
154
|
+
pushError(errors, `Projection ${statement.id} screen_regions references unknown screen '${screenId}'`, entry.loc);
|
|
155
|
+
}
|
|
156
|
+
if (regionKeyword !== "region") {
|
|
157
|
+
pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' must use 'region'`, entry.loc);
|
|
158
|
+
}
|
|
159
|
+
if (!UI_REGION_KINDS.has(regionName || "")) {
|
|
160
|
+
pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid region '${regionName}'`, entry.loc);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const directives = parseUiDirectiveMap(tokens, 4, errors, statement, entry, `screen_regions for '${screenId}'`);
|
|
164
|
+
for (const key of directives.keys()) {
|
|
165
|
+
if (!["pattern", "placement", "title", "state", "variant"].includes(key)) {
|
|
166
|
+
pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has unknown directive '${key}'`, entry.loc);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (directives.has("pattern") && !UI_PATTERN_KINDS.has(directiveValue(directives, "pattern"))) {
|
|
170
|
+
pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid pattern '${directives.get("pattern")}'`, entry.loc);
|
|
171
|
+
}
|
|
172
|
+
if (directives.has("placement") && !["primary", "secondary", "supporting"].includes(directiveValue(directives, "placement"))) {
|
|
173
|
+
pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid placement '${directives.get("placement")}'`, entry.loc);
|
|
174
|
+
}
|
|
175
|
+
if (directives.has("state") && !UI_STATE_KINDS.has(directiveValue(directives, "state"))) {
|
|
176
|
+
pushError(errors, `Projection ${statement.id} screen_regions for '${screenId}' has invalid state '${directives.get("state")}'`, entry.loc);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {ValidationErrors} errors
|
|
183
|
+
* @param {TopogramStatement} statement
|
|
184
|
+
* @param {TopogramFieldMap} fieldMap
|
|
185
|
+
* @param {TopogramRegistry} registry
|
|
186
|
+
* @returns {void}
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
export function validateProjectionUiRoutes(errors, statement, fieldMap, registry) {
|
|
190
|
+
if (statement.kind !== "projection") {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const routesField = fieldMap.get("screen_routes")?.[0];
|
|
195
|
+
if (!routesField || routesField.value.type !== "block") {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
|
|
200
|
+
const seenPaths = new Set();
|
|
201
|
+
const projectionType = symbolValue(getFieldValue(statement, "type"));
|
|
202
|
+
|
|
203
|
+
for (const entry of routesField.value.entries) {
|
|
204
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
205
|
+
const [keyword, screenId, pathKeyword, routePath] = tokens;
|
|
206
|
+
|
|
207
|
+
if (keyword !== "screen") {
|
|
208
|
+
pushError(errors, `Projection ${statement.id} screen_routes entries must start with 'screen'`, entry.loc);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (!availableScreens.has(screenId)) {
|
|
212
|
+
pushError(errors, `Projection ${statement.id} screen_routes references unknown screen '${screenId}'`, entry.loc);
|
|
213
|
+
}
|
|
214
|
+
if (pathKeyword !== "path") {
|
|
215
|
+
pushError(errors, `Projection ${statement.id} screen_routes for '${screenId}' must use 'path'`, entry.loc);
|
|
216
|
+
}
|
|
217
|
+
if (!routePath) {
|
|
218
|
+
pushError(errors, `Projection ${statement.id} screen_routes for '${screenId}' must include a path`, entry.loc);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if ((projectionType === "web_surface" || projectionType === "ios_surface") && !routePath.startsWith("/")) {
|
|
222
|
+
pushError(errors, `Projection ${statement.id} screen_routes for '${screenId}' must use an absolute path`, entry.loc);
|
|
223
|
+
}
|
|
224
|
+
if (seenPaths.has(routePath)) {
|
|
225
|
+
pushError(errors, `Projection ${statement.id} screen_routes has duplicate path '${routePath}'`, entry.loc);
|
|
226
|
+
}
|
|
227
|
+
seenPaths.add(routePath);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {ValidationErrors} errors
|
|
233
|
+
* @param {TopogramStatement} statement
|
|
234
|
+
* @param {TopogramFieldMap} fieldMap
|
|
235
|
+
* @param {TopogramRegistry} registry
|
|
236
|
+
* @param {string} surfaceBlockKey
|
|
237
|
+
* @param {string} expectedProjectionType
|
|
238
|
+
* @returns {void}
|
|
239
|
+
*/
|
|
240
|
+
|
|
241
|
+
export function validateProjectionUiSurfaceHints(errors, statement, fieldMap, registry, surfaceBlockKey, expectedProjectionType) {
|
|
242
|
+
if (statement.kind !== "projection") {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const surfaceField = fieldMap.get(surfaceBlockKey)?.[0];
|
|
247
|
+
if (!surfaceField || surfaceField.value.type !== "block") {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const projectionType = symbolValue(getFieldValue(statement, "type"));
|
|
252
|
+
if (projectionType !== expectedProjectionType) {
|
|
253
|
+
pushError(errors, `Projection ${statement.id} may only use '${surfaceBlockKey}' when projection type is '${expectedProjectionType}'`, surfaceField.loc);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
|
|
258
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
259
|
+
for (const entry of surfaceField.value.entries) {
|
|
260
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
261
|
+
const [targetKind, targetId, directive, value] = tokens;
|
|
262
|
+
|
|
263
|
+
if (targetKind === "screen") {
|
|
264
|
+
if (!availableScreens.has(targetId)) {
|
|
265
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} references unknown screen '${targetId}'`, entry.loc);
|
|
266
|
+
}
|
|
267
|
+
if (!["layout", "desktop_variant", "mobile_variant", "present", "shell", "collection", "breadcrumbs", "state_style"].includes(directive || "")) {
|
|
268
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has unknown directive '${directive}'`, entry.loc);
|
|
269
|
+
}
|
|
270
|
+
if (directive === "desktop_variant" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
|
|
271
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid desktop_variant '${value}'`, entry.loc);
|
|
272
|
+
}
|
|
273
|
+
if (directive === "mobile_variant" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
|
|
274
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid mobile_variant '${value}'`, entry.loc);
|
|
275
|
+
}
|
|
276
|
+
if (directive === "collection" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
|
|
277
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid collection '${value}'`, entry.loc);
|
|
278
|
+
}
|
|
279
|
+
if (directive === "shell" && !["topbar", "sidebar", "dual_nav", "workspace", "wizard", "bottom_tabs", "split_view", "menu_bar"].includes(value || "")) {
|
|
280
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid shell '${value}'`, entry.loc);
|
|
281
|
+
}
|
|
282
|
+
if (directive === "present" && !["page", "modal", "drawer", "sheet", "bottom_sheet", "popover"].includes(value || "")) {
|
|
283
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid present '${value}'`, entry.loc);
|
|
284
|
+
}
|
|
285
|
+
if (directive === "breadcrumbs" && !["visible", "hidden"].includes(value || "")) {
|
|
286
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid breadcrumbs '${value}'`, entry.loc);
|
|
287
|
+
}
|
|
288
|
+
if (directive === "state_style" && !["inline", "panel", "full_page"].includes(value || "")) {
|
|
289
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for screen '${targetId}' has invalid state_style '${value}'`, entry.loc);
|
|
290
|
+
}
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (targetKind === "action") {
|
|
295
|
+
const capability = registry.get(targetId);
|
|
296
|
+
if (!capability) {
|
|
297
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} references missing capability '${targetId}'`, entry.loc);
|
|
298
|
+
} else if (capability.kind !== "capability") {
|
|
299
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} must reference a capability for action '${targetId}', found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
300
|
+
} else if (!realized.has(targetId)) {
|
|
301
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} action '${targetId}' must also appear in 'realizes'`, entry.loc);
|
|
302
|
+
}
|
|
303
|
+
if (!["confirm", "present", "placement"].includes(directive || "")) {
|
|
304
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has unknown directive '${directive}'`, entry.loc);
|
|
305
|
+
}
|
|
306
|
+
if (directive === "confirm" && !["modal", "inline", "sheet", "bottom_sheet", "popover"].includes(value || "")) {
|
|
307
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has invalid confirm mode '${value}'`, entry.loc);
|
|
308
|
+
}
|
|
309
|
+
if (directive === "present" && !["button", "menu_item", "split_button", "bulk_action", "drawer", "sheet", "bottom_sheet", "fab", "popover"].includes(value || "")) {
|
|
310
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has invalid present mode '${value}'`, entry.loc);
|
|
311
|
+
}
|
|
312
|
+
if (directive === "placement" && !["toolbar", "menu", "bulk", "inline", "footer"].includes(value || "")) {
|
|
313
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} for action '${targetId}' has invalid placement '${value}'`, entry.loc);
|
|
314
|
+
}
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
pushError(errors, `Projection ${statement.id} ${surfaceBlockKey} entries must start with 'screen' or 'action'`, entry.loc);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @param {ValidationErrors} errors
|
|
324
|
+
* @param {TopogramStatement} statement
|
|
325
|
+
* @param {TopogramFieldMap} fieldMap
|
|
326
|
+
* @param {TopogramRegistry} registry
|
|
327
|
+
* @returns {void}
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
export function validateProjectionUiWeb(errors, statement, fieldMap, registry) {
|
|
331
|
+
validateProjectionUiSurfaceHints(errors, statement, fieldMap, registry, "web_hints", "web_surface");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @param {ValidationErrors} errors
|
|
336
|
+
* @param {TopogramStatement} statement
|
|
337
|
+
* @param {TopogramFieldMap} fieldMap
|
|
338
|
+
* @param {TopogramRegistry} registry
|
|
339
|
+
* @returns {void}
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
export function validateProjectionUiIos(errors, statement, fieldMap, registry) {
|
|
343
|
+
validateProjectionUiSurfaceHints(errors, statement, fieldMap, registry, "ios_hints", "ios_surface");
|
|
344
|
+
}
|