typekro 0.2.2 → 0.3.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/README.md +4 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/core/composition/imperative.d.ts.map +1 -1
- package/dist/core/composition/imperative.js +15 -2
- package/dist/core/composition/imperative.js.map +1 -1
- package/dist/core/composition/typekro-runtime/typekro-runtime.d.ts.map +1 -1
- package/dist/core/composition/typekro-runtime/typekro-runtime.js +24 -25
- package/dist/core/composition/typekro-runtime/typekro-runtime.js.map +1 -1
- package/dist/core/dependencies/type-guards.d.ts.map +1 -1
- package/dist/core/dependencies/type-guards.js +7 -2
- package/dist/core/dependencies/type-guards.js.map +1 -1
- package/dist/core/deployment/engine.d.ts +0 -1
- package/dist/core/deployment/engine.d.ts.map +1 -1
- package/dist/core/deployment/engine.js +0 -1
- package/dist/core/deployment/engine.js.map +1 -1
- package/dist/core/errors.d.ts +85 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +135 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/expressions/analyzer.d.ts +584 -0
- package/dist/core/expressions/analyzer.d.ts.map +1 -0
- package/dist/core/expressions/analyzer.js +2956 -0
- package/dist/core/expressions/analyzer.js.map +1 -0
- package/dist/core/expressions/cache.d.ts +136 -0
- package/dist/core/expressions/cache.d.ts.map +1 -0
- package/dist/core/expressions/cache.js +347 -0
- package/dist/core/expressions/cache.js.map +1 -0
- package/dist/core/expressions/cel-conversion-engine.d.ts +126 -0
- package/dist/core/expressions/cel-conversion-engine.d.ts.map +1 -0
- package/dist/core/expressions/cel-conversion-engine.js +293 -0
- package/dist/core/expressions/cel-conversion-engine.js.map +1 -0
- package/dist/core/expressions/compile-time-validation.d.ts +270 -0
- package/dist/core/expressions/compile-time-validation.d.ts.map +1 -0
- package/dist/core/expressions/compile-time-validation.js +506 -0
- package/dist/core/expressions/compile-time-validation.js.map +1 -0
- package/dist/core/expressions/composition-integration.d.ts +315 -0
- package/dist/core/expressions/composition-integration.d.ts.map +1 -0
- package/dist/core/expressions/composition-integration.js +936 -0
- package/dist/core/expressions/composition-integration.js.map +1 -0
- package/dist/core/expressions/conditional-expression-processor.d.ts +154 -0
- package/dist/core/expressions/conditional-expression-processor.d.ts.map +1 -0
- package/dist/core/expressions/conditional-expression-processor.js +479 -0
- package/dist/core/expressions/conditional-expression-processor.js.map +1 -0
- package/dist/core/expressions/conditional-integration.d.ts +133 -0
- package/dist/core/expressions/conditional-integration.d.ts.map +1 -0
- package/dist/core/expressions/conditional-integration.js +293 -0
- package/dist/core/expressions/conditional-integration.js.map +1 -0
- package/dist/core/expressions/conditional-validation.d.ts +181 -0
- package/dist/core/expressions/conditional-validation.d.ts.map +1 -0
- package/dist/core/expressions/conditional-validation.js +460 -0
- package/dist/core/expressions/conditional-validation.js.map +1 -0
- package/dist/core/expressions/context-aware-generator.d.ts +127 -0
- package/dist/core/expressions/context-aware-generator.d.ts.map +1 -0
- package/dist/core/expressions/context-aware-generator.js +500 -0
- package/dist/core/expressions/context-aware-generator.js.map +1 -0
- package/dist/core/expressions/context-detector.d.ts +148 -0
- package/dist/core/expressions/context-detector.d.ts.map +1 -0
- package/dist/core/expressions/context-detector.js +546 -0
- package/dist/core/expressions/context-detector.js.map +1 -0
- package/dist/core/expressions/context-switcher.d.ts +185 -0
- package/dist/core/expressions/context-switcher.d.ts.map +1 -0
- package/dist/core/expressions/context-switcher.js +515 -0
- package/dist/core/expressions/context-switcher.js.map +1 -0
- package/dist/core/expressions/context-validator.d.ts +176 -0
- package/dist/core/expressions/context-validator.d.ts.map +1 -0
- package/dist/core/expressions/context-validator.js +452 -0
- package/dist/core/expressions/context-validator.js.map +1 -0
- package/dist/core/expressions/custom-context-manager.d.ts +194 -0
- package/dist/core/expressions/custom-context-manager.d.ts.map +1 -0
- package/dist/core/expressions/custom-context-manager.js +390 -0
- package/dist/core/expressions/custom-context-manager.js.map +1 -0
- package/dist/core/expressions/expression-proxy.d.ts +80 -0
- package/dist/core/expressions/expression-proxy.d.ts.map +1 -0
- package/dist/core/expressions/expression-proxy.js +227 -0
- package/dist/core/expressions/expression-proxy.js.map +1 -0
- package/dist/core/expressions/factory-integration.d.ts +132 -0
- package/dist/core/expressions/factory-integration.d.ts.map +1 -0
- package/dist/core/expressions/factory-integration.js +327 -0
- package/dist/core/expressions/factory-integration.js.map +1 -0
- package/dist/core/expressions/factory-pattern-handler.d.ts +88 -0
- package/dist/core/expressions/factory-pattern-handler.d.ts.map +1 -0
- package/dist/core/expressions/factory-pattern-handler.js +336 -0
- package/dist/core/expressions/factory-pattern-handler.js.map +1 -0
- package/dist/core/expressions/field-hydration-processor.d.ts +188 -0
- package/dist/core/expressions/field-hydration-processor.d.ts.map +1 -0
- package/dist/core/expressions/field-hydration-processor.js +562 -0
- package/dist/core/expressions/field-hydration-processor.js.map +1 -0
- package/dist/core/expressions/imperative-analyzer.d.ts +21 -0
- package/dist/core/expressions/imperative-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/imperative-analyzer.js +343 -0
- package/dist/core/expressions/imperative-analyzer.js.map +1 -0
- package/dist/core/expressions/index.d.ts +54 -0
- package/dist/core/expressions/index.d.ts.map +1 -0
- package/dist/core/expressions/index.js +50 -0
- package/dist/core/expressions/index.js.map +1 -0
- package/dist/core/expressions/lazy-analysis.d.ts +1128 -0
- package/dist/core/expressions/lazy-analysis.d.ts.map +1 -0
- package/dist/core/expressions/lazy-analysis.js +2443 -0
- package/dist/core/expressions/lazy-analysis.js.map +1 -0
- package/dist/core/expressions/magic-assignable-analyzer.d.ts +123 -0
- package/dist/core/expressions/magic-assignable-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/magic-assignable-analyzer.js +352 -0
- package/dist/core/expressions/magic-assignable-analyzer.js.map +1 -0
- package/dist/core/expressions/magic-proxy-analyzer.d.ts +206 -0
- package/dist/core/expressions/magic-proxy-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/magic-proxy-analyzer.js +639 -0
- package/dist/core/expressions/magic-proxy-analyzer.js.map +1 -0
- package/dist/core/expressions/magic-proxy-detector.d.ts +154 -0
- package/dist/core/expressions/magic-proxy-detector.d.ts.map +1 -0
- package/dist/core/expressions/magic-proxy-detector.js +242 -0
- package/dist/core/expressions/magic-proxy-detector.js.map +1 -0
- package/dist/core/expressions/migration-helpers.d.ts +133 -0
- package/dist/core/expressions/migration-helpers.d.ts.map +1 -0
- package/dist/core/expressions/migration-helpers.js +443 -0
- package/dist/core/expressions/migration-helpers.js.map +1 -0
- package/dist/core/expressions/optionality-handler.d.ts +503 -0
- package/dist/core/expressions/optionality-handler.d.ts.map +1 -0
- package/dist/core/expressions/optionality-handler.js +1306 -0
- package/dist/core/expressions/optionality-handler.js.map +1 -0
- package/dist/core/expressions/readiness-integration.d.ts +119 -0
- package/dist/core/expressions/readiness-integration.d.ts.map +1 -0
- package/dist/core/expressions/readiness-integration.js +386 -0
- package/dist/core/expressions/readiness-integration.js.map +1 -0
- package/dist/core/expressions/resource-analyzer.d.ts +486 -0
- package/dist/core/expressions/resource-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/resource-analyzer.js +1086 -0
- package/dist/core/expressions/resource-analyzer.js.map +1 -0
- package/dist/core/expressions/resource-validation.d.ts +187 -0
- package/dist/core/expressions/resource-validation.d.ts.map +1 -0
- package/dist/core/expressions/resource-validation.js +552 -0
- package/dist/core/expressions/resource-validation.js.map +1 -0
- package/dist/core/expressions/runtime-error-mapper.d.ts +138 -0
- package/dist/core/expressions/runtime-error-mapper.d.ts.map +1 -0
- package/dist/core/expressions/runtime-error-mapper.js +412 -0
- package/dist/core/expressions/runtime-error-mapper.js.map +1 -0
- package/dist/core/expressions/source-map.d.ts +168 -0
- package/dist/core/expressions/source-map.d.ts.map +1 -0
- package/dist/core/expressions/source-map.js +350 -0
- package/dist/core/expressions/source-map.js.map +1 -0
- package/dist/core/expressions/status-builder-analyzer.d.ts +353 -0
- package/dist/core/expressions/status-builder-analyzer.d.ts.map +1 -0
- package/dist/core/expressions/status-builder-analyzer.js +1301 -0
- package/dist/core/expressions/status-builder-analyzer.js.map +1 -0
- package/dist/core/expressions/type-inference.d.ts +184 -0
- package/dist/core/expressions/type-inference.d.ts.map +1 -0
- package/dist/core/expressions/type-inference.js +838 -0
- package/dist/core/expressions/type-inference.js.map +1 -0
- package/dist/core/expressions/type-safety.d.ts +203 -0
- package/dist/core/expressions/type-safety.d.ts.map +1 -0
- package/dist/core/expressions/type-safety.js +442 -0
- package/dist/core/expressions/type-safety.js.map +1 -0
- package/dist/core/expressions/types.d.ts +282 -0
- package/dist/core/expressions/types.d.ts.map +1 -0
- package/dist/core/expressions/types.js +8 -0
- package/dist/core/expressions/types.js.map +1 -0
- package/dist/core/kubernetes/client-provider.js +2 -2
- package/dist/core/kubernetes/client-provider.js.map +1 -1
- package/dist/core/serialization/core.d.ts.map +1 -1
- package/dist/core/serialization/core.js +573 -9
- package/dist/core/serialization/core.js.map +1 -1
- package/dist/core/types/deployment.d.ts +4 -0
- package/dist/core/types/deployment.d.ts.map +1 -1
- package/dist/core/types/deployment.js.map +1 -1
- package/dist/core/types/index.d.ts +1 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js.map +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/factories/helm/helm-release.d.ts.map +1 -1
- package/dist/factories/helm/helm-release.js +0 -5
- package/dist/factories/helm/helm-release.js.map +1 -1
- package/dist/factories/helm/types.d.ts +1 -1
- package/dist/factories/helm/types.d.ts.map +1 -1
- package/dist/factories/shared.d.ts.map +1 -1
- package/dist/factories/shared.js +21 -1
- package/dist/factories/shared.js.map +1 -1
- package/dist/factories/simple/index.d.ts +2 -2
- package/dist/factories/simple/index.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.d.ts +3 -3
- package/dist/factories/simple/workloads/deployment.d.ts.map +1 -1
- package/dist/factories/simple/workloads/deployment.js +37 -11
- package/dist/factories/simple/workloads/deployment.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/type-guards.d.ts +6 -0
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/dist/utils/type-guards.js +25 -2
- package/dist/utils/type-guards.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,1301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Builder Analyzer for JavaScript to CEL Expression Conversion
|
|
3
|
+
*
|
|
4
|
+
* This module provides specialized analysis for status builder functions used in
|
|
5
|
+
* toResourceGraph. It detects KubernetesRef objects from the magic proxy system
|
|
6
|
+
* and converts JavaScript expressions to appropriate CEL expressions for status
|
|
7
|
+
* field population.
|
|
8
|
+
*
|
|
9
|
+
* Key Features:
|
|
10
|
+
* - Analyzes status builder functions for KubernetesRef detection
|
|
11
|
+
* - Converts return object expressions to CEL for status field mapping
|
|
12
|
+
* - Integrates with magic proxy system (SchemaProxy and ResourcesProxy)
|
|
13
|
+
* - Provides status context-specific CEL generation
|
|
14
|
+
* - Supports both direct and Kro factory patterns
|
|
15
|
+
*/
|
|
16
|
+
import * as acorn from 'acorn';
|
|
17
|
+
import * as estraverse from 'estraverse';
|
|
18
|
+
import { ConversionError } from '../errors.js';
|
|
19
|
+
import { getComponentLogger } from '../logging/index.js';
|
|
20
|
+
import { containsKubernetesRefs } from '../../utils/type-guards.js';
|
|
21
|
+
import { JavaScriptToCelAnalyzer } from './analyzer.js';
|
|
22
|
+
import { MagicProxyAnalyzer, } from './magic-proxy-analyzer.js';
|
|
23
|
+
import { SourceMapBuilder } from './source-map.js';
|
|
24
|
+
import { EnhancedTypeOptionalityHandler } from './optionality-handler.js';
|
|
25
|
+
import { CEL_EXPRESSION_BRAND } from '../constants/brands.js';
|
|
26
|
+
/**
|
|
27
|
+
* Default analysis options
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_ANALYSIS_OPTIONS = {
|
|
30
|
+
deepAnalysis: true,
|
|
31
|
+
includeSourceMapping: true,
|
|
32
|
+
validateReferences: true,
|
|
33
|
+
performOptionalityAnalysis: true,
|
|
34
|
+
factoryType: 'kro',
|
|
35
|
+
maxDepth: 10,
|
|
36
|
+
hydrationStates: new Map(),
|
|
37
|
+
conservativeNullSafety: true
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Status Builder Analyzer
|
|
41
|
+
*
|
|
42
|
+
* Analyzes status builder functions to extract KubernetesRef dependencies
|
|
43
|
+
* and convert JavaScript expressions to CEL for status field population.
|
|
44
|
+
*/
|
|
45
|
+
export class StatusBuilderAnalyzer {
|
|
46
|
+
expressionAnalyzer;
|
|
47
|
+
magicProxyAnalyzer;
|
|
48
|
+
optionalityHandler;
|
|
49
|
+
options;
|
|
50
|
+
logger = getComponentLogger('status-builder-analyzer');
|
|
51
|
+
constructor(expressionAnalyzer, options) {
|
|
52
|
+
this.expressionAnalyzer = expressionAnalyzer || new JavaScriptToCelAnalyzer();
|
|
53
|
+
this.magicProxyAnalyzer = new MagicProxyAnalyzer();
|
|
54
|
+
this.optionalityHandler = new EnhancedTypeOptionalityHandler();
|
|
55
|
+
this.options = { ...DEFAULT_ANALYSIS_OPTIONS, ...options };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Analyze status builder function for toResourceGraph integration
|
|
59
|
+
*
|
|
60
|
+
* This is the main method that analyzes a status builder function and extracts
|
|
61
|
+
* KubernetesRef dependencies for conversion to CEL expressions.
|
|
62
|
+
*/
|
|
63
|
+
analyzeStatusBuilder(statusBuilder, resources, schemaProxy) {
|
|
64
|
+
try {
|
|
65
|
+
this.logger.debug('Analyzing status builder function', {
|
|
66
|
+
resourceCount: Object.keys(resources).length,
|
|
67
|
+
hasSchemaProxy: !!schemaProxy,
|
|
68
|
+
factoryType: this.options.factoryType
|
|
69
|
+
});
|
|
70
|
+
const originalSource = statusBuilder.toString();
|
|
71
|
+
// Parse the status builder function
|
|
72
|
+
const ast = this.parseStatusBuilderFunction(originalSource);
|
|
73
|
+
// Analyze the return statement
|
|
74
|
+
const returnStatement = this.analyzeReturnStatement(ast, originalSource);
|
|
75
|
+
if (!returnStatement || !returnStatement.returnsObject) {
|
|
76
|
+
throw new ConversionError('Status builder must return an object literal', originalSource, 'function-call');
|
|
77
|
+
}
|
|
78
|
+
// Analyze each property in the returned object
|
|
79
|
+
const fieldAnalysis = new Map();
|
|
80
|
+
const statusMappings = {};
|
|
81
|
+
const allDependencies = [];
|
|
82
|
+
const allSourceMap = [];
|
|
83
|
+
const allErrors = [];
|
|
84
|
+
let overallValid = true;
|
|
85
|
+
for (const property of returnStatement.properties) {
|
|
86
|
+
try {
|
|
87
|
+
const fieldResult = this.analyzeStatusField(property, resources, originalSource, schemaProxy);
|
|
88
|
+
fieldAnalysis.set(property.name, fieldResult);
|
|
89
|
+
if (fieldResult.valid) {
|
|
90
|
+
if (fieldResult.celExpression) {
|
|
91
|
+
// For dynamic expressions, use the CEL expression directly
|
|
92
|
+
statusMappings[property.name] = fieldResult.celExpression;
|
|
93
|
+
}
|
|
94
|
+
else if (fieldResult.staticValue !== undefined) {
|
|
95
|
+
// For static objects, keep as plain JavaScript objects for performance
|
|
96
|
+
statusMappings[property.name] = fieldResult.staticValue;
|
|
97
|
+
}
|
|
98
|
+
else if (property.valueNode.type === 'Literal') {
|
|
99
|
+
// For static literals, keep as plain JavaScript values for performance
|
|
100
|
+
statusMappings[property.name] = property.valueNode.value;
|
|
101
|
+
}
|
|
102
|
+
else if (property.valueNode.type === 'Identifier' && property.valueNode.name === 'undefined') {
|
|
103
|
+
// For undefined identifier, preserve as undefined (will be filtered out during serialization)
|
|
104
|
+
statusMappings[property.name] = undefined;
|
|
105
|
+
}
|
|
106
|
+
else if (property.valueNode.type === 'UnaryExpression' &&
|
|
107
|
+
property.valueNode.operator === '!' &&
|
|
108
|
+
property.valueNode.argument?.type === 'Literal' &&
|
|
109
|
+
typeof property.valueNode.argument.value === 'number') {
|
|
110
|
+
// For boolean literals represented as !0 or !1
|
|
111
|
+
const booleanValue = property.valueNode.argument.value === 0; // !0 = true, !1 = false
|
|
112
|
+
statusMappings[property.name] = booleanValue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
allDependencies.push(...fieldResult.dependencies);
|
|
116
|
+
allSourceMap.push(...fieldResult.sourceMap);
|
|
117
|
+
allErrors.push(...fieldResult.errors);
|
|
118
|
+
if (!fieldResult.valid) {
|
|
119
|
+
overallValid = false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const fieldError = new ConversionError(`Failed to analyze status field '${property.name}': ${error instanceof Error ? error.message : String(error)}`, property.valueSource, 'unknown');
|
|
124
|
+
allErrors.push(fieldError);
|
|
125
|
+
overallValid = false;
|
|
126
|
+
this.logger.error('Failed to analyze status field', error, {
|
|
127
|
+
fieldName: property.name
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Categorize dependencies
|
|
132
|
+
const { resourceReferences, schemaReferences } = this.categorizeDependencies(allDependencies);
|
|
133
|
+
this.logger.debug('Status builder analysis complete', {
|
|
134
|
+
fieldCount: returnStatement.properties.length,
|
|
135
|
+
validFields: Object.keys(statusMappings).length,
|
|
136
|
+
totalDependencies: allDependencies.length,
|
|
137
|
+
resourceReferences: resourceReferences.length,
|
|
138
|
+
schemaReferences: schemaReferences.length,
|
|
139
|
+
overallValid
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
fieldAnalysis,
|
|
143
|
+
statusMappings,
|
|
144
|
+
allDependencies,
|
|
145
|
+
resourceReferences,
|
|
146
|
+
schemaReferences,
|
|
147
|
+
sourceMap: allSourceMap,
|
|
148
|
+
errors: allErrors,
|
|
149
|
+
valid: overallValid,
|
|
150
|
+
originalSource,
|
|
151
|
+
ast,
|
|
152
|
+
returnStatement
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const analysisError = new ConversionError(`Failed to analyze status builder: ${error instanceof Error ? error.message : String(error)}`, statusBuilder.toString(), 'function-call');
|
|
157
|
+
this.logger.error('Status builder analysis failed', error);
|
|
158
|
+
return {
|
|
159
|
+
fieldAnalysis: new Map(),
|
|
160
|
+
statusMappings: {},
|
|
161
|
+
allDependencies: [],
|
|
162
|
+
resourceReferences: [],
|
|
163
|
+
schemaReferences: [],
|
|
164
|
+
sourceMap: [],
|
|
165
|
+
errors: [analysisError],
|
|
166
|
+
valid: false,
|
|
167
|
+
originalSource: statusBuilder.toString()
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Analyze return object expressions with magic proxy support
|
|
173
|
+
*
|
|
174
|
+
* This method analyzes the object returned by the status builder function
|
|
175
|
+
* and detects KubernetesRef objects from the magic proxy system.
|
|
176
|
+
*/
|
|
177
|
+
analyzeReturnObjectWithMagicProxy(returnObject, resources, schemaProxy) {
|
|
178
|
+
const statusMappings = {};
|
|
179
|
+
const dependencies = [];
|
|
180
|
+
const errors = [];
|
|
181
|
+
if (!returnObject || typeof returnObject !== 'object') {
|
|
182
|
+
errors.push(new ConversionError('Return object must be a valid object', String(returnObject), 'unknown'));
|
|
183
|
+
return { statusMappings, dependencies, errors };
|
|
184
|
+
}
|
|
185
|
+
for (const [fieldName, fieldValue] of Object.entries(returnObject)) {
|
|
186
|
+
try {
|
|
187
|
+
const fieldResult = this.analyzeReturnObjectField(fieldName, fieldValue, resources, schemaProxy);
|
|
188
|
+
if (fieldResult.celExpression) {
|
|
189
|
+
statusMappings[fieldName] = fieldResult.celExpression;
|
|
190
|
+
}
|
|
191
|
+
dependencies.push(...fieldResult.dependencies);
|
|
192
|
+
errors.push(...fieldResult.errors);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
errors.push(new ConversionError(`Failed to analyze field '${fieldName}': ${error instanceof Error ? error.message : String(error)}`, String(fieldValue), 'unknown'));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { statusMappings, dependencies, errors };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Analyze a single field in the return object with comprehensive magic proxy support
|
|
202
|
+
*/
|
|
203
|
+
analyzeReturnObjectField(fieldName, fieldValue, resources, schemaProxy) {
|
|
204
|
+
try {
|
|
205
|
+
// Create comprehensive analysis context
|
|
206
|
+
const context = {
|
|
207
|
+
type: 'status',
|
|
208
|
+
availableReferences: resources,
|
|
209
|
+
...(schemaProxy && { schemaProxy }),
|
|
210
|
+
factoryType: this.options.factoryType,
|
|
211
|
+
hydrationStates: this.options.hydrationStates,
|
|
212
|
+
conservativeNullSafety: this.options.conservativeNullSafety,
|
|
213
|
+
useKroConditionals: true,
|
|
214
|
+
generateHasChecks: true,
|
|
215
|
+
maxOptionalityDepth: this.options.maxDepth,
|
|
216
|
+
dependencies: []
|
|
217
|
+
};
|
|
218
|
+
// Step 1: Detect if the field value contains KubernetesRef objects
|
|
219
|
+
const containsRefs = containsKubernetesRefs(fieldValue);
|
|
220
|
+
if (!containsRefs) {
|
|
221
|
+
// No KubernetesRef objects - return as static value
|
|
222
|
+
return {
|
|
223
|
+
celExpression: this.convertStaticValueToCel(fieldValue),
|
|
224
|
+
dependencies: [],
|
|
225
|
+
errors: [],
|
|
226
|
+
requiresConversion: false
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Step 2: Analyze KubernetesRef objects for optionality requirements
|
|
230
|
+
const optionalityResults = this.optionalityHandler.analyzeOptionalityRequirements(fieldValue, context);
|
|
231
|
+
// Step 3: Generate CEL expression with appropriate null-safety
|
|
232
|
+
const celResult = this.optionalityHandler.generateNullSafeCelExpression(fieldValue, optionalityResults, context);
|
|
233
|
+
// Step 4: Extract dependencies from the analysis
|
|
234
|
+
const dependencies = optionalityResults.map(result => result.kubernetesRef);
|
|
235
|
+
return {
|
|
236
|
+
celExpression: celResult.celExpression,
|
|
237
|
+
dependencies,
|
|
238
|
+
errors: celResult.errors,
|
|
239
|
+
requiresConversion: true
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
const fieldError = new ConversionError(`Failed to analyze return object field '${fieldName}': ${error instanceof Error ? error.message : String(error)}`, String(fieldValue), 'unknown');
|
|
244
|
+
return {
|
|
245
|
+
celExpression: null,
|
|
246
|
+
dependencies: [],
|
|
247
|
+
errors: [fieldError],
|
|
248
|
+
requiresConversion: false
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Convert static values (no KubernetesRef objects) to CEL expressions
|
|
254
|
+
*/
|
|
255
|
+
convertStaticValueToCel(value) {
|
|
256
|
+
let celExpression;
|
|
257
|
+
let type;
|
|
258
|
+
if (typeof value === 'string') {
|
|
259
|
+
celExpression = `"${value.replace(/"/g, '\\"')}"`;
|
|
260
|
+
type = 'string';
|
|
261
|
+
}
|
|
262
|
+
else if (typeof value === 'number') {
|
|
263
|
+
celExpression = String(value);
|
|
264
|
+
type = 'number';
|
|
265
|
+
}
|
|
266
|
+
else if (typeof value === 'boolean') {
|
|
267
|
+
celExpression = String(value);
|
|
268
|
+
type = 'boolean';
|
|
269
|
+
}
|
|
270
|
+
else if (value === null) {
|
|
271
|
+
celExpression = 'null';
|
|
272
|
+
type = 'null';
|
|
273
|
+
}
|
|
274
|
+
else if (value === undefined) {
|
|
275
|
+
celExpression = 'null';
|
|
276
|
+
type = 'null';
|
|
277
|
+
}
|
|
278
|
+
else if (Array.isArray(value)) {
|
|
279
|
+
const elements = value.map(item => this.convertStaticValueToCel(item).expression);
|
|
280
|
+
celExpression = `[${elements.join(', ')}]`;
|
|
281
|
+
type = 'array';
|
|
282
|
+
}
|
|
283
|
+
else if (typeof value === 'object') {
|
|
284
|
+
const properties = Object.entries(value).map(([key, val]) => {
|
|
285
|
+
const convertedVal = this.convertStaticValueToCel(val);
|
|
286
|
+
return `"${key}": ${convertedVal.expression}`;
|
|
287
|
+
});
|
|
288
|
+
celExpression = `{${properties.join(', ')}}`;
|
|
289
|
+
type = 'object';
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
celExpression = String(value);
|
|
293
|
+
type = 'unknown';
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
297
|
+
expression: celExpression,
|
|
298
|
+
type
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Perform deep analysis of nested return object structures
|
|
303
|
+
*/
|
|
304
|
+
analyzeNestedReturnObjectStructure(returnObject, resources, schemaProxy, depth = 0) {
|
|
305
|
+
const flattenedMappings = {};
|
|
306
|
+
const nestedDependencies = new Map();
|
|
307
|
+
const structureErrors = [];
|
|
308
|
+
if (depth > this.options.maxDepth) {
|
|
309
|
+
structureErrors.push(new ConversionError(`Maximum analysis depth (${this.options.maxDepth}) exceeded`, String(returnObject), 'unknown'));
|
|
310
|
+
return { flattenedMappings, nestedDependencies, structureErrors };
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
this.analyzeObjectStructureRecursively(returnObject, '', flattenedMappings, nestedDependencies, structureErrors, resources, schemaProxy, depth);
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
structureErrors.push(new ConversionError(`Failed to analyze nested structure: ${error instanceof Error ? error.message : String(error)}`, String(returnObject), 'unknown'));
|
|
317
|
+
}
|
|
318
|
+
return { flattenedMappings, nestedDependencies, structureErrors };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Recursively analyze object structure for KubernetesRef objects
|
|
322
|
+
*/
|
|
323
|
+
analyzeObjectStructureRecursively(obj, pathPrefix, flattenedMappings, nestedDependencies, errors, resources, schemaProxy, depth = 0) {
|
|
324
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
328
|
+
const fullPath = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
329
|
+
try {
|
|
330
|
+
if (containsKubernetesRefs(value)) {
|
|
331
|
+
// Analyze this field for KubernetesRef objects
|
|
332
|
+
const fieldResult = this.analyzeReturnObjectField(fullPath, value, resources, schemaProxy);
|
|
333
|
+
if (fieldResult.celExpression) {
|
|
334
|
+
flattenedMappings[fullPath] = fieldResult.celExpression;
|
|
335
|
+
}
|
|
336
|
+
if (fieldResult.dependencies.length > 0) {
|
|
337
|
+
nestedDependencies.set(fullPath, fieldResult.dependencies);
|
|
338
|
+
}
|
|
339
|
+
errors.push(...fieldResult.errors);
|
|
340
|
+
}
|
|
341
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
342
|
+
// Recursively analyze nested objects
|
|
343
|
+
this.analyzeObjectStructureRecursively(value, fullPath, flattenedMappings, nestedDependencies, errors, resources, schemaProxy, depth + 1);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// Static value - convert directly
|
|
347
|
+
flattenedMappings[fullPath] = this.convertStaticValueToCel(value);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
errors.push(new ConversionError(`Failed to analyze nested field '${fullPath}': ${error instanceof Error ? error.message : String(error)}`, String(value), 'unknown'));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Generate status context-specific CEL from KubernetesRef objects
|
|
357
|
+
*
|
|
358
|
+
* This method generates CEL expressions specifically for status context,
|
|
359
|
+
* taking into account the magic proxy system and field hydration timing.
|
|
360
|
+
*/
|
|
361
|
+
generateStatusContextCel(kubernetesRef, context) {
|
|
362
|
+
try {
|
|
363
|
+
return this.generateStatusContextCelWithAdvancedFeatures(kubernetesRef, context);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
this.logger.error('Failed to generate status context CEL', error, {
|
|
367
|
+
resourceId: kubernetesRef.resourceId,
|
|
368
|
+
fieldPath: kubernetesRef.fieldPath
|
|
369
|
+
});
|
|
370
|
+
// Return a safe fallback
|
|
371
|
+
return this.generateFallbackStatusCel(kubernetesRef);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Generate advanced status context CEL with full feature support
|
|
376
|
+
*/
|
|
377
|
+
generateStatusContextCelWithAdvancedFeatures(kubernetesRef, context) {
|
|
378
|
+
const isSchemaRef = kubernetesRef.resourceId === '__schema__';
|
|
379
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
380
|
+
// Build base CEL expression
|
|
381
|
+
let baseCelExpression;
|
|
382
|
+
if (isSchemaRef) {
|
|
383
|
+
baseCelExpression = `schema.${fieldPath}`;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
baseCelExpression = `resources.${kubernetesRef.resourceId}.${fieldPath}`;
|
|
387
|
+
}
|
|
388
|
+
// Determine status-specific handling requirements
|
|
389
|
+
const statusHandlingInfo = this.analyzeStatusFieldHandlingRequirements(kubernetesRef, context);
|
|
390
|
+
// Apply status-specific transformations
|
|
391
|
+
const finalExpression = this.applyStatusContextTransformations(baseCelExpression, statusHandlingInfo, context);
|
|
392
|
+
// Infer the result type based on the field path and context
|
|
393
|
+
const resultType = this.inferStatusFieldType(kubernetesRef, context);
|
|
394
|
+
return {
|
|
395
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
396
|
+
expression: finalExpression,
|
|
397
|
+
type: resultType,
|
|
398
|
+
metadata: {
|
|
399
|
+
isStatusContext: true,
|
|
400
|
+
requiresHydration: statusHandlingInfo.requiresHydration,
|
|
401
|
+
isOptional: statusHandlingInfo.isOptional,
|
|
402
|
+
handlingStrategy: statusHandlingInfo.strategy
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Analyze status field handling requirements
|
|
408
|
+
*/
|
|
409
|
+
analyzeStatusFieldHandlingRequirements(kubernetesRef, context) {
|
|
410
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
411
|
+
const isSchemaRef = kubernetesRef.resourceId === '__schema__';
|
|
412
|
+
const isStatusField = fieldPath.startsWith('status.');
|
|
413
|
+
// Determine if this field requires hydration
|
|
414
|
+
const requiresHydration = !isSchemaRef && isStatusField;
|
|
415
|
+
// Determine if this field is optional in status context
|
|
416
|
+
const isOptional = this.isFieldOptionalInStatusContext(kubernetesRef, context);
|
|
417
|
+
// Determine handling strategy
|
|
418
|
+
let strategy;
|
|
419
|
+
if (requiresHydration && isOptional) {
|
|
420
|
+
strategy = 'hydration-with-null-safety';
|
|
421
|
+
}
|
|
422
|
+
else if (requiresHydration) {
|
|
423
|
+
strategy = 'hydration-required';
|
|
424
|
+
}
|
|
425
|
+
else if (isOptional) {
|
|
426
|
+
strategy = 'null-safety-only';
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
strategy = 'direct-access';
|
|
430
|
+
}
|
|
431
|
+
// Determine priority for status field evaluation
|
|
432
|
+
const priority = this.calculateStatusFieldPriority(kubernetesRef, context);
|
|
433
|
+
return {
|
|
434
|
+
kubernetesRef,
|
|
435
|
+
requiresHydration,
|
|
436
|
+
isOptional,
|
|
437
|
+
strategy,
|
|
438
|
+
priority,
|
|
439
|
+
fieldCategory: this.categorizeStatusField(fieldPath),
|
|
440
|
+
expectedAvailability: this.estimateFieldAvailability(kubernetesRef, context)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Apply status context-specific transformations to CEL expression
|
|
445
|
+
*/
|
|
446
|
+
applyStatusContextTransformations(baseCelExpression, handlingInfo, context) {
|
|
447
|
+
let transformedExpression = baseCelExpression;
|
|
448
|
+
switch (handlingInfo.strategy) {
|
|
449
|
+
case 'hydration-with-null-safety':
|
|
450
|
+
transformedExpression = this.applyHydrationWithNullSafety(baseCelExpression, handlingInfo, context);
|
|
451
|
+
break;
|
|
452
|
+
case 'hydration-required':
|
|
453
|
+
transformedExpression = this.applyHydrationRequired(baseCelExpression, handlingInfo, context);
|
|
454
|
+
break;
|
|
455
|
+
case 'null-safety-only':
|
|
456
|
+
transformedExpression = this.applyNullSafetyOnly(baseCelExpression, handlingInfo, context);
|
|
457
|
+
break;
|
|
458
|
+
case 'direct-access':
|
|
459
|
+
// No transformation needed
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
return transformedExpression;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Apply hydration with null-safety transformation
|
|
466
|
+
*/
|
|
467
|
+
applyHydrationWithNullSafety(baseCelExpression, _handlingInfo, context) {
|
|
468
|
+
if (context.useKroConditionals) {
|
|
469
|
+
// Use Kro's conditional operators
|
|
470
|
+
return baseCelExpression.replace(/\./g, '?.');
|
|
471
|
+
}
|
|
472
|
+
else if (context.generateHasChecks) {
|
|
473
|
+
// Use has() checks
|
|
474
|
+
const pathParts = baseCelExpression.split('.');
|
|
475
|
+
const hasChecks = [];
|
|
476
|
+
for (let i = 0; i < pathParts.length; i++) {
|
|
477
|
+
const partialPath = pathParts.slice(0, i + 1).join('.');
|
|
478
|
+
hasChecks.push(`has(${partialPath})`);
|
|
479
|
+
}
|
|
480
|
+
return `${hasChecks.join(' && ')} && ${baseCelExpression}`;
|
|
481
|
+
}
|
|
482
|
+
return baseCelExpression;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Apply hydration required transformation
|
|
486
|
+
*/
|
|
487
|
+
applyHydrationRequired(baseCelExpression, handlingInfo, _context) {
|
|
488
|
+
// For hydration required fields, we might want to add readiness checks
|
|
489
|
+
if (handlingInfo.fieldCategory === 'readiness-indicator') {
|
|
490
|
+
return `${baseCelExpression} != null && ${baseCelExpression}`;
|
|
491
|
+
}
|
|
492
|
+
return baseCelExpression;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Apply null-safety only transformation
|
|
496
|
+
*/
|
|
497
|
+
applyNullSafetyOnly(baseCelExpression, _handlingInfo, context) {
|
|
498
|
+
if (context.generateHasChecks) {
|
|
499
|
+
return `has(${baseCelExpression}) && ${baseCelExpression}`;
|
|
500
|
+
}
|
|
501
|
+
return baseCelExpression;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Check if a field is optional in status context
|
|
505
|
+
*/
|
|
506
|
+
isFieldOptionalInStatusContext(kubernetesRef, _context) {
|
|
507
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
508
|
+
// Status fields are generally optional during hydration
|
|
509
|
+
if (fieldPath.startsWith('status.')) {
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
// Some spec fields might be optional
|
|
513
|
+
const optionalSpecFields = [
|
|
514
|
+
'spec.replicas',
|
|
515
|
+
'spec.resources',
|
|
516
|
+
'spec.nodeSelector',
|
|
517
|
+
'spec.tolerations'
|
|
518
|
+
];
|
|
519
|
+
if (optionalSpecFields.some(field => fieldPath.startsWith(field))) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
// Metadata fields like labels and annotations are optional
|
|
523
|
+
const optionalMetadataFields = [
|
|
524
|
+
'metadata.labels',
|
|
525
|
+
'metadata.annotations',
|
|
526
|
+
'metadata.namespace'
|
|
527
|
+
];
|
|
528
|
+
if (optionalMetadataFields.some(field => fieldPath.startsWith(field))) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Calculate priority for status field evaluation
|
|
535
|
+
*/
|
|
536
|
+
calculateStatusFieldPriority(kubernetesRef, _context) {
|
|
537
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
538
|
+
// Higher priority (lower number) for critical status fields
|
|
539
|
+
if (fieldPath.includes('ready') || fieldPath.includes('available')) {
|
|
540
|
+
return 1;
|
|
541
|
+
}
|
|
542
|
+
if (fieldPath.startsWith('status.conditions')) {
|
|
543
|
+
return 2;
|
|
544
|
+
}
|
|
545
|
+
if (fieldPath.startsWith('status.')) {
|
|
546
|
+
return 3;
|
|
547
|
+
}
|
|
548
|
+
if (fieldPath.startsWith('spec.')) {
|
|
549
|
+
return 4;
|
|
550
|
+
}
|
|
551
|
+
if (fieldPath.startsWith('metadata.')) {
|
|
552
|
+
return 5;
|
|
553
|
+
}
|
|
554
|
+
return 10; // Default priority
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Categorize status field type
|
|
558
|
+
*/
|
|
559
|
+
categorizeStatusField(fieldPath) {
|
|
560
|
+
if (fieldPath.includes('ready') || fieldPath.includes('available')) {
|
|
561
|
+
return 'readiness-indicator';
|
|
562
|
+
}
|
|
563
|
+
if (fieldPath.includes('conditions')) {
|
|
564
|
+
return 'condition-status';
|
|
565
|
+
}
|
|
566
|
+
if (fieldPath.includes('replicas')) {
|
|
567
|
+
return 'replica-status';
|
|
568
|
+
}
|
|
569
|
+
if (fieldPath.includes('loadBalancer') || fieldPath.includes('ingress')) {
|
|
570
|
+
return 'network-status';
|
|
571
|
+
}
|
|
572
|
+
if (fieldPath.includes('phase') || fieldPath.includes('state')) {
|
|
573
|
+
return 'lifecycle-status';
|
|
574
|
+
}
|
|
575
|
+
return 'general-status';
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Estimate field availability timing
|
|
579
|
+
*/
|
|
580
|
+
estimateFieldAvailability(kubernetesRef, _context) {
|
|
581
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
582
|
+
if (kubernetesRef.resourceId === '__schema__') {
|
|
583
|
+
return 'immediate';
|
|
584
|
+
}
|
|
585
|
+
if (fieldPath.startsWith('metadata.')) {
|
|
586
|
+
return 'immediate';
|
|
587
|
+
}
|
|
588
|
+
if (fieldPath.startsWith('spec.')) {
|
|
589
|
+
return 'immediate';
|
|
590
|
+
}
|
|
591
|
+
if (fieldPath.includes('ready') || fieldPath.includes('available')) {
|
|
592
|
+
return 'delayed';
|
|
593
|
+
}
|
|
594
|
+
if (fieldPath.includes('loadBalancer') || fieldPath.includes('ingress')) {
|
|
595
|
+
return 'very-delayed';
|
|
596
|
+
}
|
|
597
|
+
return 'delayed';
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Infer the type of a status field
|
|
601
|
+
*/
|
|
602
|
+
inferStatusFieldType(kubernetesRef, _context) {
|
|
603
|
+
const fieldPath = kubernetesRef.fieldPath || '';
|
|
604
|
+
if (fieldPath.includes('replicas') || fieldPath.includes('count')) {
|
|
605
|
+
return 'number';
|
|
606
|
+
}
|
|
607
|
+
if (fieldPath.includes('ready') || fieldPath.includes('available')) {
|
|
608
|
+
return 'boolean';
|
|
609
|
+
}
|
|
610
|
+
if (fieldPath.includes('conditions')) {
|
|
611
|
+
return 'array';
|
|
612
|
+
}
|
|
613
|
+
if (fieldPath.includes('phase') || fieldPath.includes('state')) {
|
|
614
|
+
return 'string';
|
|
615
|
+
}
|
|
616
|
+
if (fieldPath.includes('ip') || fieldPath.includes('IP')) {
|
|
617
|
+
return 'string';
|
|
618
|
+
}
|
|
619
|
+
return 'unknown';
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Analyze a mixed object expression (contains both static and dynamic values)
|
|
623
|
+
*/
|
|
624
|
+
analyzeMixedObjectExpression(objectNode, resources, originalSource, schemaProxy) {
|
|
625
|
+
if (objectNode.type !== 'ObjectExpression') {
|
|
626
|
+
return { valid: false, processedObject: null, dependencies: [], requiresConversion: false };
|
|
627
|
+
}
|
|
628
|
+
const result = {};
|
|
629
|
+
const allDependencies = [];
|
|
630
|
+
let requiresConversion = false;
|
|
631
|
+
for (const prop of objectNode.properties) {
|
|
632
|
+
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|
|
633
|
+
const key = prop.key.name;
|
|
634
|
+
// Handle different value types
|
|
635
|
+
if (prop.value.type === 'Literal') {
|
|
636
|
+
// Static literal value
|
|
637
|
+
result[key] = prop.value.value;
|
|
638
|
+
}
|
|
639
|
+
else if (prop.value.type === 'ObjectExpression') {
|
|
640
|
+
// Nested object - recursively analyze
|
|
641
|
+
const nestedResult = this.analyzeMixedObjectExpression(prop.value, resources, originalSource, schemaProxy);
|
|
642
|
+
if (nestedResult.valid) {
|
|
643
|
+
result[key] = nestedResult.processedObject;
|
|
644
|
+
allDependencies.push(...nestedResult.dependencies);
|
|
645
|
+
if (nestedResult.requiresConversion) {
|
|
646
|
+
requiresConversion = true;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
return { valid: false, processedObject: null, dependencies: [], requiresConversion: false };
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
// Dynamic expression - analyze with expression analyzer
|
|
655
|
+
let valueSource;
|
|
656
|
+
try {
|
|
657
|
+
valueSource = this.getNodeSource(prop.value, originalSource);
|
|
658
|
+
}
|
|
659
|
+
catch (_error) {
|
|
660
|
+
return { valid: false, processedObject: null, dependencies: [], requiresConversion: false };
|
|
661
|
+
}
|
|
662
|
+
// Create analysis context
|
|
663
|
+
const context = {
|
|
664
|
+
type: 'status',
|
|
665
|
+
availableReferences: resources,
|
|
666
|
+
...(schemaProxy && { schemaProxy }),
|
|
667
|
+
factoryType: this.options.factoryType,
|
|
668
|
+
dependencies: []
|
|
669
|
+
};
|
|
670
|
+
try {
|
|
671
|
+
// Analyze the expression using the main analyzer
|
|
672
|
+
const analysisResult = this.expressionAnalyzer.analyzeExpression(valueSource, context);
|
|
673
|
+
if (analysisResult.valid && analysisResult.celExpression) {
|
|
674
|
+
// Dynamic expression - wrap in CEL
|
|
675
|
+
const celString = analysisResult.celExpression.expression;
|
|
676
|
+
result[key] = celString.includes('${') ? celString : `\${${celString}}`;
|
|
677
|
+
allDependencies.push(...analysisResult.dependencies);
|
|
678
|
+
requiresConversion = true;
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
// Try to evaluate as static value
|
|
682
|
+
const staticValue = this.evaluateStaticValue(prop.value);
|
|
683
|
+
if (staticValue !== null) {
|
|
684
|
+
result[key] = staticValue;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
return { valid: false, processedObject: null, dependencies: [], requiresConversion: false };
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
// If expression analysis fails, try static evaluation
|
|
693
|
+
const staticValue = this.evaluateStaticValue(prop.value);
|
|
694
|
+
if (staticValue !== null) {
|
|
695
|
+
result[key] = staticValue;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
console.log(`Failed to analyze property '${key}' of type '${prop.value.type}':`, error);
|
|
699
|
+
return { valid: false, processedObject: null, dependencies: [], requiresConversion: false };
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
// Non-standard property structure
|
|
706
|
+
return { valid: false, processedObject: null, dependencies: [], requiresConversion: false };
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
valid: true,
|
|
711
|
+
processedObject: result,
|
|
712
|
+
dependencies: allDependencies,
|
|
713
|
+
requiresConversion
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Evaluate a static object expression to a JavaScript object
|
|
718
|
+
*/
|
|
719
|
+
evaluateStaticObjectExpression(objectNode) {
|
|
720
|
+
if (objectNode.type !== 'ObjectExpression') {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
const result = {};
|
|
724
|
+
for (const prop of objectNode.properties) {
|
|
725
|
+
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|
|
726
|
+
const key = prop.key.name;
|
|
727
|
+
const value = this.evaluateStaticValue(prop.value);
|
|
728
|
+
if (value === null && prop.value.type !== 'Literal') {
|
|
729
|
+
// If we can't evaluate a property statically, this isn't a static object
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
result[key] = value;
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
// Non-static property structure
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return result;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Evaluate a static value from an AST node
|
|
743
|
+
*/
|
|
744
|
+
evaluateStaticValue(node) {
|
|
745
|
+
switch (node.type) {
|
|
746
|
+
case 'Literal':
|
|
747
|
+
return node.value;
|
|
748
|
+
case 'UnaryExpression':
|
|
749
|
+
if (node.operator === '!' && node.argument?.type === 'Literal') {
|
|
750
|
+
return !node.argument.value;
|
|
751
|
+
}
|
|
752
|
+
if (node.operator === '-' && node.argument?.type === 'Literal' && typeof node.argument.value === 'number') {
|
|
753
|
+
return -node.argument.value;
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
case 'ObjectExpression':
|
|
757
|
+
return this.evaluateStaticObjectExpression(node);
|
|
758
|
+
case 'ArrayExpression': {
|
|
759
|
+
const arrayResult = [];
|
|
760
|
+
for (const element of node.elements) {
|
|
761
|
+
if (element === null) {
|
|
762
|
+
arrayResult.push(null);
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
const elementValue = this.evaluateStaticValue(element);
|
|
766
|
+
if (elementValue === null && element.type !== 'Literal') {
|
|
767
|
+
return null; // Non-static array
|
|
768
|
+
}
|
|
769
|
+
arrayResult.push(elementValue);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return arrayResult;
|
|
773
|
+
}
|
|
774
|
+
default:
|
|
775
|
+
return null; // Non-static value
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Create a CelExpression for a static value
|
|
780
|
+
*/
|
|
781
|
+
createStaticCelExpression(value) {
|
|
782
|
+
return {
|
|
783
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
784
|
+
expression: value
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Extract source code for an AST node using range information
|
|
789
|
+
*/
|
|
790
|
+
getNodeSource(node, originalSource) {
|
|
791
|
+
if (!node || !node.type) {
|
|
792
|
+
return '';
|
|
793
|
+
}
|
|
794
|
+
// Try to use range information to extract the actual source
|
|
795
|
+
let start;
|
|
796
|
+
let end;
|
|
797
|
+
// Check for start/end properties (acorn format)
|
|
798
|
+
if (typeof node.start === 'number' && typeof node.end === 'number') {
|
|
799
|
+
start = node.start;
|
|
800
|
+
end = node.end;
|
|
801
|
+
}
|
|
802
|
+
// Check for range array (alternative format)
|
|
803
|
+
else if (Array.isArray(node.range) && node.range.length === 2) {
|
|
804
|
+
start = node.range[0];
|
|
805
|
+
end = node.range[1];
|
|
806
|
+
}
|
|
807
|
+
// If we have valid range information, extract the source
|
|
808
|
+
if (typeof start === 'number' && typeof end === 'number' &&
|
|
809
|
+
start >= 0 && end <= originalSource.length && start <= end) {
|
|
810
|
+
const extracted = originalSource.slice(start, end).trim();
|
|
811
|
+
if (extracted.length > 0) {
|
|
812
|
+
return extracted;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
// Fallback to manual reconstruction for specific node types
|
|
816
|
+
switch (node.type) {
|
|
817
|
+
case 'Literal':
|
|
818
|
+
return typeof node.value === 'string' ? `"${node.value}"` : String(node.value);
|
|
819
|
+
case 'Identifier':
|
|
820
|
+
return node.name;
|
|
821
|
+
case 'BinaryExpression': {
|
|
822
|
+
const left = this.getNodeSource(node.left, originalSource);
|
|
823
|
+
const right = this.getNodeSource(node.right, originalSource);
|
|
824
|
+
return `${left} ${node.operator} ${right}`;
|
|
825
|
+
}
|
|
826
|
+
case 'MemberExpression': {
|
|
827
|
+
const object = this.getNodeSource(node.object, originalSource);
|
|
828
|
+
if (node.computed) {
|
|
829
|
+
return `${object}[${this.getNodeSource(node.property, originalSource)}]`;
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
const propertyName = node.property.name || this.getNodeSource(node.property, originalSource);
|
|
833
|
+
return `${object}.${propertyName}`;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
case 'ConditionalExpression':
|
|
837
|
+
return `${this.getNodeSource(node.test, originalSource)} ? ${this.getNodeSource(node.consequent, originalSource)} : ${this.getNodeSource(node.alternate, originalSource)}`;
|
|
838
|
+
case 'LogicalExpression':
|
|
839
|
+
return `${this.getNodeSource(node.left, originalSource)} ${node.operator} ${this.getNodeSource(node.right, originalSource)}`;
|
|
840
|
+
case 'CallExpression': {
|
|
841
|
+
const callee = this.getNodeSource(node.callee, originalSource);
|
|
842
|
+
const args = node.arguments.map((arg) => this.getNodeSource(arg, originalSource)).join(', ');
|
|
843
|
+
return `${callee}(${args})`;
|
|
844
|
+
}
|
|
845
|
+
case 'ArrowFunctionExpression': {
|
|
846
|
+
const params = node.params.map((param) => param.name).join(', ');
|
|
847
|
+
const body = this.getNodeSource(node.body, originalSource);
|
|
848
|
+
return `(${params}) => ${body}`;
|
|
849
|
+
}
|
|
850
|
+
case 'TemplateLiteral': {
|
|
851
|
+
// Simplified template literal reconstruction
|
|
852
|
+
let result = '`';
|
|
853
|
+
for (let i = 0; i < node.quasis.length; i++) {
|
|
854
|
+
const quasi = node.quasis[i];
|
|
855
|
+
result += quasi?.value?.raw || quasi?.value?.cooked || '';
|
|
856
|
+
if (i < node.expressions.length) {
|
|
857
|
+
result += `\${${this.getNodeSource(node.expressions[i], originalSource)}}`;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
result += '`';
|
|
861
|
+
return result;
|
|
862
|
+
}
|
|
863
|
+
default:
|
|
864
|
+
// Fallback - return a placeholder
|
|
865
|
+
return `<${node.type}>`;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Generate fallback status CEL expression
|
|
870
|
+
*/
|
|
871
|
+
generateFallbackStatusCel(kubernetesRef) {
|
|
872
|
+
const isSchemaRef = kubernetesRef.resourceId === '__schema__';
|
|
873
|
+
const basePath = isSchemaRef
|
|
874
|
+
? `schema.${kubernetesRef.fieldPath}`
|
|
875
|
+
: `resources.${kubernetesRef.resourceId}.${kubernetesRef.fieldPath}`;
|
|
876
|
+
return {
|
|
877
|
+
[CEL_EXPRESSION_BRAND]: true,
|
|
878
|
+
expression: basePath,
|
|
879
|
+
type: 'unknown'
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Parse status builder function to AST
|
|
884
|
+
*/
|
|
885
|
+
parseStatusBuilderFunction(source) {
|
|
886
|
+
try {
|
|
887
|
+
// Parse the function source with modern JavaScript support using acorn
|
|
888
|
+
const ast = acorn.parse(source, {
|
|
889
|
+
ecmaVersion: 2022, // Support modern JavaScript features including optional chaining
|
|
890
|
+
sourceType: 'script',
|
|
891
|
+
locations: true,
|
|
892
|
+
ranges: true
|
|
893
|
+
});
|
|
894
|
+
return ast;
|
|
895
|
+
}
|
|
896
|
+
catch (error) {
|
|
897
|
+
throw new ConversionError(`Failed to parse status builder function: ${error instanceof Error ? error.message : String(error)}`, source, 'javascript');
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Analyze the return statement of the status builder
|
|
902
|
+
*/
|
|
903
|
+
analyzeReturnStatement(ast, originalSource) {
|
|
904
|
+
let foundReturnStatement = null;
|
|
905
|
+
let foundArrowFunction = null;
|
|
906
|
+
// Find the return statement or arrow function with implicit return
|
|
907
|
+
estraverse.traverse(ast, {
|
|
908
|
+
enter: (node) => {
|
|
909
|
+
if (node.type === 'ReturnStatement') {
|
|
910
|
+
foundReturnStatement = node;
|
|
911
|
+
return estraverse.VisitorOption.Break;
|
|
912
|
+
}
|
|
913
|
+
if (node.type === 'ArrowFunctionExpression' && node.body?.type === 'ObjectExpression') {
|
|
914
|
+
foundArrowFunction = node;
|
|
915
|
+
return estraverse.VisitorOption.Break;
|
|
916
|
+
}
|
|
917
|
+
return undefined;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
// Handle explicit return statement
|
|
921
|
+
if (foundReturnStatement) {
|
|
922
|
+
const returnStatement = foundReturnStatement;
|
|
923
|
+
// Check if it returns an object expression
|
|
924
|
+
const returnsObject = returnStatement.argument?.type === 'ObjectExpression';
|
|
925
|
+
if (!returnsObject) {
|
|
926
|
+
return {
|
|
927
|
+
node: returnStatement,
|
|
928
|
+
returnsObject: false,
|
|
929
|
+
properties: [],
|
|
930
|
+
sourceLocation: {
|
|
931
|
+
line: returnStatement.loc?.start.line || 0,
|
|
932
|
+
column: returnStatement.loc?.start.column || 0,
|
|
933
|
+
length: 0
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
// Analyze properties in the object expression
|
|
938
|
+
const objectExpression = returnStatement.argument;
|
|
939
|
+
const properties = this.analyzeObjectProperties(objectExpression, originalSource);
|
|
940
|
+
return {
|
|
941
|
+
node: returnStatement,
|
|
942
|
+
returnsObject: true,
|
|
943
|
+
properties,
|
|
944
|
+
sourceLocation: {
|
|
945
|
+
line: returnStatement.loc?.start.line || 0,
|
|
946
|
+
column: returnStatement.loc?.start.column || 0,
|
|
947
|
+
length: returnStatement.range ? returnStatement.range[1] - returnStatement.range[0] : 0
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
// Handle arrow function with implicit return
|
|
952
|
+
if (foundArrowFunction && foundArrowFunction.body?.type === 'ObjectExpression') {
|
|
953
|
+
const objectExpression = foundArrowFunction.body;
|
|
954
|
+
const properties = this.analyzeObjectProperties(objectExpression, originalSource);
|
|
955
|
+
return {
|
|
956
|
+
node: foundArrowFunction,
|
|
957
|
+
returnsObject: true,
|
|
958
|
+
properties,
|
|
959
|
+
sourceLocation: {
|
|
960
|
+
line: objectExpression.loc?.start.line || 0,
|
|
961
|
+
column: objectExpression.loc?.start.column || 0,
|
|
962
|
+
length: objectExpression.range ? objectExpression.range[1] - objectExpression.range[0] : 0
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Analyze properties in an object expression
|
|
970
|
+
*/
|
|
971
|
+
analyzeObjectProperties(objectExpression, originalSource) {
|
|
972
|
+
const properties = [];
|
|
973
|
+
for (const prop of objectExpression.properties) {
|
|
974
|
+
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|
|
975
|
+
const propertyAnalysis = {
|
|
976
|
+
name: prop.key.name,
|
|
977
|
+
valueNode: prop.value,
|
|
978
|
+
valueSource: this.getNodeSource(prop.value, originalSource),
|
|
979
|
+
containsKubernetesRefs: false, // Will be determined during field analysis
|
|
980
|
+
sourceLocation: {
|
|
981
|
+
line: prop.loc?.start.line || 0,
|
|
982
|
+
column: prop.loc?.start.column || 0,
|
|
983
|
+
length: prop.range ? prop.range[1] - prop.range[0] : 0
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
properties.push(propertyAnalysis);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return properties;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Analyze a single status field
|
|
993
|
+
*/
|
|
994
|
+
analyzeStatusField(property, resources, originalSource, schemaProxy) {
|
|
995
|
+
const fieldName = property.name;
|
|
996
|
+
const originalExpression = property.valueSource;
|
|
997
|
+
try {
|
|
998
|
+
// Check if this is a static literal value
|
|
999
|
+
if (property.valueNode.type === 'Literal') {
|
|
1000
|
+
// Static literal - return as-is without CEL conversion
|
|
1001
|
+
return {
|
|
1002
|
+
fieldName,
|
|
1003
|
+
originalExpression,
|
|
1004
|
+
celExpression: null,
|
|
1005
|
+
dependencies: [],
|
|
1006
|
+
requiresConversion: false,
|
|
1007
|
+
valid: true,
|
|
1008
|
+
errors: [],
|
|
1009
|
+
sourceMap: [],
|
|
1010
|
+
optionalityAnalysis: [],
|
|
1011
|
+
inferredType: typeof property.valueNode.value,
|
|
1012
|
+
confidence: 1.0
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
// Check if this is the 'undefined' identifier (special case)
|
|
1016
|
+
if (property.valueNode.type === 'Identifier' && property.valueNode.name === 'undefined') {
|
|
1017
|
+
// undefined identifier - return as-is without CEL conversion
|
|
1018
|
+
return {
|
|
1019
|
+
fieldName,
|
|
1020
|
+
originalExpression,
|
|
1021
|
+
celExpression: null,
|
|
1022
|
+
dependencies: [],
|
|
1023
|
+
requiresConversion: false,
|
|
1024
|
+
valid: true,
|
|
1025
|
+
errors: [],
|
|
1026
|
+
sourceMap: [],
|
|
1027
|
+
optionalityAnalysis: [],
|
|
1028
|
+
inferredType: 'undefined',
|
|
1029
|
+
confidence: 1.0
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
// Check if this is a boolean literal represented as UnaryExpression (!0 for true, !1 for false)
|
|
1033
|
+
if (property.valueNode.type === 'UnaryExpression' &&
|
|
1034
|
+
property.valueNode.operator === '!' &&
|
|
1035
|
+
property.valueNode.argument?.type === 'Literal' &&
|
|
1036
|
+
typeof property.valueNode.argument.value === 'number') {
|
|
1037
|
+
const _booleanValue = property.valueNode.argument.value === 0; // !0 = true, !1 = false
|
|
1038
|
+
return {
|
|
1039
|
+
fieldName,
|
|
1040
|
+
originalExpression,
|
|
1041
|
+
celExpression: null,
|
|
1042
|
+
dependencies: [],
|
|
1043
|
+
requiresConversion: false,
|
|
1044
|
+
valid: true,
|
|
1045
|
+
errors: [],
|
|
1046
|
+
sourceMap: [],
|
|
1047
|
+
optionalityAnalysis: [],
|
|
1048
|
+
inferredType: 'boolean',
|
|
1049
|
+
confidence: 1.0
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
// Check if this is a static object expression
|
|
1053
|
+
if (property.valueNode.type === 'ObjectExpression') {
|
|
1054
|
+
// Try to evaluate the object as a static value
|
|
1055
|
+
const staticValue = this.evaluateStaticObjectExpression(property.valueNode);
|
|
1056
|
+
if (staticValue !== null) {
|
|
1057
|
+
return {
|
|
1058
|
+
fieldName,
|
|
1059
|
+
originalExpression,
|
|
1060
|
+
celExpression: null,
|
|
1061
|
+
dependencies: [],
|
|
1062
|
+
requiresConversion: false,
|
|
1063
|
+
valid: true,
|
|
1064
|
+
errors: [],
|
|
1065
|
+
sourceMap: [],
|
|
1066
|
+
optionalityAnalysis: [],
|
|
1067
|
+
inferredType: 'object',
|
|
1068
|
+
confidence: 1.0,
|
|
1069
|
+
staticValue // Store the evaluated static value
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
// This is a mixed object (contains both static and dynamic values)
|
|
1074
|
+
// Recursively analyze each property
|
|
1075
|
+
const mixedObjectResult = this.analyzeMixedObjectExpression(property.valueNode, resources, originalSource, schemaProxy);
|
|
1076
|
+
if (mixedObjectResult.valid) {
|
|
1077
|
+
return {
|
|
1078
|
+
fieldName,
|
|
1079
|
+
originalExpression,
|
|
1080
|
+
celExpression: null,
|
|
1081
|
+
dependencies: mixedObjectResult.dependencies,
|
|
1082
|
+
requiresConversion: mixedObjectResult.requiresConversion,
|
|
1083
|
+
valid: true,
|
|
1084
|
+
errors: [],
|
|
1085
|
+
sourceMap: [],
|
|
1086
|
+
optionalityAnalysis: [],
|
|
1087
|
+
inferredType: 'object',
|
|
1088
|
+
confidence: 1.0,
|
|
1089
|
+
staticValue: mixedObjectResult.processedObject
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
// Mixed object analysis failed, return error
|
|
1094
|
+
return {
|
|
1095
|
+
fieldName,
|
|
1096
|
+
originalExpression,
|
|
1097
|
+
celExpression: null,
|
|
1098
|
+
dependencies: [],
|
|
1099
|
+
requiresConversion: false,
|
|
1100
|
+
valid: false,
|
|
1101
|
+
errors: [new ConversionError(`Failed to analyze mixed object expression for field '${fieldName}'`, originalExpression, 'unknown')],
|
|
1102
|
+
sourceMap: [],
|
|
1103
|
+
optionalityAnalysis: [],
|
|
1104
|
+
inferredType: 'object',
|
|
1105
|
+
confidence: 0.0
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
// Create analysis context
|
|
1111
|
+
const context = {
|
|
1112
|
+
type: 'status',
|
|
1113
|
+
availableReferences: resources,
|
|
1114
|
+
...(schemaProxy && { schemaProxy }),
|
|
1115
|
+
factoryType: this.options.factoryType,
|
|
1116
|
+
...(this.options.includeSourceMapping && { sourceMap: new SourceMapBuilder() }),
|
|
1117
|
+
dependencies: []
|
|
1118
|
+
};
|
|
1119
|
+
// Analyze the expression using the main analyzer
|
|
1120
|
+
const analysisResult = this.expressionAnalyzer.analyzeExpression(originalExpression, context);
|
|
1121
|
+
// Perform optionality analysis if enabled
|
|
1122
|
+
let optionalityAnalysis = [];
|
|
1123
|
+
if (this.options.performOptionalityAnalysis) {
|
|
1124
|
+
const optionalityContext = {
|
|
1125
|
+
...context,
|
|
1126
|
+
hydrationStates: this.options.hydrationStates,
|
|
1127
|
+
conservativeNullSafety: this.options.conservativeNullSafety,
|
|
1128
|
+
useKroConditionals: true,
|
|
1129
|
+
generateHasChecks: true
|
|
1130
|
+
};
|
|
1131
|
+
optionalityAnalysis = this.optionalityHandler.analyzeOptionalityRequirements(originalExpression, optionalityContext);
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
fieldName,
|
|
1135
|
+
originalExpression,
|
|
1136
|
+
celExpression: analysisResult.celExpression,
|
|
1137
|
+
dependencies: analysisResult.dependencies,
|
|
1138
|
+
requiresConversion: analysisResult.requiresConversion,
|
|
1139
|
+
valid: analysisResult.valid,
|
|
1140
|
+
errors: analysisResult.errors,
|
|
1141
|
+
sourceMap: analysisResult.sourceMap,
|
|
1142
|
+
optionalityAnalysis,
|
|
1143
|
+
inferredType: analysisResult.inferredType ? String(analysisResult.inferredType) : undefined,
|
|
1144
|
+
confidence: this.calculateFieldConfidence(analysisResult, optionalityAnalysis)
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
catch (error) {
|
|
1148
|
+
const fieldError = new ConversionError(`Failed to analyze field '${fieldName}': ${error instanceof Error ? error.message : String(error)}`, originalExpression, 'unknown');
|
|
1149
|
+
return {
|
|
1150
|
+
fieldName,
|
|
1151
|
+
originalExpression,
|
|
1152
|
+
celExpression: null,
|
|
1153
|
+
dependencies: [],
|
|
1154
|
+
requiresConversion: false,
|
|
1155
|
+
valid: false,
|
|
1156
|
+
errors: [fieldError],
|
|
1157
|
+
sourceMap: [],
|
|
1158
|
+
optionalityAnalysis: [],
|
|
1159
|
+
inferredType: undefined,
|
|
1160
|
+
confidence: 0
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Calculate confidence level for field analysis
|
|
1166
|
+
*/
|
|
1167
|
+
calculateFieldConfidence(analysisResult, optionalityAnalysis) {
|
|
1168
|
+
let confidence = 0.8; // Base confidence
|
|
1169
|
+
if (analysisResult.valid) {
|
|
1170
|
+
confidence += 0.1;
|
|
1171
|
+
}
|
|
1172
|
+
if (analysisResult.errors.length === 0) {
|
|
1173
|
+
confidence += 0.1;
|
|
1174
|
+
}
|
|
1175
|
+
// Factor in optionality analysis confidence
|
|
1176
|
+
if (optionalityAnalysis.length > 0) {
|
|
1177
|
+
const avgOptionalityConfidence = optionalityAnalysis.reduce((sum, result) => sum + result.confidence, 0) / optionalityAnalysis.length;
|
|
1178
|
+
confidence = (confidence + avgOptionalityConfidence) / 2;
|
|
1179
|
+
}
|
|
1180
|
+
return Math.max(0, Math.min(1, confidence));
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Categorize dependencies into resource and schema references
|
|
1184
|
+
*/
|
|
1185
|
+
categorizeDependencies(dependencies) {
|
|
1186
|
+
const resourceReferences = [];
|
|
1187
|
+
const schemaReferences = [];
|
|
1188
|
+
for (const dep of dependencies) {
|
|
1189
|
+
if (dep.resourceId === '__schema__') {
|
|
1190
|
+
schemaReferences.push(dep);
|
|
1191
|
+
}
|
|
1192
|
+
else {
|
|
1193
|
+
resourceReferences.push(dep);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return { resourceReferences, schemaReferences };
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Analyze status builder function for toResourceGraph integration with KubernetesRef detection
|
|
1201
|
+
*
|
|
1202
|
+
* This is the main integration point for toResourceGraph to analyze status builder functions
|
|
1203
|
+
* and detect KubernetesRef objects from the magic proxy system.
|
|
1204
|
+
*/
|
|
1205
|
+
export function analyzeStatusBuilderForToResourceGraph(statusBuilder, resources, schemaProxy, factoryType = 'kro') {
|
|
1206
|
+
const options = {
|
|
1207
|
+
deepAnalysis: true,
|
|
1208
|
+
includeSourceMapping: true,
|
|
1209
|
+
validateReferences: true,
|
|
1210
|
+
performOptionalityAnalysis: true,
|
|
1211
|
+
factoryType,
|
|
1212
|
+
conservativeNullSafety: true
|
|
1213
|
+
};
|
|
1214
|
+
const analyzer = new StatusBuilderAnalyzer(undefined, options);
|
|
1215
|
+
const result = analyzer.analyzeStatusBuilder(statusBuilder, resources, schemaProxy);
|
|
1216
|
+
// Calculate hydration order based on dependencies
|
|
1217
|
+
const hydrationOrder = calculateStatusFieldHydrationOrder(result.fieldAnalysis);
|
|
1218
|
+
// Determine if any conversion is required
|
|
1219
|
+
const requiresConversion = Array.from(result.fieldAnalysis.values()).some(field => field.requiresConversion);
|
|
1220
|
+
return {
|
|
1221
|
+
statusMappings: result.statusMappings,
|
|
1222
|
+
dependencies: result.allDependencies,
|
|
1223
|
+
hydrationOrder,
|
|
1224
|
+
errors: result.errors,
|
|
1225
|
+
valid: result.valid,
|
|
1226
|
+
requiresConversion
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Calculate hydration order for status fields based on their dependencies
|
|
1231
|
+
*/
|
|
1232
|
+
function calculateStatusFieldHydrationOrder(fieldAnalysis) {
|
|
1233
|
+
const fieldDependencies = new Map();
|
|
1234
|
+
const allFields = Array.from(fieldAnalysis.keys());
|
|
1235
|
+
// Build field-to-field dependencies
|
|
1236
|
+
for (const [fieldName, analysis] of fieldAnalysis) {
|
|
1237
|
+
const fieldDeps = new Set();
|
|
1238
|
+
// For each KubernetesRef dependency, find other fields that might provide that data
|
|
1239
|
+
for (const dep of analysis.dependencies) {
|
|
1240
|
+
if (dep.resourceId !== '__schema__') {
|
|
1241
|
+
// Find fields that reference the same resource
|
|
1242
|
+
for (const [otherField, otherAnalysis] of fieldAnalysis) {
|
|
1243
|
+
if (otherField !== fieldName) {
|
|
1244
|
+
const hasMatchingResource = otherAnalysis.dependencies.some(otherDep => otherDep.resourceId === dep.resourceId);
|
|
1245
|
+
if (hasMatchingResource) {
|
|
1246
|
+
fieldDeps.add(otherField);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
fieldDependencies.set(fieldName, fieldDeps);
|
|
1253
|
+
}
|
|
1254
|
+
// Perform topological sort
|
|
1255
|
+
const visited = new Set();
|
|
1256
|
+
const visiting = new Set();
|
|
1257
|
+
const result = [];
|
|
1258
|
+
const visit = (field) => {
|
|
1259
|
+
if (visiting.has(field)) {
|
|
1260
|
+
// Circular dependency - add to result anyway
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (visited.has(field)) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
visiting.add(field);
|
|
1267
|
+
const deps = fieldDependencies.get(field) || new Set();
|
|
1268
|
+
for (const dep of deps) {
|
|
1269
|
+
visit(dep);
|
|
1270
|
+
}
|
|
1271
|
+
visiting.delete(field);
|
|
1272
|
+
visited.add(field);
|
|
1273
|
+
result.push(field);
|
|
1274
|
+
};
|
|
1275
|
+
for (const field of allFields) {
|
|
1276
|
+
visit(field);
|
|
1277
|
+
}
|
|
1278
|
+
return result;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Convenience function to analyze status builder functions
|
|
1282
|
+
*/
|
|
1283
|
+
export function analyzeStatusBuilder(statusBuilder, resources, schemaProxy, options) {
|
|
1284
|
+
const analyzer = new StatusBuilderAnalyzer(undefined, options);
|
|
1285
|
+
return analyzer.analyzeStatusBuilder(statusBuilder, resources, schemaProxy);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Convenience function to analyze return objects with magic proxy support
|
|
1289
|
+
*/
|
|
1290
|
+
export function analyzeReturnObjectWithMagicProxy(returnObject, resources, schemaProxy, options) {
|
|
1291
|
+
const analyzer = new StatusBuilderAnalyzer(undefined, options);
|
|
1292
|
+
return analyzer.analyzeReturnObjectWithMagicProxy(returnObject, resources, schemaProxy);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Convenience function to generate status context-specific CEL
|
|
1296
|
+
*/
|
|
1297
|
+
export function generateStatusContextCel(kubernetesRef, context, options) {
|
|
1298
|
+
const analyzer = new StatusBuilderAnalyzer(undefined, options);
|
|
1299
|
+
return analyzer.generateStatusContextCel(kubernetesRef, context);
|
|
1300
|
+
}
|
|
1301
|
+
//# sourceMappingURL=status-builder-analyzer.js.map
|