@topogram/cli 0.3.63 → 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.d.ts +6 -0
- package/src/adoption/plan.js +12 -703
- package/src/adoption/reporting.d.ts +10 -0
- package/src/adoption/review-groups.d.ts +6 -0
- package/src/agent-brief.d.ts +3 -0
- package/src/agent-brief.js +495 -0
- 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 -0
- package/src/agent-ops/query-builders.js +42 -5021
- package/src/archive/archive.d.ts +2 -0
- package/src/archive/compact.d.ts +1 -0
- package/src/archive/unarchive.d.ts +1 -0
- 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 +12 -0
- package/src/catalog.js +18 -750
- package/src/cli/catalog-alias.d.ts +1 -0
- package/src/cli/command-parser.js +38 -0
- package/src/cli/command-parsers/core.js +102 -0
- package/src/cli/command-parsers/generator.js +39 -0
- package/src/cli/command-parsers/import.js +44 -0
- package/src/cli/command-parsers/legacy-workflow.js +21 -0
- package/src/cli/command-parsers/project.js +47 -0
- package/src/cli/command-parsers/sdlc.js +47 -0
- package/src/cli/command-parsers/shared.js +51 -0
- package/src/cli/command-parsers/template.js +48 -0
- package/src/cli/commands/agent.js +47 -0
- 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 +32 -0
- package/src/cli/commands/check.js +268 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/emit.js +149 -0
- package/src/cli/commands/generate.js +96 -0
- 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 +17 -0
- package/src/cli/commands/generator.js +443 -0
- 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-runner.js +157 -0
- package/src/cli/commands/import.js +35 -0
- package/src/cli/commands/inspect.js +55 -0
- package/src/cli/commands/new.js +94 -0
- 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 +31 -0
- 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 +11 -0
- package/src/cli/commands/release-rollout.js +257 -0
- package/src/cli/commands/release-shared.js +528 -0
- package/src/cli/commands/release-status.js +429 -0
- package/src/cli/commands/release.js +107 -0
- package/src/cli/commands/sdlc.js +168 -0
- package/src/cli/commands/setup.js +76 -0
- package/src/cli/commands/source.js +291 -0
- 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-runner.js +198 -0
- package/src/cli/commands/template.js +43 -0
- package/src/cli/commands/trust.js +219 -0
- package/src/cli/commands/version.js +40 -0
- package/src/cli/commands/widget.js +168 -0
- package/src/cli/commands/workflow.js +63 -0
- package/src/cli/dispatcher.js +392 -0
- package/src/cli/help-dispatch.js +188 -0
- package/src/cli/help.js +296 -0
- package/src/cli/migration-guidance.js +59 -0
- package/src/cli/options.js +96 -0
- package/src/cli/output-safety.js +107 -0
- package/src/cli/path-normalization.js +29 -0
- package/src/cli.js +47 -11711
- package/src/example-implementation.d.ts +2 -0
- package/src/format.d.ts +1 -0
- 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/check.d.ts +1 -0
- package/src/generator/context/bundle.d.ts +1 -0
- 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 +44 -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/native/parity-bundle.js +2 -1
- 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/surfaces/web/html-escape.js +22 -0
- package/src/generator/surfaces/web/react.js +10 -8
- package/src/generator/surfaces/web/sveltekit.js +7 -5
- package/src/generator/surfaces/web/vanilla.js +8 -4
- 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/generator.d.ts +2 -0
- package/src/github-client.js +520 -0
- 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 +67 -910
- package/src/import/extractors/api/flutter-dio.js +4 -8
- package/src/import/extractors/api/react-native-repository.js +4 -8
- package/src/import/index.d.ts +4 -0
- package/src/import/provenance.d.ts +4 -0
- 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 -2188
- package/src/npm-safety.js +79 -0
- package/src/parser.d.ts +87 -0
- package/src/parser.js +118 -0
- package/src/path-helpers.d.ts +1 -0
- package/src/path-helpers.js +20 -0
- package/src/policy/review-boundaries.d.ts +15 -0
- package/src/project-config/index.js +564 -0
- package/src/project-config.js +19 -560
- package/src/reconcile/docs.d.ts +8 -0
- package/src/reconcile/journeys.d.ts +1 -0
- 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/resolver.d.ts +1 -0
- package/src/runtime-support.js +29 -0
- package/src/sdlc/adopt.d.ts +1 -0
- package/src/sdlc/check.d.ts +1 -0
- package/src/sdlc/explain.d.ts +1 -0
- package/src/sdlc/release.d.ts +1 -0
- package/src/sdlc/scaffold.d.ts +1 -0
- package/src/sdlc/transition.d.ts +1 -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 +7 -0
- package/src/text-helpers.js +245 -0
- package/src/topogram-config.js +306 -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/validator.d.ts +2 -0
- package/src/widget-behavior.d.ts +1 -0
- package/src/workflows/adoption/index.js +26 -0
- package/src/workflows/docs-generate.js +262 -0
- package/src/workflows/docs-scan.js +703 -0
- package/src/workflows/docs.js +15 -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 +4 -0
- package/src/workflows/import-app/db.js +538 -0
- package/src/workflows/import-app/index.js +30 -0
- package/src/workflows/import-app/shared.js +218 -0
- package/src/workflows/import-app/ui.js +443 -0
- package/src/workflows/import-app/workflow.js +159 -0
- 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 +32 -0
- 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 +37 -0
- package/src/workflows/reconcile/bundle-core/index.js +600 -0
- package/src/workflows/reconcile/bundle-core.js +14 -0
- package/src/workflows/reconcile/bundle-shared.js +75 -0
- package/src/workflows/reconcile/candidate-model.js +477 -0
- package/src/workflows/reconcile/canonical-surface.js +264 -0
- package/src/workflows/reconcile/gap-report.js +333 -0
- package/src/workflows/reconcile/ids.js +6 -0
- 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 +16 -0
- package/src/workflows/reconcile/index.js +7 -0
- package/src/workflows/reconcile/renderers.js +461 -0
- package/src/workflows/reconcile/summary.js +90 -0
- package/src/workflows/reconcile/workflow.js +309 -0
- package/src/workflows/shared.js +189 -0
- package/src/workflows/types.d.ts +93 -0
- package/src/workflows.d.ts +1 -0
- package/src/workflows.js +10 -7652
- package/src/workspace-docs.d.ts +29 -0
|
@@ -0,0 +1,493 @@
|
|
|
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
|
+
blockEntries,
|
|
23
|
+
blockSymbolItems,
|
|
24
|
+
getFieldValue,
|
|
25
|
+
pushError,
|
|
26
|
+
symbolValue,
|
|
27
|
+
symbolValues
|
|
28
|
+
} from "../utils.js";
|
|
29
|
+
import {
|
|
30
|
+
collectAvailableUiRegionKeys,
|
|
31
|
+
collectAvailableUiRegionPatterns,
|
|
32
|
+
collectAvailableUiScreenIds,
|
|
33
|
+
collectProjectionUiScreens,
|
|
34
|
+
resolveProjectionUiScreenFieldNames
|
|
35
|
+
} from "./ui-helpers.js";
|
|
36
|
+
import {
|
|
37
|
+
resolveShapeBaseFieldNames,
|
|
38
|
+
statementFieldNames
|
|
39
|
+
} from "../model-helpers.js";
|
|
40
|
+
import {
|
|
41
|
+
parseUiDirectiveMap,
|
|
42
|
+
resolveCapabilityContractFields,
|
|
43
|
+
resolveCapabilityOutputShape
|
|
44
|
+
} from "./helpers.js";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {ValidationErrors} errors
|
|
48
|
+
* @param {TopogramStatement} statement
|
|
49
|
+
* @param {TopogramFieldMap} fieldMap
|
|
50
|
+
* @param {TopogramRegistry} registry
|
|
51
|
+
* @returns {void}
|
|
52
|
+
*/
|
|
53
|
+
export function validateProjectionUiCollections(errors, statement, fieldMap, registry) {
|
|
54
|
+
if (statement.kind !== "projection") {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const collectionsField = fieldMap.get("collection_views")?.[0];
|
|
59
|
+
if (!collectionsField || collectionsField.value.type !== "block") {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const screens = collectProjectionUiScreens(statement, fieldMap);
|
|
64
|
+
for (const entry of collectionsField.value.entries) {
|
|
65
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
66
|
+
const [keyword, screenId, operation, value, extra] = tokens;
|
|
67
|
+
|
|
68
|
+
if (keyword !== "screen") {
|
|
69
|
+
pushError(errors, `Projection ${statement.id} collection_views entries must start with 'screen'`, entry.loc);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const screenEntry = screens.get(screenId);
|
|
73
|
+
if (!screenEntry) {
|
|
74
|
+
pushError(errors, `Projection ${statement.id} collection_views references unknown screen '${screenId}'`, entry.loc);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const screenTokens = blockSymbolItems(screenEntry).map((item) => item.value);
|
|
79
|
+
const screenDirectives = parseUiDirectiveMap(screenTokens, 2, [], statement, screenEntry, "");
|
|
80
|
+
if (screenDirectives.get("kind") !== "list") {
|
|
81
|
+
pushError(errors, `Projection ${statement.id} collection_views may only target list screens, found '${screenId}'`, entry.loc);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!["filter", "search", "pagination", "sort", "group", "view", "refresh"].includes(operation)) {
|
|
85
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid operation '${operation}'`, entry.loc);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const loadCapabilityId = screenDirectives.get("load");
|
|
90
|
+
const inputFields = loadCapabilityId ? resolveCapabilityContractFields(registry, loadCapabilityId, "input") : new Set();
|
|
91
|
+
const outputShape = loadCapabilityId ? resolveCapabilityOutputShape(registry, loadCapabilityId) : null;
|
|
92
|
+
const outputFields = outputShape
|
|
93
|
+
? new Set((statementFieldNames(outputShape).length > 0 ? statementFieldNames(outputShape) : resolveShapeBaseFieldNames(outputShape, registry)))
|
|
94
|
+
: new Set();
|
|
95
|
+
|
|
96
|
+
if (operation === "filter" || operation === "search") {
|
|
97
|
+
if (!value) {
|
|
98
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' must include a field for '${operation}'`, entry.loc);
|
|
99
|
+
} else if (inputFields.size > 0 && !inputFields.has(value)) {
|
|
100
|
+
pushError(errors, `Projection ${statement.id} collection_views references unknown input field '${value}' for '${operation}' on '${screenId}'`, entry.loc);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (operation === "pagination" && !["cursor", "paged", "none"].includes(value || "")) {
|
|
105
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid pagination '${value}'`, entry.loc);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (operation === "sort") {
|
|
109
|
+
if (!value || !extra) {
|
|
110
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' must use 'sort <field> <asc|desc>'`, entry.loc);
|
|
111
|
+
} else {
|
|
112
|
+
if (!["asc", "desc"].includes(extra)) {
|
|
113
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid sort direction '${extra}'`, entry.loc);
|
|
114
|
+
}
|
|
115
|
+
if (outputFields.size > 0 && !outputFields.has(value)) {
|
|
116
|
+
pushError(errors, `Projection ${statement.id} collection_views references unknown output field '${value}' for sort on '${screenId}'`, entry.loc);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (operation === "group") {
|
|
122
|
+
if (!value) {
|
|
123
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' must include a field for 'group'`, entry.loc);
|
|
124
|
+
} else if (outputFields.size > 0 && !outputFields.has(value)) {
|
|
125
|
+
pushError(errors, `Projection ${statement.id} collection_views references unknown output field '${value}' for group on '${screenId}'`, entry.loc);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (operation === "view" && !UI_COLLECTION_PRESENTATIONS.has(value || "")) {
|
|
130
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid view '${value}'`, entry.loc);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (operation === "refresh" && !["manual", "pull_to_refresh", "auto"].includes(value || "")) {
|
|
134
|
+
pushError(errors, `Projection ${statement.id} collection_views for '${screenId}' has invalid refresh '${value}'`, entry.loc);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {ValidationErrors} errors
|
|
141
|
+
* @param {TopogramStatement} statement
|
|
142
|
+
* @param {TopogramFieldMap} fieldMap
|
|
143
|
+
* @param {TopogramRegistry} registry
|
|
144
|
+
* @returns {void}
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
export function validateProjectionUiActions(errors, statement, fieldMap, registry) {
|
|
148
|
+
if (statement.kind !== "projection") {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const actionsField = fieldMap.get("screen_actions")?.[0];
|
|
153
|
+
if (!actionsField || actionsField.value.type !== "block") {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const screens = collectProjectionUiScreens(statement, fieldMap);
|
|
158
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
159
|
+
|
|
160
|
+
for (const entry of actionsField.value.entries) {
|
|
161
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
162
|
+
const [keyword, screenId, actionKeyword, capabilityId, prominenceKeyword, prominence, placementKeyword, placement] = tokens;
|
|
163
|
+
|
|
164
|
+
if (keyword !== "screen") {
|
|
165
|
+
pushError(errors, `Projection ${statement.id} screen_actions entries must start with 'screen'`, entry.loc);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (!screens.has(screenId)) {
|
|
169
|
+
pushError(errors, `Projection ${statement.id} screen_actions references unknown screen '${screenId}'`, entry.loc);
|
|
170
|
+
}
|
|
171
|
+
if (actionKeyword !== "action") {
|
|
172
|
+
pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' must use 'action'`, entry.loc);
|
|
173
|
+
}
|
|
174
|
+
const capability = registry.get(capabilityId);
|
|
175
|
+
if (!capability) {
|
|
176
|
+
pushError(errors, `Projection ${statement.id} screen_actions references missing capability '${capabilityId}'`, entry.loc);
|
|
177
|
+
} else if (capability.kind !== "capability") {
|
|
178
|
+
pushError(errors, `Projection ${statement.id} screen_actions must reference a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
179
|
+
} else if (!realized.has(capabilityId)) {
|
|
180
|
+
pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' capability '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
181
|
+
}
|
|
182
|
+
if (prominenceKeyword !== "prominence") {
|
|
183
|
+
pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' must use 'prominence'`, entry.loc);
|
|
184
|
+
}
|
|
185
|
+
if (!["primary", "secondary", "destructive", "contextual"].includes(prominence || "")) {
|
|
186
|
+
pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' has invalid prominence '${prominence}'`, entry.loc);
|
|
187
|
+
}
|
|
188
|
+
if (placementKeyword && placementKeyword !== "placement") {
|
|
189
|
+
pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' has unknown directive '${placementKeyword}'`, entry.loc);
|
|
190
|
+
}
|
|
191
|
+
if (placementKeyword === "placement" && !["toolbar", "menu", "bulk", "inline", "footer"].includes(placement || "")) {
|
|
192
|
+
pushError(errors, `Projection ${statement.id} screen_actions for '${screenId}' has invalid placement '${placement}'`, entry.loc);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {ValidationErrors} errors
|
|
199
|
+
* @param {TopogramStatement} statement
|
|
200
|
+
* @param {TopogramFieldMap} fieldMap
|
|
201
|
+
* @param {TopogramRegistry} registry
|
|
202
|
+
* @returns {void}
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
export function validateProjectionUiComponents(errors, statement, fieldMap, registry) {
|
|
206
|
+
if (statement.kind !== "projection") {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const componentsField = fieldMap.get("widget_bindings")?.[0];
|
|
211
|
+
if (!componentsField || componentsField.value.type !== "block") {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (symbolValue(getFieldValue(statement, "type")) !== "ui_contract") {
|
|
216
|
+
pushError(errors, `Projection ${statement.id} widget_bindings belongs on shared UI projections; concrete UI projections inherit widget placement through 'realizes'`, componentsField.loc);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const availableScreens = collectAvailableUiScreenIds(statement, fieldMap, registry);
|
|
220
|
+
const availableRegions = collectAvailableUiRegionKeys(statement, registry);
|
|
221
|
+
const availableRegionPatterns = collectAvailableUiRegionPatterns(statement, registry);
|
|
222
|
+
|
|
223
|
+
for (const entry of componentsField.value.entries) {
|
|
224
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
225
|
+
const [screenKeyword, screenId, regionKeyword, regionName, componentKeyword, componentId] = tokens;
|
|
226
|
+
|
|
227
|
+
if (screenKeyword !== "screen") {
|
|
228
|
+
pushError(errors, `Projection ${statement.id} widget_bindings entries must start with 'screen'`, entry.loc);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (!availableScreens.has(screenId)) {
|
|
232
|
+
pushError(errors, `Projection ${statement.id} widget_bindings references unknown screen '${screenId}'`, entry.loc);
|
|
233
|
+
}
|
|
234
|
+
if (regionKeyword !== "region") {
|
|
235
|
+
pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' must use 'region'`, entry.loc);
|
|
236
|
+
}
|
|
237
|
+
if (!UI_REGION_KINDS.has(regionName || "")) {
|
|
238
|
+
pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' has invalid region '${regionName}'`, entry.loc);
|
|
239
|
+
} else if (!availableRegions.has(`${screenId}:${regionName}`)) {
|
|
240
|
+
pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' references undeclared region '${regionName}'`, entry.loc);
|
|
241
|
+
}
|
|
242
|
+
if (componentKeyword !== "widget") {
|
|
243
|
+
pushError(errors, `Projection ${statement.id} widget_bindings for '${screenId}' must use 'widget'`, entry.loc);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const widget = registry.get(componentId);
|
|
247
|
+
if (!widget) {
|
|
248
|
+
pushError(errors, `Projection ${statement.id} widget_bindings references missing widget '${componentId}'`, entry.loc);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (widget.kind !== "widget") {
|
|
252
|
+
pushError(errors, `Projection ${statement.id} widget_bindings must reference a widget, found ${widget.kind} '${widget.id}'`, entry.loc);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const propNames = new Set(blockEntries(getFieldValue(widget, "props"))
|
|
257
|
+
.map((propEntry) => propEntry.items[0])
|
|
258
|
+
.filter((item) => item?.type === "symbol")
|
|
259
|
+
.map((item) => item.value));
|
|
260
|
+
const eventNames = new Set(blockEntries(getFieldValue(widget, "events"))
|
|
261
|
+
.map((eventEntry) => eventEntry.items[0])
|
|
262
|
+
.filter((item) => item?.type === "symbol")
|
|
263
|
+
.map((item) => item.value));
|
|
264
|
+
const componentRegions = symbolValues(getFieldValue(widget, "regions"));
|
|
265
|
+
const componentPatterns = symbolValues(getFieldValue(widget, "patterns"));
|
|
266
|
+
if (componentRegions.length > 0 && !componentRegions.includes(regionName)) {
|
|
267
|
+
pushError(
|
|
268
|
+
errors,
|
|
269
|
+
`Projection ${statement.id} widget_bindings uses widget '${componentId}' in region '${regionName}', but the widget supports regions [${componentRegions.join(", ")}]`,
|
|
270
|
+
entry.loc
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
const regionPattern = availableRegionPatterns.get(`${screenId}:${regionName}`) || null;
|
|
274
|
+
if (regionPattern && componentPatterns.length > 0 && !componentPatterns.includes(regionPattern)) {
|
|
275
|
+
pushError(
|
|
276
|
+
errors,
|
|
277
|
+
`Projection ${statement.id} widget_bindings uses widget '${componentId}' in '${screenId}:${regionName}' with pattern '${regionPattern}', but the widget supports patterns [${componentPatterns.join(", ")}]`,
|
|
278
|
+
entry.loc
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (let i = 6; i < tokens.length;) {
|
|
283
|
+
const directive = tokens[i];
|
|
284
|
+
if (directive === "data") {
|
|
285
|
+
const propName = tokens[i + 1];
|
|
286
|
+
const fromKeyword = tokens[i + 2];
|
|
287
|
+
const sourceId = tokens[i + 3];
|
|
288
|
+
if (!propName || fromKeyword !== "from" || !sourceId) {
|
|
289
|
+
pushError(errors, `Projection ${statement.id} widget_bindings data bindings must use 'data <prop> from <source>'`, entry.loc);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
if (!propNames.has(propName)) {
|
|
293
|
+
pushError(errors, `Projection ${statement.id} widget_bindings references unknown prop '${propName}' on widget '${componentId}'`, entry.loc);
|
|
294
|
+
}
|
|
295
|
+
const source = registry.get(sourceId);
|
|
296
|
+
if (!source || !["capability", "projection", "shape", "entity"].includes(source.kind)) {
|
|
297
|
+
pushError(errors, `Projection ${statement.id} widget_bindings data binding for '${propName}' references missing source '${sourceId}'`, entry.loc);
|
|
298
|
+
}
|
|
299
|
+
i += 4;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (directive === "event") {
|
|
304
|
+
const eventName = tokens[i + 1];
|
|
305
|
+
const action = tokens[i + 2];
|
|
306
|
+
const targetId = tokens[i + 3];
|
|
307
|
+
if (!eventName || !action || !targetId) {
|
|
308
|
+
pushError(errors, `Projection ${statement.id} widget_bindings event bindings must use 'event <event> <navigate|action> <target>'`, entry.loc);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
if (!eventNames.has(eventName)) {
|
|
312
|
+
pushError(errors, `Projection ${statement.id} widget_bindings references unknown event '${eventName}' on widget '${componentId}'`, entry.loc);
|
|
313
|
+
}
|
|
314
|
+
if (action === "navigate") {
|
|
315
|
+
if (!availableScreens.has(targetId)) {
|
|
316
|
+
pushError(errors, `Projection ${statement.id} widget_bindings event '${eventName}' references unknown navigation target '${targetId}'`, entry.loc);
|
|
317
|
+
}
|
|
318
|
+
} else if (action === "action") {
|
|
319
|
+
const target = registry.get(targetId);
|
|
320
|
+
if (!target || target.kind !== "capability") {
|
|
321
|
+
pushError(errors, `Projection ${statement.id} widget_bindings event '${eventName}' references missing capability action '${targetId}'`, entry.loc);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
pushError(errors, `Projection ${statement.id} widget_bindings event '${eventName}' has unsupported action '${action}'`, entry.loc);
|
|
325
|
+
}
|
|
326
|
+
i += 4;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
pushError(errors, `Projection ${statement.id} widget_bindings has unknown directive '${directive}'`, entry.loc);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @param {ValidationErrors} errors
|
|
338
|
+
* @param {TopogramStatement} statement
|
|
339
|
+
* @param {TopogramFieldMap} fieldMap
|
|
340
|
+
* @param {TopogramRegistry} registry
|
|
341
|
+
* @returns {void}
|
|
342
|
+
*/
|
|
343
|
+
|
|
344
|
+
export function validateProjectionUiVisibility(errors, statement, fieldMap, registry) {
|
|
345
|
+
if (statement.kind !== "projection") {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const visibilityField = fieldMap.get("visibility_rules")?.[0];
|
|
350
|
+
if (!visibilityField || visibilityField.value.type !== "block") {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
|
|
355
|
+
for (const entry of visibilityField.value.entries) {
|
|
356
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
357
|
+
const [keyword, capabilityId, predicateKeyword, predicateType, predicateValue] = tokens;
|
|
358
|
+
|
|
359
|
+
if (keyword !== "action") {
|
|
360
|
+
pushError(errors, `Projection ${statement.id} visibility_rules entries must start with 'action'`, entry.loc);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const capability = registry.get(capabilityId);
|
|
365
|
+
if (!capability) {
|
|
366
|
+
pushError(errors, `Projection ${statement.id} visibility_rules references missing capability '${capabilityId}'`, entry.loc);
|
|
367
|
+
} else if (capability.kind !== "capability") {
|
|
368
|
+
pushError(errors, `Projection ${statement.id} visibility_rules must reference a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
|
|
369
|
+
} else if (!realized.has(capabilityId)) {
|
|
370
|
+
pushError(errors, `Projection ${statement.id} visibility_rules action '${capabilityId}' must also appear in 'realizes'`, entry.loc);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (predicateKeyword !== "visible_if") {
|
|
374
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' must use 'visible_if'`, entry.loc);
|
|
375
|
+
}
|
|
376
|
+
if (!["permission", "ownership", "claim"].includes(predicateType || "")) {
|
|
377
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' has invalid predicate '${predicateType}'`, entry.loc);
|
|
378
|
+
}
|
|
379
|
+
if (!predicateValue) {
|
|
380
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' must include a predicate value`, entry.loc);
|
|
381
|
+
}
|
|
382
|
+
if (predicateType === "ownership" && !["owner", "owner_or_admin", "project_member", "none"].includes(predicateValue || "")) {
|
|
383
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' has invalid ownership '${predicateValue}'`, entry.loc);
|
|
384
|
+
}
|
|
385
|
+
const directiveTokens = blockSymbolItems(entry).map((item) => item.value);
|
|
386
|
+
const directives = new Map();
|
|
387
|
+
for (let i = 5; i < directiveTokens.length; i += 2) {
|
|
388
|
+
const key = directiveTokens[i];
|
|
389
|
+
const value = directiveTokens[i + 1];
|
|
390
|
+
if (!value) {
|
|
391
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' is missing a value for '${key}'`, entry.loc);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
directives.set(key, value);
|
|
395
|
+
}
|
|
396
|
+
for (const key of directives.keys()) {
|
|
397
|
+
if (!["claim_value"].includes(key)) {
|
|
398
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' has unknown directive '${key}'`, entry.loc);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (directives.get("claim_value") && predicateType !== "claim") {
|
|
402
|
+
pushError(errors, `Projection ${statement.id} visibility_rules for '${capabilityId}' cannot declare claim_value without claim`, entry.loc);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* @param {ValidationErrors} errors
|
|
409
|
+
* @param {TopogramStatement} statement
|
|
410
|
+
* @param {TopogramFieldMap} fieldMap
|
|
411
|
+
* @param {TopogramRegistry} registry
|
|
412
|
+
* @returns {void}
|
|
413
|
+
*/
|
|
414
|
+
|
|
415
|
+
export function validateProjectionUiLookups(errors, statement, fieldMap, registry) {
|
|
416
|
+
if (statement.kind !== "projection") {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const lookupsField = fieldMap.get("field_lookups")?.[0];
|
|
421
|
+
if (!lookupsField || lookupsField.value.type !== "block") {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const screens = collectProjectionUiScreens(statement, fieldMap);
|
|
426
|
+
|
|
427
|
+
for (const entry of lookupsField.value.entries) {
|
|
428
|
+
const tokens = blockSymbolItems(entry).map((item) => item.value);
|
|
429
|
+
const [keyword, screenId, fieldKeyword, fieldName, entityKeyword, entityId, labelKeyword, labelField, maybeEmptyKeyword, maybeEmptyLabel] = tokens;
|
|
430
|
+
|
|
431
|
+
if (keyword !== "screen") {
|
|
432
|
+
pushError(errors, `Projection ${statement.id} field_lookups entries must start with 'screen'`, entry.loc);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const screenEntry = screens.get(screenId);
|
|
437
|
+
if (!screenEntry) {
|
|
438
|
+
pushError(errors, `Projection ${statement.id} field_lookups references unknown screen '${screenId}'`, entry.loc);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (fieldKeyword !== "field") {
|
|
443
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must use 'field'`, entry.loc);
|
|
444
|
+
}
|
|
445
|
+
if (!fieldName) {
|
|
446
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must include a field name`, entry.loc);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (entityKeyword !== "entity") {
|
|
450
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must use 'entity'`, entry.loc);
|
|
451
|
+
}
|
|
452
|
+
const entity = entityId ? registry.get(entityId) : null;
|
|
453
|
+
if (!entity) {
|
|
454
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' references missing entity '${entityId}'`, entry.loc);
|
|
455
|
+
} else if (entity.kind !== "entity") {
|
|
456
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must reference an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (labelKeyword !== "label_field") {
|
|
460
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must use 'label_field'`, entry.loc);
|
|
461
|
+
}
|
|
462
|
+
if (!labelField) {
|
|
463
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must include a label_field`, entry.loc);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (maybeEmptyKeyword && maybeEmptyKeyword !== "empty_label") {
|
|
467
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' has unknown directive '${maybeEmptyKeyword}'`, entry.loc);
|
|
468
|
+
}
|
|
469
|
+
if (maybeEmptyKeyword === "empty_label" && !maybeEmptyLabel) {
|
|
470
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' must include a value for 'empty_label'`, entry.loc);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const availableFields = resolveProjectionUiScreenFieldNames(registry, screenEntry, statement);
|
|
474
|
+
if (fieldName && availableFields.size > 0 && !availableFields.has(fieldName)) {
|
|
475
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' references unknown screen field '${fieldName}'`, entry.loc);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (entity?.kind === "entity") {
|
|
479
|
+
const entityFieldNames = new Set(statementFieldNames(entity));
|
|
480
|
+
if (labelField && !entityFieldNames.has(labelField)) {
|
|
481
|
+
pushError(errors, `Projection ${statement.id} field_lookups for '${screenId}' references unknown entity field '${labelField}' on '${entity.id}'`, entry.loc);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* @param {ValidationErrors} errors
|
|
489
|
+
* @param {TopogramStatement} statement
|
|
490
|
+
* @param {TopogramFieldMap} fieldMap
|
|
491
|
+
* @param {TopogramRegistry} registry
|
|
492
|
+
* @returns {void}
|
|
493
|
+
*/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
validateProjectionUiOwnership,
|
|
5
|
+
validateProjectionUiScreens,
|
|
6
|
+
validateProjectionUiAppShell,
|
|
7
|
+
validateProjectionUiDesign
|
|
8
|
+
} from "./ui-structure.js";
|
|
9
|
+
import {
|
|
10
|
+
validateProjectionUiNavigation,
|
|
11
|
+
validateProjectionUiScreenRegions,
|
|
12
|
+
validateProjectionUiRoutes,
|
|
13
|
+
validateProjectionUiWeb,
|
|
14
|
+
validateProjectionUiIos
|
|
15
|
+
} from "./ui-navigation.js";
|
|
16
|
+
import {
|
|
17
|
+
validateProjectionUiCollections,
|
|
18
|
+
validateProjectionUiActions,
|
|
19
|
+
validateProjectionUiComponents,
|
|
20
|
+
validateProjectionUiVisibility,
|
|
21
|
+
validateProjectionUiLookups
|
|
22
|
+
} from "./ui-widgets.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {ValidationErrors} errors
|
|
26
|
+
* @param {TopogramStatement} statement
|
|
27
|
+
* @param {TopogramFieldMap} fieldMap
|
|
28
|
+
* @param {TopogramRegistry} registry
|
|
29
|
+
* @returns {void}
|
|
30
|
+
*/
|
|
31
|
+
export function validateUiProjection(errors, statement, fieldMap, registry) {
|
|
32
|
+
validateProjectionUiOwnership(errors, statement, fieldMap);
|
|
33
|
+
validateProjectionUiScreens(errors, statement, fieldMap, registry);
|
|
34
|
+
validateProjectionUiCollections(errors, statement, fieldMap, registry);
|
|
35
|
+
validateProjectionUiActions(errors, statement, fieldMap, registry);
|
|
36
|
+
validateProjectionUiVisibility(errors, statement, fieldMap, registry);
|
|
37
|
+
validateProjectionUiLookups(errors, statement, fieldMap, registry);
|
|
38
|
+
validateProjectionUiRoutes(errors, statement, fieldMap, registry);
|
|
39
|
+
validateProjectionUiAppShell(errors, statement, fieldMap);
|
|
40
|
+
validateProjectionUiDesign(errors, statement, fieldMap);
|
|
41
|
+
validateProjectionUiNavigation(errors, statement, fieldMap, registry);
|
|
42
|
+
validateProjectionUiScreenRegions(errors, statement, fieldMap, registry);
|
|
43
|
+
validateProjectionUiComponents(errors, statement, fieldMap, registry);
|
|
44
|
+
validateProjectionUiWeb(errors, statement, fieldMap, registry);
|
|
45
|
+
validateProjectionUiIos(errors, statement, fieldMap, registry);
|
|
46
|
+
}
|
|
@@ -1 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { IDENTIFIER_PATTERN, STATEMENT_KINDS } from "./kinds.js";
|
|
4
|
+
import { pushError } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} oldName
|
|
8
|
+
* @param {string} newName
|
|
9
|
+
* @param {string} example
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
function renameDiagnostic(oldName, newName, example) {
|
|
13
|
+
return `${oldName} was renamed to ${newName}. Example fix: ${example}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import("../parser.js").WorkspaceAst} workspaceAst
|
|
18
|
+
* @param {ValidationErrors} errors
|
|
19
|
+
* @returns {TopogramRegistry}
|
|
20
|
+
*/
|
|
21
|
+
export function buildRegistry(workspaceAst, errors) {
|
|
22
|
+
const registry = new Map();
|
|
23
|
+
|
|
24
|
+
for (const file of workspaceAst.files) {
|
|
25
|
+
for (const statement of file.statements) {
|
|
26
|
+
if (!STATEMENT_KINDS.has(statement.kind)) {
|
|
27
|
+
if (statement.kind === "component") {
|
|
28
|
+
pushError(errors, `Statement kind ${renameDiagnostic("'component'", "'widget'", "widget widget_data_grid { ... }")}`, statement.loc);
|
|
29
|
+
} else {
|
|
30
|
+
pushError(errors, `Unknown statement kind '${statement.kind}'`, statement.loc);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!IDENTIFIER_PATTERN.test(statement.id)) {
|
|
35
|
+
pushError(errors, `Invalid identifier '${statement.id}'`, statement.loc);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (registry.has(statement.id)) {
|
|
39
|
+
pushError(errors, `Duplicate statement id '${statement.id}'`, statement.loc);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
registry.set(statement.id, statement);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return registry;
|
|
48
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TopogramBlockEntry,
|
|
3
|
+
TopogramField,
|
|
4
|
+
TopogramFieldMap,
|
|
5
|
+
TopogramStatement,
|
|
6
|
+
TopogramToken,
|
|
7
|
+
ValidationErrors
|
|
8
|
+
} from "../topogram-types.js";
|
|
9
|
+
|
|
10
|
+
export function blockEntries(value: TopogramToken | null | undefined): TopogramBlockEntry[];
|
|
11
|
+
export function blockSymbolItems(entry: TopogramBlockEntry): TopogramToken[];
|
|
12
|
+
export function collectFieldMap(statement: TopogramStatement): TopogramFieldMap;
|
|
13
|
+
export function formatLoc(loc: any): string;
|
|
14
|
+
export function getField(statement: TopogramStatement, key: string): TopogramField | null;
|
|
15
|
+
export function getFieldValue(statement: TopogramStatement, key: string): TopogramToken | null;
|
|
16
|
+
export function pushError(errors: ValidationErrors, message: string, loc?: any): void;
|
|
17
|
+
export function stringValue(value: TopogramToken | null | undefined): string | null;
|
|
18
|
+
export function symbolValue(value: TopogramToken | null | undefined): string | null;
|
|
19
|
+
export function symbolValues(value: TopogramToken | null | undefined): string[];
|
|
20
|
+
export function valueAsArray(value: TopogramToken | null | undefined): TopogramToken[];
|