typekro 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/alchemy/deployers.d.ts +16 -1
- package/dist/alchemy/deployers.d.ts.map +1 -1
- package/dist/alchemy/deployers.js +131 -18
- package/dist/alchemy/deployers.js.map +1 -1
- package/dist/alchemy/deployment.d.ts +1 -1
- package/dist/alchemy/deployment.d.ts.map +1 -1
- package/dist/alchemy/deployment.js +1 -1
- package/dist/alchemy/deployment.js.map +1 -1
- package/dist/alchemy/kro-delete.d.ts +66 -0
- package/dist/alchemy/kro-delete.d.ts.map +1 -0
- package/dist/alchemy/kro-delete.js +183 -0
- package/dist/alchemy/kro-delete.js.map +1 -0
- package/dist/alchemy/resource-registration.d.ts +16 -0
- package/dist/alchemy/resource-registration.d.ts.map +1 -1
- package/dist/alchemy/resource-registration.js +138 -24
- package/dist/alchemy/resource-registration.js.map +1 -1
- package/dist/alchemy/types.d.ts +8 -4
- package/dist/alchemy/types.d.ts.map +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.d.ts +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.d.ts.map +1 -1
- package/dist/compositions/typekro-runtime/typekro-runtime.js +39 -7
- package/dist/compositions/typekro-runtime/typekro-runtime.js.map +1 -1
- package/dist/core/composition/context.d.ts +58 -2
- package/dist/core/composition/context.d.ts.map +1 -1
- package/dist/core/composition/context.js +4 -0
- package/dist/core/composition/context.js.map +1 -1
- package/dist/core/composition/imperative.d.ts +9 -0
- package/dist/core/composition/imperative.d.ts.map +1 -1
- package/dist/core/composition/imperative.js +538 -54
- package/dist/core/composition/imperative.js.map +1 -1
- package/dist/core/composition/nested-status-cel.d.ts +34 -1
- package/dist/core/composition/nested-status-cel.d.ts.map +1 -1
- package/dist/core/composition/nested-status-cel.js +379 -41
- package/dist/core/composition/nested-status-cel.js.map +1 -1
- package/dist/core/composition-debugger.d.ts +1 -1
- package/dist/core/composition-debugger.d.ts.map +1 -1
- package/dist/core/composition-debugger.js.map +1 -1
- package/dist/core/constants/brands.d.ts +1 -1
- package/dist/core/constants/brands.d.ts.map +1 -1
- package/dist/core/constants/brands.js +1 -1
- package/dist/core/constants/brands.js.map +1 -1
- package/dist/core/containers/build.d.ts +4 -4
- package/dist/core/containers/build.d.ts.map +1 -1
- package/dist/core/containers/build.js +44 -16
- package/dist/core/containers/build.js.map +1 -1
- package/dist/core/containers/registries/types.d.ts +1 -1
- package/dist/core/containers/registries/types.d.ts.map +1 -1
- package/dist/core/dependencies/resolver.d.ts +19 -0
- package/dist/core/dependencies/resolver.d.ts.map +1 -1
- package/dist/core/dependencies/resolver.js +261 -1
- package/dist/core/dependencies/resolver.js.map +1 -1
- package/dist/core/deployment/client-provider-manager.d.ts +9 -3
- package/dist/core/deployment/client-provider-manager.d.ts.map +1 -1
- package/dist/core/deployment/client-provider-manager.js +12 -0
- package/dist/core/deployment/client-provider-manager.js.map +1 -1
- package/dist/core/deployment/crd-manager.d.ts +24 -0
- package/dist/core/deployment/crd-manager.d.ts.map +1 -1
- package/dist/core/deployment/crd-manager.js +79 -1
- package/dist/core/deployment/crd-manager.js.map +1 -1
- package/dist/core/deployment/deployment-state-discovery.d.ts +116 -0
- package/dist/core/deployment/deployment-state-discovery.d.ts.map +1 -0
- package/dist/core/deployment/deployment-state-discovery.js +400 -0
- package/dist/core/deployment/deployment-state-discovery.js.map +1 -0
- package/dist/core/deployment/direct-factory.d.ts +65 -6
- package/dist/core/deployment/direct-factory.d.ts.map +1 -1
- package/dist/core/deployment/direct-factory.js +551 -56
- package/dist/core/deployment/direct-factory.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +64 -3
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +194 -27
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/deployment/event-filter.js +1 -1
- package/dist/core/deployment/event-filter.js.map +1 -1
- package/dist/core/deployment/event-monitor.d.ts +11 -0
- package/dist/core/deployment/event-monitor.d.ts.map +1 -1
- package/dist/core/deployment/event-monitor.js +14 -0
- package/dist/core/deployment/event-monitor.js.map +1 -1
- package/dist/core/deployment/handle-tracing.d.ts +14 -0
- package/dist/core/deployment/handle-tracing.d.ts.map +1 -0
- package/dist/core/deployment/handle-tracing.js +38 -0
- package/dist/core/deployment/handle-tracing.js.map +1 -0
- package/dist/core/deployment/kro-factory.d.ts +136 -3
- package/dist/core/deployment/kro-factory.d.ts.map +1 -1
- package/dist/core/deployment/kro-factory.js +945 -268
- package/dist/core/deployment/kro-factory.js.map +1 -1
- package/dist/core/deployment/kro-readiness.d.ts.map +1 -1
- package/dist/core/deployment/kro-readiness.js +21 -12
- package/dist/core/deployment/kro-readiness.js.map +1 -1
- package/dist/core/deployment/nested-composition-status.d.ts +1 -1
- package/dist/core/deployment/nested-composition-status.d.ts.map +1 -1
- package/dist/core/deployment/nested-composition-status.js +96 -53
- package/dist/core/deployment/nested-composition-status.js.map +1 -1
- package/dist/core/deployment/resource-applier.d.ts +15 -2
- package/dist/core/deployment/resource-applier.d.ts.map +1 -1
- package/dist/core/deployment/resource-applier.js +75 -25
- package/dist/core/deployment/resource-applier.js.map +1 -1
- package/dist/core/deployment/resource-tagging.d.ts +220 -0
- package/dist/core/deployment/resource-tagging.d.ts.map +1 -0
- package/dist/core/deployment/resource-tagging.js +292 -0
- package/dist/core/deployment/resource-tagging.js.map +1 -0
- package/dist/core/deployment/rollback-manager.d.ts +25 -4
- package/dist/core/deployment/rollback-manager.d.ts.map +1 -1
- package/dist/core/deployment/rollback-manager.js +70 -57
- package/dist/core/deployment/rollback-manager.js.map +1 -1
- package/dist/core/deployment/shared-utilities.d.ts +6 -0
- package/dist/core/deployment/shared-utilities.d.ts.map +1 -1
- package/dist/core/deployment/shared-utilities.js +32 -2
- package/dist/core/deployment/shared-utilities.js.map +1 -1
- package/dist/core/deployment/singleton-owner-drift.d.ts +16 -0
- package/dist/core/deployment/singleton-owner-drift.d.ts.map +1 -0
- package/dist/core/deployment/singleton-owner-drift.js +54 -0
- package/dist/core/deployment/singleton-owner-drift.js.map +1 -0
- package/dist/core/deployment/strategies/alchemy-strategy.d.ts +3 -1
- package/dist/core/deployment/strategies/alchemy-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/alchemy-strategy.js +121 -18
- package/dist/core/deployment/strategies/alchemy-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.d.ts +9 -3
- package/dist/core/deployment/strategies/base-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/base-strategy.js +32 -4
- package/dist/core/deployment/strategies/base-strategy.js.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.d.ts +12 -4
- package/dist/core/deployment/strategies/direct-strategy.d.ts.map +1 -1
- package/dist/core/deployment/strategies/direct-strategy.js +112 -8
- package/dist/core/deployment/strategies/direct-strategy.js.map +1 -1
- package/dist/core/errors.d.ts +2 -2
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/expressions/analysis/cache.d.ts +2 -2
- package/dist/core/expressions/analysis/cache.d.ts.map +1 -1
- package/dist/core/expressions/analysis/cache.js +4 -0
- package/dist/core/expressions/analysis/cache.js.map +1 -1
- package/dist/core/expressions/analysis/fn-toString-self-test.d.ts.map +1 -1
- package/dist/core/expressions/analysis/fn-toString-self-test.js +0 -1
- package/dist/core/expressions/analysis/fn-toString-self-test.js.map +1 -1
- package/dist/core/expressions/analysis/shared-types.d.ts +2 -2
- package/dist/core/expressions/analysis/shared-types.d.ts.map +1 -1
- package/dist/core/expressions/analysis/source-map.d.ts.map +1 -1
- package/dist/core/expressions/analysis/source-map.js +1 -0
- package/dist/core/expressions/analysis/source-map.js.map +1 -1
- package/dist/core/expressions/analysis/types.d.ts +7 -7
- package/dist/core/expressions/analysis/types.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts +37 -3
- package/dist/core/expressions/composition/composition-analyzer-helpers.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-helpers.js +370 -7
- package/dist/core/expressions/composition/composition-analyzer-helpers.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts +2 -2
- package/dist/core/expressions/composition/composition-analyzer-ternary.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-ternary.js +264 -29
- package/dist/core/expressions/composition/composition-analyzer-ternary.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-traversal.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-traversal.js +78 -6
- package/dist/core/expressions/composition/composition-analyzer-traversal.js.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer-types.d.ts +60 -0
- package/dist/core/expressions/composition/composition-analyzer-types.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer.d.ts +1 -1
- package/dist/core/expressions/composition/composition-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/composition-analyzer.js +158 -8
- package/dist/core/expressions/composition/composition-analyzer.js.map +1 -1
- package/dist/core/expressions/composition/expression-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/expression-analyzer.js +21 -4
- package/dist/core/expressions/composition/expression-analyzer.js.map +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.d.ts +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/composition/imperative-analyzer.js +31 -7
- package/dist/core/expressions/composition/imperative-analyzer.js.map +1 -1
- package/dist/core/expressions/composition/scope-manager.d.ts +2 -2
- package/dist/core/expressions/composition/scope-manager.d.ts.map +1 -1
- package/dist/core/expressions/composition/scope-manager.js.map +1 -1
- package/dist/core/expressions/conditional/conditional-expression-processor.d.ts +3 -3
- package/dist/core/expressions/conditional/conditional-expression-processor.d.ts.map +1 -1
- package/dist/core/expressions/conditional/conditional-expression-processor.js.map +1 -1
- package/dist/core/expressions/conditional/conditional-integration.d.ts.map +1 -1
- package/dist/core/expressions/conditional/conditional-integration.js.map +1 -1
- package/dist/core/expressions/context/context-aware-generator.d.ts +5 -5
- package/dist/core/expressions/context/context-aware-generator.d.ts.map +1 -1
- package/dist/core/expressions/context/context-aware-generator.js.map +1 -1
- package/dist/core/expressions/context/context-detector.d.ts +3 -3
- package/dist/core/expressions/context/context-detector.d.ts.map +1 -1
- package/dist/core/expressions/context/context-detector.js.map +1 -1
- package/dist/core/expressions/context/context-validator.d.ts +6 -6
- package/dist/core/expressions/context/context-validator.d.ts.map +1 -1
- package/dist/core/expressions/context/context-validator.js +2 -2
- package/dist/core/expressions/context/context-validator.js.map +1 -1
- package/dist/core/expressions/factory/cel-conversion-engine.d.ts +4 -4
- package/dist/core/expressions/factory/cel-conversion-engine.d.ts.map +1 -1
- package/dist/core/expressions/factory/cel-conversion-engine.js.map +1 -1
- package/dist/core/expressions/factory/dependency-tracker.d.ts +2 -2
- package/dist/core/expressions/factory/dependency-tracker.d.ts.map +1 -1
- package/dist/core/expressions/factory/dependency-tracker.js +21 -5
- package/dist/core/expressions/factory/dependency-tracker.js.map +1 -1
- package/dist/core/expressions/factory/factory-integration.d.ts +2 -4
- package/dist/core/expressions/factory/factory-integration.d.ts.map +1 -1
- package/dist/core/expressions/factory/factory-integration.js +0 -6
- package/dist/core/expressions/factory/factory-integration.js.map +1 -1
- package/dist/core/expressions/factory/factory-pattern-handler.d.ts +3 -3
- package/dist/core/expressions/factory/factory-pattern-handler.d.ts.map +1 -1
- package/dist/core/expressions/factory/factory-pattern-handler.js +1 -0
- package/dist/core/expressions/factory/factory-pattern-handler.js.map +1 -1
- package/dist/core/expressions/factory/migration-helpers.js.map +1 -1
- package/dist/core/expressions/factory/resource-analyzer.d.ts +4 -4
- package/dist/core/expressions/factory/resource-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/factory/resource-analyzer.js.map +1 -1
- package/dist/core/expressions/factory/resource-type-validator.d.ts +5 -5
- package/dist/core/expressions/factory/resource-type-validator.d.ts.map +1 -1
- package/dist/core/expressions/factory/resource-type-validator.js.map +1 -1
- package/dist/core/expressions/factory/status-builder-analyzer.d.ts +6 -7
- package/dist/core/expressions/factory/status-builder-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-builder-analyzer.js +0 -3
- package/dist/core/expressions/factory/status-builder-analyzer.js.map +1 -1
- package/dist/core/expressions/factory/status-builder-types.d.ts +1 -1
- package/dist/core/expressions/factory/status-builder-types.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-cel-generation.d.ts +1 -1
- package/dist/core/expressions/factory/status-cel-generation.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-cel-generation.js +1 -1
- package/dist/core/expressions/factory/status-cel-generation.js.map +1 -1
- package/dist/core/expressions/factory/status-field-analysis.d.ts +3 -3
- package/dist/core/expressions/factory/status-field-analysis.d.ts.map +1 -1
- package/dist/core/expressions/factory/status-field-analysis.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.d.ts +5 -5
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.js +1 -1
- package/dist/core/expressions/magic-proxy/magic-assignable-analyzer.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.d.ts +10 -10
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.js +5 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-analyzer.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-ast.d.ts +2 -2
- package/dist/core/expressions/magic-proxy/magic-proxy-ast.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-ast.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-detector.d.ts +5 -5
- package/dist/core/expressions/magic-proxy/magic-proxy-detector.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-detector.js.map +1 -1
- package/dist/core/expressions/magic-proxy/magic-proxy-types.d.ts +2 -2
- package/dist/core/expressions/magic-proxy/magic-proxy-types.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/optionality-handler.d.ts +1 -2
- package/dist/core/expressions/magic-proxy/optionality-handler.d.ts.map +1 -1
- package/dist/core/expressions/magic-proxy/optionality-handler.js +2 -15
- package/dist/core/expressions/magic-proxy/optionality-handler.js.map +1 -1
- package/dist/core/expressions/validation/compile-time-checker.d.ts +1 -1
- package/dist/core/expressions/validation/compile-time-checker.d.ts.map +1 -1
- package/dist/core/expressions/validation/compile-time-checker.js.map +1 -1
- package/dist/core/expressions/validation/compile-time-types.d.ts +2 -2
- package/dist/core/expressions/validation/compile-time-types.d.ts.map +1 -1
- package/dist/core/expressions/validation/kubernetes-field-types.d.ts +4 -4
- package/dist/core/expressions/validation/kubernetes-field-types.d.ts.map +1 -1
- package/dist/core/expressions/validation/kubernetes-field-types.js.map +1 -1
- package/dist/core/expressions/validation/resource-field-utils.d.ts +10 -10
- package/dist/core/expressions/validation/resource-field-utils.d.ts.map +1 -1
- package/dist/core/expressions/validation/resource-field-utils.js.map +1 -1
- package/dist/core/expressions/validation/resource-validation.d.ts +3 -3
- package/dist/core/expressions/validation/resource-validation.d.ts.map +1 -1
- package/dist/core/expressions/validation/resource-validation.js.map +1 -1
- package/dist/core/expressions/validation/type-inference-types.d.ts +2 -2
- package/dist/core/expressions/validation/type-inference-types.d.ts.map +1 -1
- package/dist/core/expressions/validation/type-safety.d.ts +2 -2
- package/dist/core/expressions/validation/type-safety.d.ts.map +1 -1
- package/dist/core/expressions/validation/type-safety.js +1 -0
- package/dist/core/expressions/validation/type-safety.js.map +1 -1
- package/dist/core/kubernetes/bun-api-client.js.map +1 -1
- package/dist/core/kubernetes/bun-http-library.d.ts.map +1 -1
- package/dist/core/kubernetes/bun-http-library.js +29 -3
- package/dist/core/kubernetes/bun-http-library.js.map +1 -1
- package/dist/core/kubernetes/client-provider.d.ts +12 -0
- package/dist/core/kubernetes/client-provider.d.ts.map +1 -1
- package/dist/core/kubernetes/client-provider.js +35 -0
- package/dist/core/kubernetes/client-provider.js.map +1 -1
- package/dist/core/metadata/resource-metadata.d.ts +46 -1
- package/dist/core/metadata/resource-metadata.d.ts.map +1 -1
- package/dist/core/metadata/resource-metadata.js.map +1 -1
- package/dist/core/proxy/create-resource.d.ts +15 -0
- package/dist/core/proxy/create-resource.d.ts.map +1 -1
- package/dist/core/proxy/create-resource.js +56 -2
- package/dist/core/proxy/create-resource.js.map +1 -1
- package/dist/core/readiness/registry.js +2 -2
- package/dist/core/readiness/registry.js.map +1 -1
- package/dist/core/references/cel-evaluator.d.ts +1 -4
- package/dist/core/references/cel-evaluator.d.ts.map +1 -1
- package/dist/core/references/cel-evaluator.js +3 -7
- package/dist/core/references/cel-evaluator.js.map +1 -1
- package/dist/core/references/cel.d.ts +70 -0
- package/dist/core/references/cel.d.ts.map +1 -1
- package/dist/core/references/cel.js +188 -8
- package/dist/core/references/cel.js.map +1 -1
- package/dist/core/references/external-refs.d.ts.map +1 -1
- package/dist/core/references/external-refs.js +3 -0
- package/dist/core/references/external-refs.js.map +1 -1
- package/dist/core/references/resolver.d.ts.map +1 -1
- package/dist/core/references/resolver.js +28 -17
- package/dist/core/references/resolver.js.map +1 -1
- package/dist/core/references/schema-proxy.d.ts +18 -10
- package/dist/core/references/schema-proxy.d.ts.map +1 -1
- package/dist/core/references/schema-proxy.js +174 -23
- package/dist/core/references/schema-proxy.js.map +1 -1
- package/dist/core/runtime-patches/crd-schema-fix.d.ts.map +1 -1
- package/dist/core/runtime-patches/crd-schema-fix.js +4 -1
- package/dist/core/runtime-patches/crd-schema-fix.js.map +1 -1
- package/dist/core/serialization/cel-optimizer.d.ts.map +1 -1
- package/dist/core/serialization/cel-optimizer.js +2 -0
- package/dist/core/serialization/cel-optimizer.js.map +1 -1
- package/dist/core/serialization/cel-references.d.ts +75 -1
- package/dist/core/serialization/cel-references.d.ts.map +1 -1
- package/dist/core/serialization/cel-references.js +723 -145
- package/dist/core/serialization/cel-references.js.map +1 -1
- package/dist/core/serialization/core.d.ts +13 -8
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +973 -12
- package/dist/core/serialization/core.js.map +1 -1
- package/dist/core/serialization/kro-post-processing.d.ts +46 -0
- package/dist/core/serialization/kro-post-processing.d.ts.map +1 -0
- package/dist/core/serialization/kro-post-processing.js +150 -0
- package/dist/core/serialization/kro-post-processing.js.map +1 -0
- package/dist/core/serialization/schema.d.ts +62 -3
- package/dist/core/serialization/schema.d.ts.map +1 -1
- package/dist/core/serialization/schema.js +819 -6
- package/dist/core/serialization/schema.js.map +1 -1
- package/dist/core/serialization/status-analysis-pipeline.d.ts +1 -1
- package/dist/core/serialization/status-analysis-pipeline.d.ts.map +1 -1
- package/dist/core/serialization/status-analysis-pipeline.js.map +1 -1
- package/dist/core/serialization/yaml.d.ts +3 -2
- package/dist/core/serialization/yaml.d.ts.map +1 -1
- package/dist/core/serialization/yaml.js +404 -56
- package/dist/core/serialization/yaml.js.map +1 -1
- package/dist/core/singleton/singleton.d.ts +16 -0
- package/dist/core/singleton/singleton.d.ts.map +1 -0
- package/dist/core/singleton/singleton.js +135 -0
- package/dist/core/singleton/singleton.js.map +1 -0
- package/dist/core/types/common.d.ts +2 -2
- package/dist/core/types/common.d.ts.map +1 -1
- package/dist/core/types/composable.d.ts +1 -1
- package/dist/core/types/composable.d.ts.map +1 -1
- package/dist/core/types/deployment.d.ts +126 -6
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/deployment.js +1 -1
- package/dist/core/types/deployment.js.map +1 -1
- package/dist/core/types/kubernetes.d.ts +25 -17
- package/dist/core/types/kubernetes.d.ts.map +1 -1
- package/dist/core/types/references.d.ts +1 -1
- package/dist/core/types/references.d.ts.map +1 -1
- package/dist/core/types/references.js.map +1 -1
- package/dist/core/types/resource-graph.d.ts +1 -1
- package/dist/core/types/resource-graph.d.ts.map +1 -1
- package/dist/core/types/schema.d.ts +1 -1
- package/dist/core/types/schema.d.ts.map +1 -1
- package/dist/core/types/serialization.d.ts +62 -6
- package/dist/core/types/serialization.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.d.ts +15 -2
- package/dist/core/validation/cel-validator.d.ts.map +1 -1
- package/dist/core/validation/cel-validator.js +144 -63
- package/dist/core/validation/cel-validator.js.map +1 -1
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts +2 -41
- package/dist/factories/apisix/compositions/apisix-bootstrap.d.ts.map +1 -1
- package/dist/factories/apisix/compositions/apisix-bootstrap.js +262 -217
- package/dist/factories/apisix/compositions/apisix-bootstrap.js.map +1 -1
- package/dist/factories/apisix/index.d.ts +2 -2
- package/dist/factories/apisix/index.js +2 -2
- package/dist/factories/apisix/resources/helm.d.ts +2 -2
- package/dist/factories/apisix/resources/helm.d.ts.map +1 -1
- package/dist/factories/apisix/resources/helm.js.map +1 -1
- package/dist/factories/apisix/types.d.ts +21 -11
- package/dist/factories/apisix/types.d.ts.map +1 -1
- package/dist/factories/apisix/types.js +106 -4
- package/dist/factories/apisix/types.js.map +1 -1
- package/dist/factories/apisix/utils/admin-credentials.d.ts +5 -3
- package/dist/factories/apisix/utils/admin-credentials.d.ts.map +1 -1
- package/dist/factories/apisix/utils/admin-credentials.js +14 -10
- package/dist/factories/apisix/utils/admin-credentials.js.map +1 -1
- package/dist/factories/apisix/utils/helm-values-mapper.d.ts.map +1 -1
- package/dist/factories/apisix/utils/helm-values-mapper.js +4 -2
- package/dist/factories/apisix/utils/helm-values-mapper.js.map +1 -1
- package/dist/factories/cert-manager/resources/challenges.js.map +1 -1
- package/dist/factories/cert-manager/types.d.ts +3 -3
- package/dist/factories/cert-manager/types.d.ts.map +1 -1
- package/dist/factories/cilium/compositions/cilium-bootstrap.d.ts +4 -4
- package/dist/factories/cilium/types.d.ts +3 -3
- package/dist/factories/cilium/types.d.ts.map +1 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts +1 -0
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.d.ts.map +1 -1
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.js +48 -0
- package/dist/factories/cnpg/compositions/cnpg-bootstrap.js.map +1 -1
- package/dist/factories/cnpg/resources/cluster.js +1 -1
- package/dist/factories/cnpg/resources/cluster.js.map +1 -1
- package/dist/factories/cnpg/resources/helm.d.ts.map +1 -1
- package/dist/factories/cnpg/resources/helm.js +1 -0
- package/dist/factories/cnpg/resources/helm.js.map +1 -1
- package/dist/factories/cnpg/resources/pooler.js +1 -1
- package/dist/factories/cnpg/resources/pooler.js.map +1 -1
- package/dist/factories/cnpg/types.d.ts +9 -8
- package/dist/factories/cnpg/types.d.ts.map +1 -1
- package/dist/factories/cnpg/types.js +11 -0
- package/dist/factories/cnpg/types.js.map +1 -1
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.d.ts.map +1 -1
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js +153 -41
- package/dist/factories/external-dns/compositions/external-dns-bootstrap.js.map +1 -1
- package/dist/factories/external-dns/resources/dns-endpoint.js +1 -1
- package/dist/factories/external-dns/resources/dns-endpoint.js.map +1 -1
- package/dist/factories/external-dns/resources/helm.d.ts +1 -1
- package/dist/factories/external-dns/resources/helm.d.ts.map +1 -1
- package/dist/factories/external-dns/resources/helm.js +17 -10
- package/dist/factories/external-dns/resources/helm.js.map +1 -1
- package/dist/factories/external-dns/types.d.ts +5 -2
- package/dist/factories/external-dns/types.d.ts.map +1 -1
- package/dist/factories/external-dns/types.js.map +1 -1
- package/dist/factories/flux/git-repository.d.ts.map +1 -1
- package/dist/factories/flux/git-repository.js +1 -1
- package/dist/factories/flux/git-repository.js.map +1 -1
- package/dist/factories/flux/kustomize/kustomization.d.ts +2 -2
- package/dist/factories/flux/kustomize/kustomization.d.ts.map +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.d.ts +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.d.ts.map +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.js +1 -1
- package/dist/factories/flux/kustomize/readiness-evaluators.js.map +1 -1
- package/dist/factories/helm/helm-release.d.ts +3 -2
- package/dist/factories/helm/helm-release.d.ts.map +1 -1
- package/dist/factories/helm/helm-release.js +1 -0
- package/dist/factories/helm/helm-release.js.map +1 -1
- package/dist/factories/helm/helm-repository.d.ts +1 -1
- package/dist/factories/helm/helm-repository.d.ts.map +1 -1
- package/dist/factories/helm/helm-repository.js +6 -4
- package/dist/factories/helm/helm-repository.js.map +1 -1
- package/dist/factories/helm/readiness-evaluators.d.ts +6 -6
- package/dist/factories/helm/readiness-evaluators.d.ts.map +1 -1
- package/dist/factories/helm/readiness-evaluators.js +15 -9
- package/dist/factories/helm/readiness-evaluators.js.map +1 -1
- package/dist/factories/helm/types.d.ts +5 -1
- package/dist/factories/helm/types.d.ts.map +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts +1 -0
- package/dist/factories/inngest/compositions/inngest-bootstrap.d.ts.map +1 -1
- package/dist/factories/inngest/compositions/inngest-bootstrap.js +4 -3
- package/dist/factories/inngest/compositions/inngest-bootstrap.js.map +1 -1
- package/dist/factories/inngest/resources/helm.js +1 -1
- package/dist/factories/inngest/resources/helm.js.map +1 -1
- package/dist/factories/inngest/types.d.ts +5 -4
- package/dist/factories/inngest/types.d.ts.map +1 -1
- package/dist/factories/inngest/types.js +2 -0
- package/dist/factories/inngest/types.js.map +1 -1
- package/dist/factories/kro/kro-custom-resource.js +1 -1
- package/dist/factories/kro/kro-custom-resource.js.map +1 -1
- package/dist/factories/kubernetes/config/config-map.d.ts +2 -2
- package/dist/factories/kubernetes/config/config-map.d.ts.map +1 -1
- package/dist/factories/kubernetes/config/secret.d.ts +2 -2
- package/dist/factories/kubernetes/config/secret.d.ts.map +1 -1
- package/dist/factories/kubernetes/config/secret.js +11 -1
- package/dist/factories/kubernetes/config/secret.js.map +1 -1
- package/dist/factories/kubernetes/networking/service.js +1 -1
- package/dist/factories/kubernetes/networking/service.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-directory.js +9 -0
- package/dist/factories/kubernetes/yaml/yaml-directory.js.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.d.ts.map +1 -1
- package/dist/factories/kubernetes/yaml/yaml-file.js +9 -0
- package/dist/factories/kubernetes/yaml/yaml-file.js.map +1 -1
- package/dist/factories/pebble/resources/helm.js.map +1 -1
- package/dist/factories/pebble/types.d.ts +2 -2
- package/dist/factories/searxng/compositions/index.d.ts +2 -0
- package/dist/factories/searxng/compositions/index.d.ts.map +1 -0
- package/dist/factories/searxng/compositions/index.js +2 -0
- package/dist/factories/searxng/compositions/index.js.map +1 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts +66 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.d.ts.map +1 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.js +275 -0
- package/dist/factories/searxng/compositions/searxng-bootstrap.js.map +1 -0
- package/dist/factories/searxng/index.d.ts +29 -0
- package/dist/factories/searxng/index.d.ts.map +1 -0
- package/dist/factories/searxng/index.js +27 -0
- package/dist/factories/searxng/index.js.map +1 -0
- package/dist/factories/searxng/resources/index.d.ts +2 -0
- package/dist/factories/searxng/resources/index.d.ts.map +1 -0
- package/dist/factories/searxng/resources/index.js +2 -0
- package/dist/factories/searxng/resources/index.js.map +1 -0
- package/dist/factories/searxng/resources/searxng.d.ts +65 -0
- package/dist/factories/searxng/resources/searxng.d.ts.map +1 -0
- package/dist/factories/searxng/resources/searxng.js +188 -0
- package/dist/factories/searxng/resources/searxng.js.map +1 -0
- package/dist/factories/searxng/types.d.ts +127 -0
- package/dist/factories/searxng/types.d.ts.map +1 -0
- package/dist/factories/searxng/types.js +177 -0
- package/dist/factories/searxng/types.js.map +1 -0
- package/dist/factories/searxng/utils/settings-builder.d.ts +47 -0
- package/dist/factories/searxng/utils/settings-builder.d.ts.map +1 -0
- package/dist/factories/searxng/utils/settings-builder.js +53 -0
- package/dist/factories/searxng/utils/settings-builder.js.map +1 -0
- package/dist/factories/simple/config/config-map.d.ts +2 -2
- package/dist/factories/simple/config/config-map.d.ts.map +1 -1
- package/dist/factories/simple/config/secret.d.ts +2 -2
- package/dist/factories/simple/config/secret.d.ts.map +1 -1
- package/dist/factories/simple/config/secret.js +28 -0
- package/dist/factories/simple/config/secret.js.map +1 -1
- package/dist/factories/simple/helm/index.d.ts +1 -1
- package/dist/factories/simple/helm/index.d.ts.map +1 -1
- package/dist/factories/simple/helm/index.js.map +1 -1
- package/dist/factories/simple/storage/persistent-volume.js.map +1 -1
- package/dist/factories/simple/types.d.ts +11 -1
- package/dist/factories/simple/types.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.js +3 -0
- package/dist/factories/simple/workloads/deployment.js.map +1 -1
- package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts +1 -0
- package/dist/factories/valkey/compositions/valkey-bootstrap.d.ts.map +1 -1
- package/dist/factories/valkey/compositions/valkey-bootstrap.js +116 -0
- package/dist/factories/valkey/compositions/valkey-bootstrap.js.map +1 -1
- package/dist/factories/valkey/resources/valkey.js +1 -1
- package/dist/factories/valkey/resources/valkey.js.map +1 -1
- package/dist/factories/valkey/types.d.ts +6 -5
- package/dist/factories/valkey/types.d.ts.map +1 -1
- package/dist/factories/valkey/types.js +10 -0
- package/dist/factories/valkey/types.js.map +1 -1
- package/dist/factories/webapp/compositions/web-app-with-processing.d.ts +95 -12
- package/dist/factories/webapp/compositions/web-app-with-processing.d.ts.map +1 -1
- package/dist/factories/webapp/compositions/web-app-with-processing.js +185 -26
- package/dist/factories/webapp/compositions/web-app-with-processing.js.map +1 -1
- package/dist/factories/webapp/index.d.ts +3 -4
- package/dist/factories/webapp/index.d.ts.map +1 -1
- package/dist/factories/webapp/index.js +3 -4
- package/dist/factories/webapp/index.js.map +1 -1
- package/dist/factories/webapp/types.d.ts +60 -2
- package/dist/factories/webapp/types.d.ts.map +1 -1
- package/dist/factories/webapp/types.js +80 -3
- package/dist/factories/webapp/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/shared/brands.d.ts +18 -8
- package/dist/shared/brands.d.ts.map +1 -1
- package/dist/shared/brands.js +19 -9
- package/dist/shared/brands.js.map +1 -1
- package/dist/utils/cel-escape.d.ts +12 -0
- package/dist/utils/cel-escape.d.ts.map +1 -0
- package/dist/utils/cel-escape.js +19 -0
- package/dist/utils/cel-escape.js.map +1 -0
- package/package.json +7 -2
|
@@ -4,17 +4,22 @@
|
|
|
4
4
|
* This module provides the main serialization functions to convert
|
|
5
5
|
* TypeScript resource definitions to Kro ResourceGraphDefinition YAML manifests.
|
|
6
6
|
*/
|
|
7
|
+
import { createCompositionContext, getCurrentCompositionContext, runInStatusBuilderContext, runWithCompositionContext, } from '../composition/context.js';
|
|
7
8
|
import { createDirectResourceFactory } from '../deployment/direct-factory.js';
|
|
8
9
|
import { createKroResourceFactory } from '../deployment/kro-factory.js';
|
|
9
10
|
import { ensureError, ValidationError } from '../errors.js';
|
|
11
|
+
import { CEL_EXPRESSION_BRAND, KUBERNETES_REF_MARKER_SOURCE } from '../../shared/brands.js';
|
|
10
12
|
import { analyzeCompositionBody, applyAnalysisToResources, } from '../expressions/composition/composition-analyzer.js';
|
|
13
|
+
import { remapResourceStatusReferences } from '../expressions/composition/composition-analyzer-helpers.js';
|
|
11
14
|
import { StatusBuilderAnalyzer } from '../expressions/factory/status-builder-analyzer.js';
|
|
12
15
|
import { getComponentLogger } from '../logging/index.js';
|
|
13
|
-
import { setResourceId } from '../metadata/index.js';
|
|
16
|
+
import { getMetadataField, setResourceId } from '../metadata/index.js';
|
|
14
17
|
import { createExternalRefWithoutRegistration, createSchemaProxy } from '../references/index.js';
|
|
15
18
|
import { getKindInfo, getSemanticCandidateKinds } from '../resources/factory-registry.js';
|
|
16
19
|
import { validateResourceGraphDefinition } from '../validation/cel-validator.js';
|
|
17
20
|
import { optimizeStatusMappings } from './cel-optimizer.js';
|
|
21
|
+
import { finalizeCelForKro } from './cel-references.js';
|
|
22
|
+
import { applyTernaryConditionalsToResources } from './kro-post-processing.js';
|
|
18
23
|
import { generateKroSchemaFromArktype } from './schema.js';
|
|
19
24
|
import { runStatusAnalysisPipeline } from './status-analysis-pipeline.js';
|
|
20
25
|
import { serializeResourceGraphToYaml } from './yaml.js';
|
|
@@ -236,16 +241,170 @@ function analyzeAndConvertStatusMappings(definition, statusBuilder, schema, reso
|
|
|
236
241
|
*
|
|
237
242
|
* @internal Exported for testing only
|
|
238
243
|
*/
|
|
239
|
-
function processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger) {
|
|
244
|
+
function processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger, schemaDefinition) {
|
|
240
245
|
let compositionAnalysis = null;
|
|
241
|
-
const analysisState = { appliedToResources: false };
|
|
246
|
+
const analysisState = { appliedToResources: false, ternaryAndOmitApplied: false };
|
|
242
247
|
const originalCompositionFnForAnalysis = statusMappings
|
|
243
248
|
?.__originalCompositionFn;
|
|
244
249
|
if (originalCompositionFnForAnalysis) {
|
|
245
250
|
try {
|
|
246
251
|
const resourceIds = new Set(Object.keys(resourcesWithKeys));
|
|
247
|
-
|
|
248
|
-
|
|
252
|
+
const specJson = schemaDefinition?.spec?.json;
|
|
253
|
+
const optionalFieldNames = specJson ? collectOptionalSpecPaths(specJson) : undefined;
|
|
254
|
+
const nestedStatusDescriptor = Object.getOwnPropertyDescriptor(statusMappings, '__nestedStatusCel');
|
|
255
|
+
const nestedStatusCel = nestedStatusDescriptor?.value;
|
|
256
|
+
compositionAnalysis = analyzeCompositionBody(originalCompositionFnForAnalysis, resourceIds, optionalFieldNames);
|
|
257
|
+
// Differential execution to capture untaken-branch resources.
|
|
258
|
+
//
|
|
259
|
+
// When a composition uses plain JS control flow (`if (!spec.optional) { ... }`),
|
|
260
|
+
// the truthy proxy pass takes the OPPOSITE branch because `!proxy === false`,
|
|
261
|
+
// which means the resource inside the branch is never registered at runtime.
|
|
262
|
+
// We re-execute the composition with optional fields set to `undefined`
|
|
263
|
+
// (where `!undefined === true` triggers those branches) in an ISOLATED
|
|
264
|
+
// composition context and merge any resources captured there into the main
|
|
265
|
+
// resource map. The AST analyzer has already recorded the correct
|
|
266
|
+
// `includeWhen` for each such resource, and `applyAnalysisToResources`
|
|
267
|
+
// (called later during toYaml()) attaches that metadata — so the final
|
|
268
|
+
// RGD emits the resource with a CEL `has()` / `!has()` conditional
|
|
269
|
+
// derived from the composition's native `if`/`else` statements.
|
|
270
|
+
//
|
|
271
|
+
// Prerequisites: we need the schema definition to know which fields
|
|
272
|
+
// are optional (so we set them to undefined) and which are required
|
|
273
|
+
// (so we set them to a sentinel that prevents the composition from
|
|
274
|
+
// dereferencing undefined). Both runs use the same composition
|
|
275
|
+
// function, so resource IDs and factory calls are deterministic.
|
|
276
|
+
//
|
|
277
|
+
// SKIP when this composition is being executed as a nested call
|
|
278
|
+
// (`context.isNestedCall === true`). The inner composition's own
|
|
279
|
+
// definition-time pass already captured its hybrid-branch analysis
|
|
280
|
+
// with the INNER schema proxy. Re-running that hybrid capture here —
|
|
281
|
+
// against the fresh inner schema proxy that `captureHybridRunResources`
|
|
282
|
+
// creates — would emit differential CEL conditionals that reference
|
|
283
|
+
// inner-schema fields (e.g., `has(schema.spec.secretKeyRef)`) which
|
|
284
|
+
// don't exist in the outer RGD. The outer composition is the
|
|
285
|
+
// authority on branch conditions for its own calls; the inner's
|
|
286
|
+
// branch shape is driven by what the outer passed in.
|
|
287
|
+
// ── Resource-status ternary compilation (Phases 3+4) ────────────
|
|
288
|
+
//
|
|
289
|
+
// When the AST detects ternaries conditioned on resource status
|
|
290
|
+
// fields (e.g., `cache.status.ready ? 'redis' : 'memory'`), proxy
|
|
291
|
+
// JS evaluation does not necessarily match CEL truth evaluation.
|
|
292
|
+
// To emit the CEL conditional, re-execute explicit true and false
|
|
293
|
+
// branches using `liveStatusMap`, then diff those branch outputs.
|
|
294
|
+
//
|
|
295
|
+
// To avoid false positives (other fields changing due to the status
|
|
296
|
+
// flip), direct factory calls diff only `callSiteResourceId`; nested
|
|
297
|
+
// composition call arguments diff only resources registered under known
|
|
298
|
+
// nested composition base IDs.
|
|
299
|
+
const resourceStatusTernaries = compositionAnalysis.resourceStatusTernaries;
|
|
300
|
+
// Deduplicate by call site and condition. Conditionalization is scoped to
|
|
301
|
+
// one callSiteResourceId, so two resources using the same status condition
|
|
302
|
+
// must both be processed.
|
|
303
|
+
const seenConditions = new Set();
|
|
304
|
+
const uniqueTernaries = resourceStatusTernaries.filter((t) => {
|
|
305
|
+
const key = `${t.callSiteResourceId}:${t.variableName}:${t.conditionExpression ?? t.statusField}`;
|
|
306
|
+
if (seenConditions.has(key))
|
|
307
|
+
return false;
|
|
308
|
+
seenConditions.add(key);
|
|
309
|
+
return true;
|
|
310
|
+
});
|
|
311
|
+
// Process EACH resource-status ternary independently to avoid
|
|
312
|
+
// cross-contamination: flip ONE condition → run → diff → apply.
|
|
313
|
+
// Multiple ternaries on the same resource get independent conditionals.
|
|
314
|
+
for (const ternary of uniqueTernaries) {
|
|
315
|
+
const resId = compositionAnalysis.variableToResourceId.get(ternary.variableName) ??
|
|
316
|
+
ternary.variableName;
|
|
317
|
+
if (!resourceIds.has(resId))
|
|
318
|
+
continue;
|
|
319
|
+
const conditionCel = ternary.conditionExpression
|
|
320
|
+
? remapResourceStatusReferences(ternary.conditionExpression, new Map(compositionAnalysis.variableToResourceId).set(ternary.variableName, resId))
|
|
321
|
+
: `${resId}.status.${ternary.statusField}`;
|
|
322
|
+
const trueCtx = runResourceStatusBranch(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis, ternary, true);
|
|
323
|
+
const falseCtx = runResourceStatusBranch(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis, ternary, false);
|
|
324
|
+
// Diff ONLY the targeted resource(s)
|
|
325
|
+
const targetIds = ternary.callSiteResourceId && ternary.callSiteResourceId !== '__non_factory_call__'
|
|
326
|
+
? [ternary.callSiteResourceId]
|
|
327
|
+
: getNestedResourceStatusTargetIds(trueCtx.resources, trueCtx.nestedCompositionIds);
|
|
328
|
+
for (const id of targetIds) {
|
|
329
|
+
const targetRes = resourcesWithKeys[id];
|
|
330
|
+
const trueRes = trueCtx.resources[id];
|
|
331
|
+
const falseRes = falseCtx.resources[id];
|
|
332
|
+
if (targetRes && trueRes && falseRes) {
|
|
333
|
+
applyResourceStatusBranchDiff(targetRes, trueRes, falseRes, conditionCel, nestedStatusCel, resourceIds);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
serializationLogger.debug('Resource-status branch runs applied', {
|
|
337
|
+
conditionCel,
|
|
338
|
+
targetIds,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const currentCtx = getCurrentCompositionContext();
|
|
342
|
+
const skipHybridCapture = currentCtx?.isNestedCall === true;
|
|
343
|
+
if (!skipHybridCapture &&
|
|
344
|
+
schemaDefinition &&
|
|
345
|
+
(compositionAnalysis.unregisteredFactories.length > 0 ||
|
|
346
|
+
collectOverridableOptionalFields(schemaDefinition, compositionAnalysis).size > 0)) {
|
|
347
|
+
const { captured, overriddenFields, overrideConditions } = captureHybridRunResources(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis);
|
|
348
|
+
// (a) Merge resources that exist ONLY in the hybrid run — these
|
|
349
|
+
// come from branches the proxy run didn't take (e.g., `if (!spec.x)`).
|
|
350
|
+
// The AST analyzer has already attached the appropriate includeWhen.
|
|
351
|
+
for (const [id, resource] of Object.entries(captured)) {
|
|
352
|
+
if (!resourceIds.has(id)) {
|
|
353
|
+
resourcesWithKeys[id] = resource;
|
|
354
|
+
resourceIds.add(id);
|
|
355
|
+
serializationLogger.debug('Captured resource from untaken branch', {
|
|
356
|
+
resourceId: id,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// (b) For resources that exist in BOTH runs, walk them in parallel
|
|
361
|
+
// and detect field-level differences. Each differing leaf becomes
|
|
362
|
+
// a CEL `has(...) ? <proxy value> : <hybrid value>` conditional
|
|
363
|
+
// applied in place on the proxy-run resource. This covers the
|
|
364
|
+
// "ternary inside a custom factory's internal transformation" case
|
|
365
|
+
// where the AST analyzer can't see the final template path, since
|
|
366
|
+
// the comparison works on the emitted resource structure directly.
|
|
367
|
+
//
|
|
368
|
+
// MUTATION SAFETY: this step replaces LEAF values (strings, numbers,
|
|
369
|
+
// KubernetesRef proxies) inside the proxy-run resources with CEL
|
|
370
|
+
// conditional strings. It runs after status analysis has captured
|
|
371
|
+
// its own references (so status builders see the pre-mutation state)
|
|
372
|
+
// and before YAML serialization (so the mutated state is what gets
|
|
373
|
+
// emitted). The tree structure is preserved; only leaves change.
|
|
374
|
+
// `processCompositionBodyAnalysis` is called exactly once per
|
|
375
|
+
// `toResourceGraph` invocation, and the walk itself is idempotent
|
|
376
|
+
// (a second pass finds `leafEquals` true for every leaf and no-ops),
|
|
377
|
+
// so multiple `toYaml()` calls on the resulting TypedResourceGraph
|
|
378
|
+
// all see a consistent final state.
|
|
379
|
+
if (overriddenFields.size > 0) {
|
|
380
|
+
const baselineResources = Object.fromEntries(Object.entries(resourcesWithKeys).map(([id, resource]) => [
|
|
381
|
+
id,
|
|
382
|
+
cloneResourceTree(resource),
|
|
383
|
+
]));
|
|
384
|
+
const differentialFields = collectDifferentialOptionalFields(compositionAnalysis);
|
|
385
|
+
const fieldsToDiff = Array.from(differentialFields).filter((field) => overriddenFields.has(field));
|
|
386
|
+
for (const field of fieldsToDiff) {
|
|
387
|
+
const singleFieldSet = new Set([field]);
|
|
388
|
+
const { captured: fieldCaptured } = captureHybridRunResources(originalCompositionFnForAnalysis, schemaDefinition, compositionAnalysis, singleFieldSet);
|
|
389
|
+
const fieldConditions = new Map();
|
|
390
|
+
const explicitCondition = overrideConditions.get(field);
|
|
391
|
+
if (explicitCondition) {
|
|
392
|
+
fieldConditions.set(field, explicitCondition);
|
|
393
|
+
}
|
|
394
|
+
for (const id of Object.keys(resourcesWithKeys)) {
|
|
395
|
+
const proxyRes = resourcesWithKeys[id];
|
|
396
|
+
const baselineRes = baselineResources[id];
|
|
397
|
+
const hybridRes = fieldCaptured[id];
|
|
398
|
+
if (proxyRes && baselineRes && hybridRes) {
|
|
399
|
+
applyDifferentialFieldConditionals(proxyRes, baselineRes, hybridRes, singleFieldSet, fieldConditions, nestedStatusCel, resourceIds);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Create stub resources for factory calls that STILL weren't registered
|
|
406
|
+
// (e.g., branches the differential run also didn't take because they
|
|
407
|
+
// required a different condition to be truthy).
|
|
249
408
|
for (const unregistered of compositionAnalysis.unregisteredFactories) {
|
|
250
409
|
if (!resourceIds.has(unregistered.resourceId)) {
|
|
251
410
|
const stub = createStubResource(unregistered.factoryName, unregistered.resourceId);
|
|
@@ -276,6 +435,752 @@ function processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analy
|
|
|
276
435
|
}
|
|
277
436
|
return { compositionAnalysis, analysisState };
|
|
278
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Collect the top-level optional spec fields that should be overridden
|
|
440
|
+
* when running the composition for differential branch/field capture.
|
|
441
|
+
*
|
|
442
|
+
* We only override fields that the AST analyzer observed being TESTED
|
|
443
|
+
* in a condition — `if (spec.x)`, `spec.x ? a : b`, or similar.
|
|
444
|
+
* Overriding fields that the composition only READS unconditionally
|
|
445
|
+
* (e.g., `spec.server.secret_key` inside a ConfigMap's stringData) is
|
|
446
|
+
* counterproductive: it would replace those proxy references with
|
|
447
|
+
* `undefined` in the hybrid run, producing incorrect captured
|
|
448
|
+
* resources.
|
|
449
|
+
*
|
|
450
|
+
* The tested fields are extracted from the AST analyzer's recorded
|
|
451
|
+
* `includeWhen` expressions (which, after the `conditionToCel` fix,
|
|
452
|
+
* wrap bare truthiness checks with `has(...)`) by scanning for
|
|
453
|
+
* `schema.spec.<field>` paths.
|
|
454
|
+
*/
|
|
455
|
+
function collectOverridableOptionalFields(schemaDefinition, analysis) {
|
|
456
|
+
const specJson = schemaDefinition.spec.json;
|
|
457
|
+
if (!specJson)
|
|
458
|
+
return new Set();
|
|
459
|
+
const differentialFields = collectDifferentialOptionalFields(analysis);
|
|
460
|
+
return new Set(Array.from(collectOptionalSpecPaths(specJson))
|
|
461
|
+
.filter((field) => differentialFields.has(field)));
|
|
462
|
+
}
|
|
463
|
+
function collectOptionalSpecPaths(schemaJson, prefix = '') {
|
|
464
|
+
const paths = new Set();
|
|
465
|
+
if (!schemaJson || typeof schemaJson !== 'object')
|
|
466
|
+
return paths;
|
|
467
|
+
const node = schemaJson;
|
|
468
|
+
for (const entry of node.optional ?? []) {
|
|
469
|
+
if (!entry.key)
|
|
470
|
+
continue;
|
|
471
|
+
const path = prefix ? `${prefix}.${entry.key}` : entry.key;
|
|
472
|
+
paths.add(path);
|
|
473
|
+
for (const childPath of collectOptionalSpecPaths(entry.value, path)) {
|
|
474
|
+
paths.add(childPath);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
for (const entry of node.required ?? []) {
|
|
478
|
+
if (!entry.key)
|
|
479
|
+
continue;
|
|
480
|
+
const path = prefix ? `${prefix}.${entry.key}` : entry.key;
|
|
481
|
+
for (const childPath of collectOptionalSpecPaths(entry.value, path)) {
|
|
482
|
+
paths.add(childPath);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return paths;
|
|
486
|
+
}
|
|
487
|
+
function collectDifferentialOptionalFields(analysis) {
|
|
488
|
+
const fields = new Set(analysis.hybridOverrideConditions.keys());
|
|
489
|
+
for (const field of analysis.differentialConditionFields) {
|
|
490
|
+
fields.add(field);
|
|
491
|
+
}
|
|
492
|
+
for (const controlFlow of analysis.resources.values()) {
|
|
493
|
+
for (const condition of controlFlow.includeWhen) {
|
|
494
|
+
const expression = condition.expression;
|
|
495
|
+
const matches = expression.matchAll(/schema\.spec\.([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)/g);
|
|
496
|
+
for (const match of matches) {
|
|
497
|
+
const field = match[1];
|
|
498
|
+
if (field) {
|
|
499
|
+
fields.add(field);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return fields;
|
|
505
|
+
}
|
|
506
|
+
function collectHybridOverrideValues(analysis) {
|
|
507
|
+
const overrideValues = new Map();
|
|
508
|
+
for (const [field, expression] of analysis.hybridOverrideConditions.entries()) {
|
|
509
|
+
const match = expression.match(/^schema\.spec\.([a-zA-Z0-9_.]+)\s*!=\s*false$/);
|
|
510
|
+
const path = match?.[1];
|
|
511
|
+
if (!path || path !== field || overrideValues.has(field))
|
|
512
|
+
continue;
|
|
513
|
+
overrideValues.set(field, false);
|
|
514
|
+
}
|
|
515
|
+
return overrideValues;
|
|
516
|
+
}
|
|
517
|
+
function collectHybridOverrideConditions(analysis) {
|
|
518
|
+
return new Map(analysis.hybridOverrideConditions);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Re-execute the composition function in an isolated composition context
|
|
522
|
+
* with a HYBRID schema spec — the original schema proxy for most fields
|
|
523
|
+
* (so `spec.name`, `spec.server.secret_key`, etc. still produce
|
|
524
|
+
* `KubernetesRef` proxy values that serialize into CEL references) but
|
|
525
|
+
* with specific top-level optional fields overridden to `undefined` so
|
|
526
|
+
* that `if (!spec.x)`, `if (spec.x === undefined)`, and similar
|
|
527
|
+
* field-presence tests take the opposite branch from the proxy run.
|
|
528
|
+
*
|
|
529
|
+
* Used by {@link processCompositionBodyAnalysis} to capture BOTH:
|
|
530
|
+
* (a) resources registered inside branches that the main proxy run
|
|
531
|
+
* skipped because `!proxy === false`
|
|
532
|
+
* (b) resources that exist in both runs but with field-level
|
|
533
|
+
* differences caused by ternary/fallback logic that depends on
|
|
534
|
+
* the overridden fields — used for differential CEL conditional
|
|
535
|
+
* emission by {@link applyDifferentialFieldConditionals}
|
|
536
|
+
*
|
|
537
|
+
* The branches run with real proxy values for every field EXCEPT the
|
|
538
|
+
* overridden ones, so the captured resources contain the correct CEL
|
|
539
|
+
* references just like the proxy-run resources.
|
|
540
|
+
*
|
|
541
|
+
* Composition functions with external side effects will fire those
|
|
542
|
+
* effects a second time during this re-execution — see integration-skill
|
|
543
|
+
* rule #30 for the full contract.
|
|
544
|
+
*/
|
|
545
|
+
function captureHybridRunResources(compositionFn, schemaDefinition, analysis, fieldsToOverride) {
|
|
546
|
+
try {
|
|
547
|
+
const allOverriddenFields = collectOverridableOptionalFields(schemaDefinition, analysis);
|
|
548
|
+
const overriddenFields = fieldsToOverride
|
|
549
|
+
? new Set(Array.from(allOverriddenFields).filter((field) => fieldsToOverride.has(field)))
|
|
550
|
+
: allOverriddenFields;
|
|
551
|
+
if (overriddenFields.size === 0) {
|
|
552
|
+
return { captured: {}, overriddenFields, overrideConditions: new Map() };
|
|
553
|
+
}
|
|
554
|
+
const overrideValues = new Map(Array.from(collectHybridOverrideValues(analysis)).filter(([field]) => overriddenFields.has(field)));
|
|
555
|
+
const overrideConditions = new Map(Array.from(collectHybridOverrideConditions(analysis)).filter(([field]) => overriddenFields.has(field)));
|
|
556
|
+
// Build the hybrid spec: the real schema proxy (so all other field
|
|
557
|
+
// accesses produce KubernetesRef values) wrapped in a Proxy that
|
|
558
|
+
// intercepts the overridden keys and returns `undefined`. Accessing
|
|
559
|
+
// an overridden key's sub-property (e.g., `spec.secretKeyRef.name`)
|
|
560
|
+
// will throw — but that's exactly the code path the override is
|
|
561
|
+
// meant to skip, so the thrown access lives inside the `else`
|
|
562
|
+
// branch that this run intentionally does not execute.
|
|
563
|
+
const realSchema = createSchemaProxy(schemaDefinition.spec?.json, schemaDefinition.status?.json);
|
|
564
|
+
const hybridSpec = createHybridSpecProxy(realSchema.spec, overriddenFields, overrideValues);
|
|
565
|
+
const tempCtx = createCompositionContext('hybrid-capture');
|
|
566
|
+
runWithCompositionContext(tempCtx, () => {
|
|
567
|
+
compositionFn(hybridSpec);
|
|
568
|
+
});
|
|
569
|
+
return {
|
|
570
|
+
captured: tempCtx.resources,
|
|
571
|
+
overriddenFields,
|
|
572
|
+
overrideConditions,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
// Best-effort: compositions that throw when running with a hybrid spec
|
|
577
|
+
// degrade gracefully — stub resources still cover the missing factories
|
|
578
|
+
// and the proxy-run resources are used as-is.
|
|
579
|
+
return { captured: {}, overriddenFields: new Set(), overrideConditions: new Map() };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function createHybridSpecProxy(target, overriddenFields, overrideValues, pathPrefix = '') {
|
|
583
|
+
return new Proxy(target, {
|
|
584
|
+
get(proxyTarget, prop, receiver) {
|
|
585
|
+
if (typeof prop !== 'string') {
|
|
586
|
+
return Reflect.get(proxyTarget, prop, receiver);
|
|
587
|
+
}
|
|
588
|
+
const path = pathPrefix ? `${pathPrefix}.${prop}` : prop;
|
|
589
|
+
if (overriddenFields.has(path)) {
|
|
590
|
+
return overrideValues.has(path) ? overrideValues.get(path) : undefined;
|
|
591
|
+
}
|
|
592
|
+
const hasNestedOverride = Array.from(overriddenFields).some((field) => field.startsWith(`${path}.`));
|
|
593
|
+
const value = Reflect.get(proxyTarget, prop, receiver);
|
|
594
|
+
if (hasNestedOverride && value && (typeof value === 'object' || typeof value === 'function')) {
|
|
595
|
+
return createHybridSpecProxy(value, overriddenFields, overrideValues, path);
|
|
596
|
+
}
|
|
597
|
+
return value;
|
|
598
|
+
},
|
|
599
|
+
has(proxyTarget, prop) {
|
|
600
|
+
if (typeof prop !== 'string') {
|
|
601
|
+
return Reflect.has(proxyTarget, prop);
|
|
602
|
+
}
|
|
603
|
+
const path = pathPrefix ? `${pathPrefix}.${prop}` : prop;
|
|
604
|
+
if (overriddenFields.has(path)) {
|
|
605
|
+
return overrideValues.has(path);
|
|
606
|
+
}
|
|
607
|
+
return Reflect.has(proxyTarget, prop);
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
function cloneResourceTree(value) {
|
|
612
|
+
if (Array.isArray(value)) {
|
|
613
|
+
return value.map((item) => cloneResourceTree(item));
|
|
614
|
+
}
|
|
615
|
+
if (isPlainObject(value)) {
|
|
616
|
+
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, cloneResourceTree(entryValue)]));
|
|
617
|
+
}
|
|
618
|
+
return value;
|
|
619
|
+
}
|
|
620
|
+
function structuralEquals(a, b) {
|
|
621
|
+
if (isLeafValue(a) || isLeafValue(b)) {
|
|
622
|
+
return leafEquals(a, b);
|
|
623
|
+
}
|
|
624
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
625
|
+
return a.length === b.length && a.every((item, index) => structuralEquals(item, b[index]));
|
|
626
|
+
}
|
|
627
|
+
if (isWalkableRecord(a) && isWalkableRecord(b)) {
|
|
628
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
629
|
+
for (const key of keys) {
|
|
630
|
+
if (!structuralEquals(a[key], b[key])) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
return a === b;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Walk `proxyRes` and `hybridRes` in parallel and replace any leaf value
|
|
640
|
+
* in `proxyRes` that differs from the corresponding leaf in `hybridRes`
|
|
641
|
+
* with a CEL `has(...) ? <proxy value> : <hybrid value>` conditional.
|
|
642
|
+
*
|
|
643
|
+
* This is how differential field-level branch detection works: when a
|
|
644
|
+
* composition writes `spec.x ? foo(spec.y) : bar()` at a deep nested
|
|
645
|
+
* location inside a custom factory's emitted resource, the proxy run
|
|
646
|
+
* produces one leaf and the hybrid run (with `spec.x` overridden to
|
|
647
|
+
* `undefined`) produces the other. Comparing the two resource trees
|
|
648
|
+
* field-by-field surfaces the divergence and lets us emit the correct
|
|
649
|
+
* KRO CEL conditional without needing to thread AST information through
|
|
650
|
+
* the factory's internal transformations.
|
|
651
|
+
*
|
|
652
|
+
* Assumptions and caveats:
|
|
653
|
+
* - Both resources come from the same factory call with the same
|
|
654
|
+
* resource ID, so their structures are shape-compatible.
|
|
655
|
+
* - The `overriddenFields` set drives the `has(...)` check — with a
|
|
656
|
+
* single overridden field we can emit a direct `has(schema.spec.X)`
|
|
657
|
+
* test. With multiple fields, we use the first one that appears in
|
|
658
|
+
* either leaf value; this is a heuristic, but in practice
|
|
659
|
+
* compositions rarely have multiple optional fields driving the
|
|
660
|
+
* same leaf.
|
|
661
|
+
* - Marker strings (`__KUBERNETES_REF__...__`) and CEL expressions
|
|
662
|
+
* are converted to their dollar-wrapped forms before embedding in
|
|
663
|
+
* the conditional.
|
|
664
|
+
*/
|
|
665
|
+
function applyDifferentialFieldConditionals(currentRes, baselineRes, hybridRes, overriddenFields, overrideConditions, nestedStatusCel, resourceIds) {
|
|
666
|
+
walkAndConditionalize(currentRes, baselineRes, hybridRes, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
|
|
667
|
+
}
|
|
668
|
+
function walkAndConditionalize(current, baseline, hybrid, overriddenFields, overrideConditions, nestedStatusCel, resourceIds) {
|
|
669
|
+
if (Array.isArray(current) && Array.isArray(baseline) && Array.isArray(hybrid)) {
|
|
670
|
+
if (baseline.length !== hybrid.length) {
|
|
671
|
+
return structuralEquals(current, baseline)
|
|
672
|
+
? buildCelConditional(baseline, hybrid, overriddenFields, overrideConditions, nestedStatusCel, resourceIds)
|
|
673
|
+
: current;
|
|
674
|
+
}
|
|
675
|
+
const maxLen = Math.max(current.length, hybrid.length);
|
|
676
|
+
for (let i = 0; i < maxLen; i++) {
|
|
677
|
+
const c = current[i];
|
|
678
|
+
const b = baseline[i];
|
|
679
|
+
const h = hybrid[i];
|
|
680
|
+
if (i >= current.length) {
|
|
681
|
+
// New element added by hybrid run — copy it over.
|
|
682
|
+
current.push(h);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (i >= hybrid.length) {
|
|
686
|
+
// Element removed in hybrid — leave the proxy value as-is.
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
if (isLeafValue(b) && isLeafValue(h)) {
|
|
690
|
+
if (!leafEquals(b, h) && leafEquals(c, b)) {
|
|
691
|
+
current[i] = buildCelConditional(b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
const conditionalized = walkAndConditionalize(c, b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
|
|
696
|
+
if (conditionalized !== c) {
|
|
697
|
+
current[i] = conditionalized;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return current;
|
|
702
|
+
}
|
|
703
|
+
if (isWalkableRecord(current) && isWalkableRecord(baseline) && isWalkableRecord(hybrid)) {
|
|
704
|
+
const keys = new Set([...Object.keys(current), ...Object.keys(baseline), ...Object.keys(hybrid)]);
|
|
705
|
+
for (const key of keys) {
|
|
706
|
+
const c = current[key];
|
|
707
|
+
const b = baseline[key];
|
|
708
|
+
const h = hybrid[key];
|
|
709
|
+
if (!(key in hybrid)) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (!(key in current)) {
|
|
713
|
+
current[key] = h;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (isLeafValue(b) && isLeafValue(h)) {
|
|
717
|
+
if (!leafEquals(b, h) && leafEquals(c, b)) {
|
|
718
|
+
current[key] = buildCelConditional(b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
const conditionalized = walkAndConditionalize(c, b, h, overriddenFields, overrideConditions, nestedStatusCel, resourceIds);
|
|
723
|
+
if (conditionalized !== c) {
|
|
724
|
+
current[key] = conditionalized;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return current;
|
|
729
|
+
}
|
|
730
|
+
return current;
|
|
731
|
+
}
|
|
732
|
+
function isLeafValue(v) {
|
|
733
|
+
return (v === null ||
|
|
734
|
+
v === undefined ||
|
|
735
|
+
typeof v === 'string' ||
|
|
736
|
+
typeof v === 'number' ||
|
|
737
|
+
typeof v === 'boolean' ||
|
|
738
|
+
isCelExpressionLike(v) ||
|
|
739
|
+
// KubernetesRef proxies register as functions (typeof fn is 'function')
|
|
740
|
+
typeof v === 'function');
|
|
741
|
+
}
|
|
742
|
+
function runResourceStatusBranch(compositionFn, schemaDefinition, analysis, ternary, desiredConditionValue) {
|
|
743
|
+
const branchCtx = createCompositionContext('resource-status-branch', {
|
|
744
|
+
isReExecution: true,
|
|
745
|
+
});
|
|
746
|
+
branchCtx.liveStatusMap = createResourceStatusBranchMap(analysis, ternary, desiredConditionValue);
|
|
747
|
+
const branchSchema = createSchemaProxy(schemaDefinition?.spec?.json, schemaDefinition?.status?.json);
|
|
748
|
+
const specOverrides = createSpecConditionOverrideMap(ternary.conditionExpression, desiredConditionValue);
|
|
749
|
+
const branchSpec = specOverrides.size > 0
|
|
750
|
+
? createSpecOverrideProxy(branchSchema.spec, specOverrides)
|
|
751
|
+
: branchSchema.spec;
|
|
752
|
+
runWithCompositionContext(branchCtx, () => {
|
|
753
|
+
runInStatusBuilderContext(() => {
|
|
754
|
+
compositionFn(branchSpec);
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
return branchCtx;
|
|
758
|
+
}
|
|
759
|
+
function createResourceStatusBranchMap(analysis, ternary, desiredConditionValue) {
|
|
760
|
+
const conditionExpression = ternary.conditionExpression ?? `${ternary.variableName}.status.${ternary.statusField}`;
|
|
761
|
+
const statusRefs = collectStatusRefs(conditionExpression);
|
|
762
|
+
if (statusRefs.length === 0) {
|
|
763
|
+
statusRefs.push({ variableName: ternary.variableName, statusField: ternary.statusField });
|
|
764
|
+
}
|
|
765
|
+
const statusMap = new Map();
|
|
766
|
+
for (const statusRef of statusRefs) {
|
|
767
|
+
const resourceId = analysis.variableToResourceId.get(statusRef.variableName) ?? statusRef.variableName;
|
|
768
|
+
const existing = statusMap.get(resourceId) ?? {};
|
|
769
|
+
setNestedBranchStatusValue(existing, statusRef.statusField, getBranchStatusValue(conditionExpression, `${statusRef.variableName}.status.${statusRef.statusField}`, desiredConditionValue));
|
|
770
|
+
statusMap.set(resourceId, existing);
|
|
771
|
+
}
|
|
772
|
+
return statusMap;
|
|
773
|
+
}
|
|
774
|
+
function setNestedBranchStatusValue(target, statusField, value) {
|
|
775
|
+
const parts = statusField.split('.').filter(Boolean);
|
|
776
|
+
if (parts.length === 0)
|
|
777
|
+
return;
|
|
778
|
+
let cursor = target;
|
|
779
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
780
|
+
const part = parts[i];
|
|
781
|
+
if (!part)
|
|
782
|
+
continue;
|
|
783
|
+
const next = cursor[part];
|
|
784
|
+
if (!isPlainObject(next)) {
|
|
785
|
+
cursor[part] = {};
|
|
786
|
+
}
|
|
787
|
+
cursor = cursor[part];
|
|
788
|
+
}
|
|
789
|
+
const leaf = parts[parts.length - 1];
|
|
790
|
+
if (leaf) {
|
|
791
|
+
cursor[leaf] = value;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
function collectStatusRefs(conditionExpression) {
|
|
795
|
+
const refs = [];
|
|
796
|
+
const seen = new Set();
|
|
797
|
+
const statusRefPattern = /\b([A-Za-z_$][\w$]*)\.status\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
798
|
+
for (const match of conditionExpression.matchAll(statusRefPattern)) {
|
|
799
|
+
const variableName = match[1];
|
|
800
|
+
const statusField = match[2];
|
|
801
|
+
if (!variableName || !statusField)
|
|
802
|
+
continue;
|
|
803
|
+
const key = `${variableName}:${statusField}`;
|
|
804
|
+
if (seen.has(key))
|
|
805
|
+
continue;
|
|
806
|
+
seen.add(key);
|
|
807
|
+
refs.push({ variableName, statusField });
|
|
808
|
+
}
|
|
809
|
+
return refs;
|
|
810
|
+
}
|
|
811
|
+
function createSpecConditionOverrideMap(conditionExpression, desiredConditionValue) {
|
|
812
|
+
const overrides = new Map();
|
|
813
|
+
if (!conditionExpression)
|
|
814
|
+
return overrides;
|
|
815
|
+
const specRefPattern = /\b(?:schema\.)?spec\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
816
|
+
for (const match of conditionExpression.matchAll(specRefPattern)) {
|
|
817
|
+
const specPath = match[1];
|
|
818
|
+
const fullRef = match[0];
|
|
819
|
+
if (!specPath || !fullRef || overrides.has(specPath))
|
|
820
|
+
continue;
|
|
821
|
+
overrides.set(specPath, getBranchStatusValue(conditionExpression, fullRef, desiredConditionValue));
|
|
822
|
+
}
|
|
823
|
+
return overrides;
|
|
824
|
+
}
|
|
825
|
+
function createSpecOverrideProxy(target, overrides, path = []) {
|
|
826
|
+
return new Proxy(target, {
|
|
827
|
+
get(obj, prop, receiver) {
|
|
828
|
+
if (typeof prop !== 'string')
|
|
829
|
+
return Reflect.get(obj, prop, receiver);
|
|
830
|
+
const fullPath = [...path, prop].join('.');
|
|
831
|
+
if (overrides.has(fullPath))
|
|
832
|
+
return overrides.get(fullPath);
|
|
833
|
+
const hasNestedOverride = [...overrides.keys()].some((key) => key.startsWith(`${fullPath}.`));
|
|
834
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
835
|
+
if (hasNestedOverride && value && (typeof value === 'object' || typeof value === 'function')) {
|
|
836
|
+
return createSpecOverrideProxy(value, overrides, [...path, prop]);
|
|
837
|
+
}
|
|
838
|
+
return value;
|
|
839
|
+
},
|
|
840
|
+
ownKeys: (obj) => Reflect.ownKeys(obj),
|
|
841
|
+
getOwnPropertyDescriptor: (obj, prop) => Reflect.getOwnPropertyDescriptor(obj, prop),
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
function getBranchStatusValue(conditionExpression, statusRefExpression, desiredConditionValue) {
|
|
845
|
+
const escapedRef = statusRefExpression.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
846
|
+
const comparison = conditionExpression.match(new RegExp(`${escapedRef}\\s*(>=|>|<=|<|==|!=)\\s*(-?\\d+(?:\\.\\d+)?)`));
|
|
847
|
+
if (comparison?.[1] && comparison[2] !== undefined) {
|
|
848
|
+
const operator = comparison[1];
|
|
849
|
+
const numberValue = Number(comparison[2]);
|
|
850
|
+
if (operator === '>=')
|
|
851
|
+
return desiredConditionValue ? numberValue : numberValue - 1;
|
|
852
|
+
if (operator === '>')
|
|
853
|
+
return desiredConditionValue ? numberValue + 1 : numberValue;
|
|
854
|
+
if (operator === '<=')
|
|
855
|
+
return desiredConditionValue ? numberValue : numberValue + 1;
|
|
856
|
+
if (operator === '<')
|
|
857
|
+
return desiredConditionValue ? numberValue - 1 : numberValue;
|
|
858
|
+
if (operator === '==')
|
|
859
|
+
return desiredConditionValue ? numberValue : numberValue + 1;
|
|
860
|
+
if (operator === '!=')
|
|
861
|
+
return desiredConditionValue ? numberValue + 1 : numberValue;
|
|
862
|
+
}
|
|
863
|
+
const stringComparison = conditionExpression.match(new RegExp(`${escapedRef}\\s*(==|!=)\\s*(['"])(.*?)\\2`));
|
|
864
|
+
if (stringComparison?.[1] && stringComparison[3] !== undefined) {
|
|
865
|
+
const operator = stringComparison[1];
|
|
866
|
+
const stringValue = stringComparison[3];
|
|
867
|
+
if (operator === '==')
|
|
868
|
+
return desiredConditionValue ? stringValue : `__typekro_not_${stringValue}`;
|
|
869
|
+
if (operator === '!=')
|
|
870
|
+
return desiredConditionValue ? `__typekro_not_${stringValue}` : stringValue;
|
|
871
|
+
}
|
|
872
|
+
const booleanComparison = conditionExpression.match(new RegExp(`${escapedRef}\\s*(==|!=)\\s*(true|false)`));
|
|
873
|
+
if (booleanComparison?.[1] && booleanComparison[2] !== undefined) {
|
|
874
|
+
const operator = booleanComparison[1];
|
|
875
|
+
const booleanValue = booleanComparison[2] === 'true';
|
|
876
|
+
if (operator === '==')
|
|
877
|
+
return desiredConditionValue ? booleanValue : !booleanValue;
|
|
878
|
+
if (operator === '!=')
|
|
879
|
+
return desiredConditionValue ? !booleanValue : booleanValue;
|
|
880
|
+
}
|
|
881
|
+
const negatedRef = new RegExp(`!\\s*${escapedRef}(?![A-Za-z0-9_$.])`).test(conditionExpression);
|
|
882
|
+
return negatedRef ? !desiredConditionValue : desiredConditionValue;
|
|
883
|
+
}
|
|
884
|
+
function applyResourceStatusBranchDiff(targetRes, trueRes, falseRes, conditionCel, nestedStatusCel, resourceIds) {
|
|
885
|
+
for (const key of new Set([...Object.keys(trueRes), ...Object.keys(falseRes)])) {
|
|
886
|
+
if (key === '__resourceId' || key === 'id' || key.startsWith('__'))
|
|
887
|
+
continue;
|
|
888
|
+
const tv = trueRes[key];
|
|
889
|
+
const fv = falseRes[key];
|
|
890
|
+
if (tv === undefined && fv !== undefined) {
|
|
891
|
+
const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
|
|
892
|
+
targetRes[key] = `\${${conditionCel} ? omit() : ${falseRepr}}`;
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (fv === undefined && tv !== undefined) {
|
|
896
|
+
const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
|
|
897
|
+
targetRes[key] = `\${${conditionCel} ? ${trueRepr} : omit()}`;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
if (tv === undefined || fv === undefined)
|
|
901
|
+
continue;
|
|
902
|
+
if (isCelExpressionLike(tv) || isCelExpressionLike(fv)) {
|
|
903
|
+
if (!leafEquals(tv, fv)) {
|
|
904
|
+
const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
|
|
905
|
+
const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
|
|
906
|
+
targetRes[key] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
|
|
907
|
+
}
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const targetValue = targetRes[key];
|
|
911
|
+
if (isPlainObject(tv) && isPlainObject(fv)) {
|
|
912
|
+
if (!isPlainObject(targetValue))
|
|
913
|
+
targetRes[key] = {};
|
|
914
|
+
applyResourceStatusBranchDiff(targetRes[key], tv, fv, conditionCel, nestedStatusCel, resourceIds);
|
|
915
|
+
}
|
|
916
|
+
else if (Array.isArray(tv) && Array.isArray(fv) && tv.length === fv.length) {
|
|
917
|
+
if (!Array.isArray(targetValue))
|
|
918
|
+
targetRes[key] = [...tv];
|
|
919
|
+
const targetArray = targetRes[key];
|
|
920
|
+
for (let i = 0; i < tv.length; i++) {
|
|
921
|
+
if (isPlainObject(tv[i]) && isPlainObject(fv[i])) {
|
|
922
|
+
if (!isPlainObject(targetArray[i]))
|
|
923
|
+
targetArray[i] = {};
|
|
924
|
+
applyResourceStatusBranchDiff(targetArray[i], tv[i], fv[i], conditionCel, nestedStatusCel, resourceIds);
|
|
925
|
+
}
|
|
926
|
+
else if (!leafEquals(tv[i], fv[i])) {
|
|
927
|
+
const trueRepr = celValueRepr(tv[i], nestedStatusCel, resourceIds);
|
|
928
|
+
const falseRepr = celValueRepr(fv[i], nestedStatusCel, resourceIds);
|
|
929
|
+
targetArray[i] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
else if (Array.isArray(tv) && Array.isArray(fv) && tv.length !== fv.length) {
|
|
934
|
+
const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
|
|
935
|
+
const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
|
|
936
|
+
targetRes[key] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
|
|
937
|
+
}
|
|
938
|
+
else if (!leafEquals(tv, fv)) {
|
|
939
|
+
const trueRepr = celValueRepr(tv, nestedStatusCel, resourceIds);
|
|
940
|
+
const falseRepr = celValueRepr(fv, nestedStatusCel, resourceIds);
|
|
941
|
+
targetRes[key] = `\${${conditionCel} ? ${trueRepr} : ${falseRepr}}`;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function leafEquals(a, b) {
|
|
946
|
+
// For KubernetesRef proxies, compare their string coercions (marker tokens).
|
|
947
|
+
if (typeof a === 'function' || typeof b === 'function') {
|
|
948
|
+
return String(a) === String(b);
|
|
949
|
+
}
|
|
950
|
+
return a === b;
|
|
951
|
+
}
|
|
952
|
+
function getNestedResourceStatusTargetIds(resources, nestedCompositionIds) {
|
|
953
|
+
if (!nestedCompositionIds || nestedCompositionIds.size === 0)
|
|
954
|
+
return [];
|
|
955
|
+
return Object.entries(resources)
|
|
956
|
+
.filter(([resourceId, resource]) => [...nestedCompositionIds].some((nestedId) => isNestedCompositionChild(resourceId, resource, nestedId)))
|
|
957
|
+
.map(([resourceId]) => resourceId);
|
|
958
|
+
}
|
|
959
|
+
function isNestedCompositionChild(resourceId, resource, nestedId) {
|
|
960
|
+
if (resourceId === nestedId)
|
|
961
|
+
return true;
|
|
962
|
+
const boundaryChar = resourceId[nestedId.length];
|
|
963
|
+
if (resourceId.startsWith(nestedId) && boundaryChar !== undefined && /[A-Z_-]/.test(boundaryChar)) {
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
966
|
+
const aliases = getMetadataField(resource, 'resourceAliases');
|
|
967
|
+
return aliases?.some((alias) => {
|
|
968
|
+
if (alias === nestedId)
|
|
969
|
+
return true;
|
|
970
|
+
const aliasBoundaryChar = alias[nestedId.length];
|
|
971
|
+
return alias.startsWith(nestedId) && aliasBoundaryChar !== undefined && /[A-Z_-]/.test(aliasBoundaryChar);
|
|
972
|
+
}) ?? false;
|
|
973
|
+
}
|
|
974
|
+
function isPlainObject(v) {
|
|
975
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v) && !isCelExpressionLike(v);
|
|
976
|
+
}
|
|
977
|
+
function isWalkableRecord(v) {
|
|
978
|
+
if (isPlainObject(v)) {
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
return typeof v === 'function' && Object.keys(v).length > 0;
|
|
982
|
+
}
|
|
983
|
+
/** Duck-type check for CelExpression objects — avoids importing from cel.ts (cycle risk). */
|
|
984
|
+
function isCelExpressionLike(v) {
|
|
985
|
+
if (typeof v !== 'object' || v === null)
|
|
986
|
+
return false;
|
|
987
|
+
// Plain Cel.expr() objects carry the symbol brand; template expressions also
|
|
988
|
+
// carry `__isTemplate`. Support both shapes without importing cel.ts.
|
|
989
|
+
return 'expression' in v && typeof v.expression === 'string'
|
|
990
|
+
&& (v[CEL_EXPRESSION_BRAND] === true || '__isTemplate' in v);
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Build a CEL conditional string from two diverging leaf values.
|
|
994
|
+
*
|
|
995
|
+
* Both values may be concrete primitives, marker strings (from template
|
|
996
|
+
* literals containing proxy references), or KubernetesRef proxies. The
|
|
997
|
+
* returned string is a KRO mixed-template value like
|
|
998
|
+
* `${has(schema.spec.X) ? <proxy repr> : <hybrid repr>}` that later
|
|
999
|
+
* serialization phases treat as a final CEL expression.
|
|
1000
|
+
*/
|
|
1001
|
+
function buildCelConditional(proxyValue, hybridValue, overriddenFields, overrideConditions, nestedStatusCel, resourceIds) {
|
|
1002
|
+
const field = pickConditionField(proxyValue, hybridValue, overriddenFields);
|
|
1003
|
+
const proxyRepr = celValueRepr(proxyValue, nestedStatusCel, resourceIds);
|
|
1004
|
+
const hybridRepr = celValueRepr(hybridValue, nestedStatusCel, resourceIds);
|
|
1005
|
+
const explicitCondition = overrideConditions.get(field);
|
|
1006
|
+
if (explicitCondition) {
|
|
1007
|
+
return `\${${explicitCondition} ? ${proxyRepr} : ${hybridRepr}}`;
|
|
1008
|
+
}
|
|
1009
|
+
// Chain has() guards when the proxy value references a sub-field deeper
|
|
1010
|
+
// than the controlling optional field. A single has(schema.spec.X) is
|
|
1011
|
+
// insufficient when the value accesses X.Y — the user may provide X: {}
|
|
1012
|
+
// without Y, and KRO would fail with "no such key: Y".
|
|
1013
|
+
const guardField = `schema.spec.${field}`;
|
|
1014
|
+
let guard = `has(${guardField})`;
|
|
1015
|
+
// Extract the full schema path from the proxy repr to check depth.
|
|
1016
|
+
// proxyRepr may be a bare path like `schema.spec.cnpgOperator.version`
|
|
1017
|
+
// or wrapped in string() like `string(schema.spec.cnpgOperator.version)`.
|
|
1018
|
+
// Chain has() guards for ALL intermediate levels between the controlling
|
|
1019
|
+
// field and the leaf. For `cnpgOperator.monitoring.enabled`, we need:
|
|
1020
|
+
// has(cnpgOperator) && has(cnpgOperator.monitoring) && has(cnpgOperator.monitoring.enabled)
|
|
1021
|
+
const schemaPathMatch = proxyRepr.match(/schema\.spec\.([a-zA-Z0-9_.]+)/);
|
|
1022
|
+
if (schemaPathMatch) {
|
|
1023
|
+
const fullRefPath = schemaPathMatch[1]?.replace(/\.+$/, '');
|
|
1024
|
+
if (!fullRefPath)
|
|
1025
|
+
return `\${has(${guardField}) ? ${proxyRepr} : ${hybridRepr}}`;
|
|
1026
|
+
const fullPath = `schema.spec.${fullRefPath}`;
|
|
1027
|
+
if (fullPath !== guardField && fullPath.startsWith(`${guardField}.`)) {
|
|
1028
|
+
const guardSegments = field.split('.').length;
|
|
1029
|
+
const leafSegments = fullRefPath.split('.');
|
|
1030
|
+
const guards = [`has(${guardField})`];
|
|
1031
|
+
for (let j = guardSegments + 1; j <= leafSegments.length; j++) {
|
|
1032
|
+
const intermediatePath = `schema.spec.${leafSegments.slice(0, j).join('.')}`;
|
|
1033
|
+
guards.push(`has(${intermediatePath})`);
|
|
1034
|
+
}
|
|
1035
|
+
guard = guards.join(' && ');
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return `\${${guard} ? ${proxyRepr} : ${hybridRepr}}`;
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Pick the "controlling" optional field for a diverging leaf. If exactly
|
|
1042
|
+
* one overridden field's marker appears in either value, use that. If
|
|
1043
|
+
* neither appears explicitly, default to the first field in the set —
|
|
1044
|
+
* this covers the common case of a single overridden field driving the
|
|
1045
|
+
* whole divergence.
|
|
1046
|
+
*/
|
|
1047
|
+
function pickConditionField(proxyValue, hybridValue, overriddenFields) {
|
|
1048
|
+
const proxyStr = String(proxyValue);
|
|
1049
|
+
const hybridStr = String(hybridValue);
|
|
1050
|
+
for (const field of overriddenFields) {
|
|
1051
|
+
const marker = `__KUBERNETES_REF___schema___spec.${field}`;
|
|
1052
|
+
if (proxyStr.includes(marker) || hybridStr.includes(marker)) {
|
|
1053
|
+
return field;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
// Fallback: first overridden field (iteration order = insertion order).
|
|
1057
|
+
// Single-field overrides are the common case so this is usually correct;
|
|
1058
|
+
// the explicit-marker check above handles the multi-field case. When the
|
|
1059
|
+
// fallback fires, the emitted CEL may pick the wrong `has()` condition
|
|
1060
|
+
// for compositions with multiple optional fields driving the same leaf —
|
|
1061
|
+
// log it at debug level so the case is diagnosable if anyone reports
|
|
1062
|
+
// incorrect CEL output.
|
|
1063
|
+
const fallback = overriddenFields.values().next().value ?? '';
|
|
1064
|
+
getComponentLogger('serialization').debug('pickConditionField fallback: no marker matched, using first overridden field', {
|
|
1065
|
+
fallbackField: fallback,
|
|
1066
|
+
overriddenFields: Array.from(overriddenFields),
|
|
1067
|
+
proxyValuePreview: proxyStr.slice(0, 120),
|
|
1068
|
+
hybridValuePreview: hybridStr.slice(0, 120),
|
|
1069
|
+
});
|
|
1070
|
+
return fallback;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Convert a leaf value into its CEL expression representation.
|
|
1074
|
+
*
|
|
1075
|
+
* - Marker strings become `schema.spec.X` paths (bare, no `${}`)
|
|
1076
|
+
* - KubernetesRef proxies become their inner CEL path
|
|
1077
|
+
* - String literals become double-quoted CEL string literals (with
|
|
1078
|
+
* embedded markers converted to string() concatenations so the
|
|
1079
|
+
* result is valid CEL, not just a raw string)
|
|
1080
|
+
* - Numbers and booleans are emitted verbatim
|
|
1081
|
+
*/
|
|
1082
|
+
// Re-export from shared utility for local use. This was previously an
|
|
1083
|
+
// inline copy; the canonical implementation lives in utils/cel-escape.ts.
|
|
1084
|
+
import { escapeCelString as escapeCelLiteral } from '../../utils/cel-escape.js';
|
|
1085
|
+
function celValueRepr(value, nestedStatusCel, resourceIds) {
|
|
1086
|
+
if (value === null || value === undefined)
|
|
1087
|
+
return '""';
|
|
1088
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
1089
|
+
return String(value);
|
|
1090
|
+
// CelExpression object — use the expression string directly.
|
|
1091
|
+
if (isCelExpressionLike(value)) {
|
|
1092
|
+
return unwrapKroExpression(finalizeCelForKro(value.expression, nestedStatusCel, createBranchCelContext(nestedStatusCel, resourceIds)));
|
|
1093
|
+
}
|
|
1094
|
+
if (typeof value === 'function') {
|
|
1095
|
+
// KubernetesRef proxy — toString yields the marker token which we
|
|
1096
|
+
// convert to a bare CEL path via the marker → CEL rules.
|
|
1097
|
+
return markerStringToCelBare(String(value));
|
|
1098
|
+
}
|
|
1099
|
+
if (typeof value === 'string') {
|
|
1100
|
+
if (value.includes('__KUBERNETES_REF_')) {
|
|
1101
|
+
return markerStringToCelExpr(value);
|
|
1102
|
+
}
|
|
1103
|
+
// Plain string literal — escape for CEL embedding
|
|
1104
|
+
return `"${escapeCelLiteral(value)}"`;
|
|
1105
|
+
}
|
|
1106
|
+
if (Array.isArray(value)) {
|
|
1107
|
+
return `[${value.map((item) => celValueRepr(item, nestedStatusCel, resourceIds)).join(', ')}]`;
|
|
1108
|
+
}
|
|
1109
|
+
if (isPlainObject(value)) {
|
|
1110
|
+
return `{${Object.entries(value)
|
|
1111
|
+
.map(([key, entryValue]) => `"${escapeCelLiteral(key)}": ${celValueRepr(entryValue, nestedStatusCel, resourceIds)}`)
|
|
1112
|
+
.join(', ')}}`;
|
|
1113
|
+
}
|
|
1114
|
+
return '""';
|
|
1115
|
+
}
|
|
1116
|
+
function createBranchCelContext(nestedStatusCel, resourceIds) {
|
|
1117
|
+
if (!nestedStatusCel && !resourceIds)
|
|
1118
|
+
return undefined;
|
|
1119
|
+
return {
|
|
1120
|
+
celPrefix: '',
|
|
1121
|
+
resourceIdStrategy: 'deterministic',
|
|
1122
|
+
...(nestedStatusCel ? { nestedStatusCel } : {}),
|
|
1123
|
+
...(resourceIds ? { resourceIds } : {}),
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function unwrapKroExpression(value) {
|
|
1127
|
+
if (value.startsWith('${') && value.endsWith('}') && value.indexOf('${', 2) === -1) {
|
|
1128
|
+
return value.slice(2, -1);
|
|
1129
|
+
}
|
|
1130
|
+
return value;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Convert a single-marker string (the whole string is one marker) to
|
|
1134
|
+
* its bare CEL path form: `schema.spec.X` or `resources.X.field`.
|
|
1135
|
+
*/
|
|
1136
|
+
function markerStringToCelBare(str) {
|
|
1137
|
+
const m = str.match(new RegExp(`^${KUBERNETES_REF_MARKER_SOURCE}$`));
|
|
1138
|
+
if (!m)
|
|
1139
|
+
return markerStringToCelExpr(str);
|
|
1140
|
+
const [, resourceId, fieldPath] = m;
|
|
1141
|
+
return resourceId === '__schema__' ? `schema.${fieldPath}` : `${resourceId}.${fieldPath}`;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Convert a mixed string containing literal text and markers to a CEL
|
|
1145
|
+
* concatenation expression using `string()` wrappers, suitable for
|
|
1146
|
+
* embedding inside a CEL ternary.
|
|
1147
|
+
*/
|
|
1148
|
+
function markerStringToCelExpr(str) {
|
|
1149
|
+
const markerSource = KUBERNETES_REF_MARKER_SOURCE;
|
|
1150
|
+
// Fast path: whole string is a single marker
|
|
1151
|
+
const singleMatch = str.match(new RegExp(`^${markerSource}$`));
|
|
1152
|
+
if (singleMatch) {
|
|
1153
|
+
const [, resourceId, fieldPath] = singleMatch;
|
|
1154
|
+
return resourceId === '__schema__' ? `schema.${fieldPath}` : `${resourceId}.${fieldPath}`;
|
|
1155
|
+
}
|
|
1156
|
+
// Slow path: interleave literal text and markers via CEL string concatenation
|
|
1157
|
+
const parts = [];
|
|
1158
|
+
let lastIndex = 0;
|
|
1159
|
+
const pattern = new RegExp(markerSource, 'g');
|
|
1160
|
+
let m = pattern.exec(str);
|
|
1161
|
+
while (m !== null) {
|
|
1162
|
+
if (m.index > lastIndex) {
|
|
1163
|
+
const literal = str.slice(lastIndex, m.index);
|
|
1164
|
+
parts.push(`"${escapeCelLiteral(literal)}"`);
|
|
1165
|
+
}
|
|
1166
|
+
const resourceId = m[1];
|
|
1167
|
+
const fieldPath = m[2];
|
|
1168
|
+
if (!resourceId || !fieldPath) {
|
|
1169
|
+
m = pattern.exec(str);
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
const celPath = resourceId === '__schema__' ? `schema.${fieldPath}` : `${resourceId}.${fieldPath}`;
|
|
1173
|
+
parts.push(`string(${celPath})`);
|
|
1174
|
+
lastIndex = m.index + m[0].length;
|
|
1175
|
+
m = pattern.exec(str);
|
|
1176
|
+
}
|
|
1177
|
+
if (lastIndex < str.length) {
|
|
1178
|
+
const literal = str.slice(lastIndex);
|
|
1179
|
+
parts.push(`"${escapeCelLiteral(literal)}"`);
|
|
1180
|
+
}
|
|
1181
|
+
const [firstPart] = parts;
|
|
1182
|
+
return parts.length === 1 && firstPart !== undefined ? firstPart : parts.join(' + ');
|
|
1183
|
+
}
|
|
279
1184
|
// =============================================================================
|
|
280
1185
|
// Extracted helper: Direct factory status re-analysis
|
|
281
1186
|
// =============================================================================
|
|
@@ -427,16 +1332,20 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
427
1332
|
const schemaDefinition = {
|
|
428
1333
|
apiVersion: definition.apiVersion || 'v1alpha1',
|
|
429
1334
|
kind: definition.kind,
|
|
1335
|
+
...(definition.group && { group: definition.group }),
|
|
430
1336
|
spec: definition.spec,
|
|
431
1337
|
status: definition.status,
|
|
432
1338
|
};
|
|
433
|
-
|
|
1339
|
+
// Pass the Arktype JSON so the proxy is shape-aware — spread
|
|
1340
|
+
// (`{ ...spec.X }`) and `Object.keys(spec.X)` enumerate declared
|
|
1341
|
+
// fields instead of returning an opaque empty object.
|
|
1342
|
+
const schema = createSchemaProxy(definition.spec?.json, definition.status?.json);
|
|
434
1343
|
const builderResult = resourceBuilder(schema);
|
|
435
1344
|
const { resources: resourcesWithKeys, closures } = separateResourcesAndClosures(builderResult);
|
|
436
1345
|
// 3. Analyze status builder and convert JS expressions to CEL
|
|
437
1346
|
const { statusMappings, analyzedStatusMappings, mappingAnalysis, phaseBStatusMappings } = analyzeAndConvertStatusMappings(definition, statusBuilder, schema, resourcesWithKeys, serializationLogger);
|
|
438
1347
|
// 4. Analyze composition body for control flow patterns (must run before validation)
|
|
439
|
-
const { compositionAnalysis, analysisState } = processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger);
|
|
1348
|
+
const { compositionAnalysis, analysisState } = processCompositionBodyAnalysis(statusMappings, resourcesWithKeys, analyzedStatusMappings, serializationLogger, schemaDefinition);
|
|
440
1349
|
// 5. Validate resource IDs and CEL expressions
|
|
441
1350
|
const validation = validateResourceGraphDefinition(resourcesWithKeys, analyzedStatusMappings);
|
|
442
1351
|
if (!validation.isValid) {
|
|
@@ -455,6 +1364,18 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
455
1364
|
// Evaluate and optimize CEL expressions
|
|
456
1365
|
const evaluationContext = { resources: resourcesWithKeys, schema };
|
|
457
1366
|
const { mappings: optimizedStatusMappings, optimizations } = optimizeStatusMappings(analyzedStatusMappings, evaluationContext);
|
|
1367
|
+
for (const metadataKey of [
|
|
1368
|
+
'__originalCompositionFn',
|
|
1369
|
+
'__nestedCompositionFns',
|
|
1370
|
+
'__nestedCompositionDefinitions',
|
|
1371
|
+
'__nestedCompositionResources',
|
|
1372
|
+
'__nestedCompositionSpecMappings',
|
|
1373
|
+
]) {
|
|
1374
|
+
const descriptor = Object.getOwnPropertyDescriptor(statusMappings, metadataKey);
|
|
1375
|
+
if (descriptor) {
|
|
1376
|
+
Object.defineProperty(optimizedStatusMappings, metadataKey, descriptor);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
458
1379
|
if (optimizations.length > 0) {
|
|
459
1380
|
serializationLogger.info('CEL expression optimizations applied', { optimizations });
|
|
460
1381
|
}
|
|
@@ -489,6 +1410,11 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
489
1410
|
statusMappings: directStatusMappings,
|
|
490
1411
|
compositionFn: declarativeCompositionFn,
|
|
491
1412
|
compositionDefinition: definition,
|
|
1413
|
+
...(this._singletonDefinitions
|
|
1414
|
+
? {
|
|
1415
|
+
singletonDefinitions: this._singletonDefinitions,
|
|
1416
|
+
}
|
|
1417
|
+
: {}),
|
|
492
1418
|
});
|
|
493
1419
|
}
|
|
494
1420
|
else if (mode === 'kro') {
|
|
@@ -497,13 +1423,22 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
497
1423
|
closures,
|
|
498
1424
|
factoryType: 'kro',
|
|
499
1425
|
compositionFn: declarativeCompositionFn,
|
|
1426
|
+
compositionAnalysis,
|
|
1427
|
+
...(this._singletonDefinitions
|
|
1428
|
+
? {
|
|
1429
|
+
singletonDefinitions: this._singletonDefinitions,
|
|
1430
|
+
}
|
|
1431
|
+
: {}),
|
|
500
1432
|
});
|
|
501
1433
|
}
|
|
502
1434
|
else {
|
|
503
1435
|
throw new ValidationError(`Unsupported factory mode: ${mode}`, 'ResourceGraphDefinition', definition.name, 'mode', ['Use "kro" or "direct" as the factory mode']);
|
|
504
1436
|
}
|
|
505
1437
|
},
|
|
506
|
-
toYaml() {
|
|
1438
|
+
toYaml(spec) {
|
|
1439
|
+
if (spec !== undefined) {
|
|
1440
|
+
return this.factory('kro').toYaml(spec);
|
|
1441
|
+
}
|
|
507
1442
|
// Apply composition body analysis results (guard: only once)
|
|
508
1443
|
if (compositionAnalysis && !analysisState.appliedToResources) {
|
|
509
1444
|
analysisState.appliedToResources = true;
|
|
@@ -520,10 +1455,13 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
520
1455
|
// Collect nested composition status CEL mappings from the composition context.
|
|
521
1456
|
// These enable inlining the inner composition's real CEL expressions instead
|
|
522
1457
|
// of referencing virtual nested composition IDs.
|
|
523
|
-
// Extract nested composition status CEL mappings attached by
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
1458
|
+
// Extract nested composition status CEL mappings attached by
|
|
1459
|
+
// executeCompositionCore via Reflect.set. Must use
|
|
1460
|
+
// Object.getOwnPropertyDescriptor to bypass the Enhanced proxy's
|
|
1461
|
+
// get handler which would return a KubernetesRef instead of the
|
|
1462
|
+
// actual Record<string, string>.
|
|
1463
|
+
const nestedStatusDescriptor = Object.getOwnPropertyDescriptor(statusMappings, '__nestedStatusCel');
|
|
1464
|
+
const nestedStatusCel = nestedStatusDescriptor?.value ?? {};
|
|
527
1465
|
serializationLogger.debug('Nested status CEL extraction', {
|
|
528
1466
|
hasNestedStatusCel: Object.keys(nestedStatusCel).length > 0,
|
|
529
1467
|
keys: Object.keys(nestedStatusCel),
|
|
@@ -533,6 +1471,17 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
533
1471
|
if (definition.group) {
|
|
534
1472
|
kroSchema.group = definition.group;
|
|
535
1473
|
}
|
|
1474
|
+
// Attach nested status CEL mappings to the schema as a non-enumerable
|
|
1475
|
+
// property (same pattern as __ternaryConditionals, __omitFields).
|
|
1476
|
+
// Non-enumerable so it doesn't appear in the YAML output, but
|
|
1477
|
+
// accessible via KroSimpleSchemaWithMetadata for the YAML serializer
|
|
1478
|
+
// to resolve virtual composition IDs in resource templates.
|
|
1479
|
+
if (Object.keys(nestedStatusCel).length > 0) {
|
|
1480
|
+
Object.defineProperty(kroSchema, '__nestedStatusCel', {
|
|
1481
|
+
value: nestedStatusCel,
|
|
1482
|
+
enumerable: false,
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
536
1485
|
// Inject status overrides into schema status section.
|
|
537
1486
|
// Convert "..." to '...' in CEL string literals for YAML compatibility.
|
|
538
1487
|
const statusOverrides = compositionAnalysis?.statusOverrides ?? [];
|
|
@@ -545,6 +1494,18 @@ function createTypedResourceGraph(definition, resourceBuilder, statusBuilder, op
|
|
|
545
1494
|
kroSchema.status[override.propertyPath] = yamlSafe;
|
|
546
1495
|
}
|
|
547
1496
|
}
|
|
1497
|
+
// Apply ternary conditionals (once only — guard prevents
|
|
1498
|
+
// double-processing if toYaml() is called multiple times).
|
|
1499
|
+
// Note: omit() wrapping for optional fields is no longer a
|
|
1500
|
+
// post-processing step — it's applied inline during ref-to-CEL
|
|
1501
|
+
// conversion via `SerializationContext.omitFields`, which reads
|
|
1502
|
+
// from `kroSchema.__omitFields` inside `serializeResourceGraphToYaml`.
|
|
1503
|
+
if (!analysisState.ternaryAndOmitApplied) {
|
|
1504
|
+
analysisState.ternaryAndOmitApplied = true;
|
|
1505
|
+
if (kroSchema.__ternaryConditionals?.length) {
|
|
1506
|
+
applyTernaryConditionalsToResources(resourcesWithKeys, kroSchema.__ternaryConditionals, kroSchema.__nestedStatusCel);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
548
1509
|
return serializeResourceGraphToYaml(definition.name, resourcesWithKeys, options, kroSchema);
|
|
549
1510
|
},
|
|
550
1511
|
};
|