@fjall/generator 0.88.4 → 0.89.2
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/LICENSE +21 -0
- package/dist/src/ast/astCdnParser.d.ts +15 -0
- package/dist/src/ast/astCdnParser.js +114 -0
- package/dist/src/ast/astCommonParser.d.ts +90 -0
- package/dist/src/ast/astCommonParser.js +351 -0
- package/dist/src/ast/astComputeParser.d.ts +14 -2
- package/dist/src/ast/astComputeParser.js +55 -9
- package/dist/src/ast/astDatabaseParser.d.ts +104 -0
- package/dist/src/ast/astDatabaseParser.js +275 -0
- package/dist/src/ast/astInfrastructureParser.d.ts +23 -277
- package/dist/src/ast/astInfrastructureParser.js +83 -1456
- package/dist/src/ast/astMessagingParser.d.ts +25 -0
- package/dist/src/ast/astMessagingParser.js +78 -0
- package/dist/src/ast/astNetworkParser.d.ts +70 -0
- package/dist/src/ast/astNetworkParser.js +219 -0
- package/dist/src/ast/astPatternParser.d.ts +80 -0
- package/dist/src/ast/astPatternParser.js +155 -0
- package/dist/src/ast/astStorageParser.d.ts +18 -0
- package/dist/src/ast/astStorageParser.js +164 -0
- package/dist/src/ast/index.d.ts +1 -0
- package/dist/src/ast/index.js +4 -0
- package/dist/src/dns/bindParser.d.ts +13 -0
- package/dist/src/dns/bindParser.js +224 -0
- package/dist/src/dns/bindWriter.d.ts +2 -0
- package/dist/src/dns/bindWriter.js +52 -0
- package/dist/src/dns/index.d.ts +4 -0
- package/dist/src/dns/index.js +4 -0
- package/dist/src/dns/infrastructureWriter.d.ts +2 -0
- package/dist/src/dns/infrastructureWriter.js +58 -0
- package/dist/src/dns/types.d.ts +82 -0
- package/dist/src/dns/types.js +52 -0
- package/dist/src/generation/common.d.ts +1 -16
- package/dist/src/generation/common.js +2 -28
- package/dist/src/generation/compute.js +77 -28
- package/dist/src/generation/index.d.ts +2 -1
- package/dist/src/generation/index.js +3 -1
- package/dist/src/generation/messagingConnections.d.ts +33 -0
- package/dist/src/generation/messagingConnections.js +73 -0
- package/dist/src/generation/storage.d.ts +5 -1
- package/dist/src/generation/storage.js +9 -1
- package/dist/src/generation/storageConnections.d.ts +3 -3
- package/dist/src/generation/storageConnections.js +8 -4
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/planning/resourcePlanning.js +0 -2
- package/dist/src/presets/tierTypes.d.ts +4 -1
- package/dist/src/schemas/applicationSchemas.d.ts +854 -0
- package/dist/src/schemas/applicationSchemas.js +80 -0
- package/dist/src/schemas/baseSchemas.d.ts +206 -0
- package/dist/src/schemas/baseSchemas.js +248 -0
- package/dist/src/schemas/cdnSchemas.d.ts +61 -0
- package/dist/src/schemas/cdnSchemas.js +62 -0
- package/dist/src/schemas/computeSchemas.d.ts +723 -0
- package/dist/src/schemas/computeSchemas.js +727 -0
- package/dist/src/schemas/constants.d.ts +12 -8
- package/dist/src/schemas/constants.js +14 -4
- package/dist/src/schemas/databaseSchemas.d.ts +638 -0
- package/dist/src/schemas/databaseSchemas.js +366 -0
- package/dist/src/schemas/messagingSchemas.d.ts +20 -0
- package/dist/src/schemas/messagingSchemas.js +29 -0
- package/dist/src/schemas/networkSchemas.d.ts +246 -0
- package/dist/src/schemas/networkSchemas.js +125 -0
- package/dist/src/schemas/patternSchemas.d.ts +708 -0
- package/dist/src/schemas/patternSchemas.js +294 -0
- package/dist/src/schemas/resourceSchemas.d.ts +24 -3530
- package/dist/src/schemas/resourceSchemas.js +24 -2011
- package/dist/src/schemas/storageSchemas.d.ts +93 -0
- package/dist/src/schemas/storageSchemas.js +119 -0
- package/dist/src/util/errorUtils.d.ts +1 -2
- package/dist/src/util/errorUtils.js +1 -15
- package/dist/src/validation/patterns.d.ts +9 -0
- package/dist/src/validation/patterns.js +9 -0
- package/package.json +4 -3
|
@@ -1,215 +1,20 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
|
-
import { ApplicationResourcePlanSchema, getZodErrorMessage,
|
|
2
|
+
import { ApplicationResourcePlanSchema, getZodErrorMessage, } from "../schemas/resourceSchemas.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { constIncludes, S3_ENCRYPTION_TYPES, BACKUP_VAULT_TIERS, } from "../schemas/constants.js";
|
|
5
4
|
import { COST_ALLOCATION_TAG } from "../generation/infrastructure.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
/** Type guard for parsed objects */
|
|
21
|
-
export function isParsedObject(value) {
|
|
22
|
-
return (isPlainObject(value) &&
|
|
23
|
-
!("__identifier" in value) &&
|
|
24
|
-
!("__expression" in value) &&
|
|
25
|
-
!("__call" in value) &&
|
|
26
|
-
!("__unknown" in value));
|
|
27
|
-
}
|
|
28
|
-
/** Safely extract a string from ParsedValue */
|
|
29
|
-
export function asString(value) {
|
|
30
|
-
return typeof value === "string" ? value : undefined;
|
|
31
|
-
}
|
|
32
|
-
/** Safely extract a number from ParsedValue */
|
|
33
|
-
export function asNumber(value) {
|
|
34
|
-
return typeof value === "number" ? value : undefined;
|
|
35
|
-
}
|
|
36
|
-
/** Safely extract a boolean from ParsedValue */
|
|
37
|
-
export function asBoolean(value) {
|
|
38
|
-
return typeof value === "boolean" ? value : undefined;
|
|
39
|
-
}
|
|
40
|
-
/** Safely extract a string array from ParsedValue */
|
|
41
|
-
export function asStringArray(value) {
|
|
42
|
-
if (!Array.isArray(value))
|
|
43
|
-
return undefined;
|
|
44
|
-
const result = [];
|
|
45
|
-
for (const item of value) {
|
|
46
|
-
if (typeof item === "string")
|
|
47
|
-
result.push(item);
|
|
48
|
-
}
|
|
49
|
-
return result.length > 0 ? result : undefined;
|
|
50
|
-
}
|
|
51
|
-
/** Check if an object literal property is a named property assignment */
|
|
52
|
-
function isNamedProperty(prop, name) {
|
|
53
|
-
return (ts.isPropertyAssignment(prop) &&
|
|
54
|
-
ts.isIdentifier(prop.name) &&
|
|
55
|
-
prop.name.text === name);
|
|
56
|
-
}
|
|
57
|
-
/** Check if a node is an App.getApp() call expression */
|
|
58
|
-
function isAppGetAppCall(node) {
|
|
59
|
-
return (ts.isCallExpression(node) &&
|
|
60
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
61
|
-
node.expression.name.text === "getApp" &&
|
|
62
|
-
ts.isIdentifier(node.expression.expression) &&
|
|
63
|
-
node.expression.expression.text === "App");
|
|
64
|
-
}
|
|
65
|
-
/** Check if a node is a chained factory method call (e.g., app.addDatabase(...)) */
|
|
66
|
-
function isFactoryMethodCall(node, methodName) {
|
|
67
|
-
const { expression } = node;
|
|
68
|
-
return (ts.isPropertyAccessExpression(expression) &&
|
|
69
|
-
expression.name.text === methodName &&
|
|
70
|
-
node.arguments.length > 0);
|
|
71
|
-
}
|
|
72
|
-
/** Traverse AST and return the first non-undefined result from the finder. */
|
|
73
|
-
function findFirstInAst(sourceFile, finder) {
|
|
74
|
-
let result;
|
|
75
|
-
const visit = (node) => {
|
|
76
|
-
if (result !== undefined)
|
|
77
|
-
return;
|
|
78
|
-
result = finder(node);
|
|
79
|
-
if (result === undefined)
|
|
80
|
-
ts.forEachChild(node, visit);
|
|
81
|
-
};
|
|
82
|
-
visit(sourceFile);
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
/** Traverse AST and collect all non-null results from the collector. */
|
|
86
|
-
function collectFromAst(sourceFile, collector) {
|
|
87
|
-
const results = [];
|
|
88
|
-
const visit = (node) => {
|
|
89
|
-
const item = collector(node);
|
|
90
|
-
if (item !== null)
|
|
91
|
-
results.push(item);
|
|
92
|
-
ts.forEachChild(node, visit);
|
|
93
|
-
};
|
|
94
|
-
visit(sourceFile);
|
|
95
|
-
return results;
|
|
96
|
-
}
|
|
97
|
-
const FLOW_LOG_DESTINATIONS = ["cloudwatch", "s3"];
|
|
98
|
-
function asFlowLogDestination(value) {
|
|
99
|
-
if (value === undefined)
|
|
100
|
-
return undefined;
|
|
101
|
-
return FLOW_LOG_DESTINATIONS.includes(value)
|
|
102
|
-
? value
|
|
103
|
-
: undefined;
|
|
104
|
-
}
|
|
105
|
-
function parseDeadLetterQueueConfig(value) {
|
|
106
|
-
if (value === undefined || value === null)
|
|
107
|
-
return undefined;
|
|
108
|
-
if (value === false)
|
|
109
|
-
return false;
|
|
110
|
-
if (!isParsedObject(value))
|
|
111
|
-
return undefined;
|
|
112
|
-
return omitUndefined({
|
|
113
|
-
enabled: asBoolean(value.enabled),
|
|
114
|
-
maxReceiveCount: asNumber(value.maxReceiveCount),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
/** Safely extract an object or false from ParsedValue */
|
|
118
|
-
function asObjectOrFalse(value) {
|
|
119
|
-
if (value === undefined || value === null)
|
|
120
|
-
return undefined;
|
|
121
|
-
if (value === false)
|
|
122
|
-
return false;
|
|
123
|
-
if (typeof value === "object" && !Array.isArray(value))
|
|
124
|
-
return value;
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
function parseBooleanOrConfig(value, cast) {
|
|
128
|
-
if (value === false)
|
|
129
|
-
return false;
|
|
130
|
-
return isParsedObject(value) ? cast(value) : undefined;
|
|
131
|
-
}
|
|
132
|
-
function parseOptionalConfig(value, parser) {
|
|
133
|
-
return isParsedObject(value) ? parser(value) : undefined;
|
|
134
|
-
}
|
|
135
|
-
/** Extract a named sub-object from a config, returning undefined if not a plain object */
|
|
136
|
-
function extractSubConfig(rawConfig, field, transform) {
|
|
137
|
-
const value = rawConfig[field];
|
|
138
|
-
return isParsedObject(value) ? transform(value) : undefined;
|
|
139
|
-
}
|
|
140
|
-
function extractVariableName(node) {
|
|
141
|
-
const parent = node.parent;
|
|
142
|
-
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
143
|
-
return parent.name.text;
|
|
144
|
-
}
|
|
145
|
-
return undefined;
|
|
146
|
-
}
|
|
147
|
-
/** Identity cast - trusts that parsed CDK output matches the expected shape.
|
|
148
|
-
* Safe because input is always generated CDK code that was originally validated by Zod schemas. */
|
|
149
|
-
const typed = () => (v) => v;
|
|
150
|
-
/** Narrow a string to a union member, returning undefined if not a valid member */
|
|
151
|
-
export function asStringUnion(value, validValues) {
|
|
152
|
-
const str = asString(value);
|
|
153
|
-
if (str === undefined)
|
|
154
|
-
return undefined;
|
|
155
|
-
return constIncludes(validValues, str) ? str : undefined;
|
|
156
|
-
}
|
|
157
|
-
/** Returns a shallow copy with all undefined-valued properties removed */
|
|
158
|
-
function omitUndefined(obj) {
|
|
159
|
-
const result = {};
|
|
160
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
161
|
-
if (value !== undefined) {
|
|
162
|
-
result[key] = value;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return result;
|
|
166
|
-
}
|
|
167
|
-
/** Reconstruct source text from a parsed AST value for round-trip preservation */
|
|
168
|
-
function parsedValueToSourceText(value) {
|
|
169
|
-
if (value === null)
|
|
170
|
-
return "null";
|
|
171
|
-
if (value === undefined)
|
|
172
|
-
return "undefined";
|
|
173
|
-
if (typeof value === "string")
|
|
174
|
-
return JSON.stringify(value);
|
|
175
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
176
|
-
return String(value);
|
|
177
|
-
if (Array.isArray(value)) {
|
|
178
|
-
return `[${value.map(parsedValueToSourceText).join(", ")}]`;
|
|
179
|
-
}
|
|
180
|
-
if (!isPlainObject(value))
|
|
181
|
-
return String(value);
|
|
182
|
-
if (isIdentifierRef(value))
|
|
183
|
-
return value.__identifier;
|
|
184
|
-
if (isExpressionRef(value))
|
|
185
|
-
return value.__expression;
|
|
186
|
-
if (isCallRef(value))
|
|
187
|
-
return value.__call;
|
|
188
|
-
if ("__unknown" in value && typeof value.__unknown === "string")
|
|
189
|
-
return value.__unknown;
|
|
190
|
-
const entries = Object.entries(value);
|
|
191
|
-
if (entries.length === 0)
|
|
192
|
-
return "{}";
|
|
193
|
-
const inner = entries
|
|
194
|
-
.map(([k, v]) => `${k}: ${parsedValueToSourceText(v)}`)
|
|
195
|
-
.join(", ");
|
|
196
|
-
return `{ ${inner} }`;
|
|
197
|
-
}
|
|
198
|
-
/** Type guard for call references */
|
|
199
|
-
export function isCallRef(value) {
|
|
200
|
-
return isPlainObject(value) && "__call" in value;
|
|
201
|
-
}
|
|
202
|
-
/** Capture properties from a parsed config that are NOT in the known keys set */
|
|
203
|
-
export function captureExtraProperties(config, knownKeys) {
|
|
204
|
-
const extras = [];
|
|
205
|
-
for (const [key, value] of Object.entries(config)) {
|
|
206
|
-
if (knownKeys.has(key) || value === undefined)
|
|
207
|
-
continue;
|
|
208
|
-
extras.push({ key, sourceText: parsedValueToSourceText(value) });
|
|
209
|
-
}
|
|
210
|
-
return extras;
|
|
211
|
-
}
|
|
212
|
-
const DYNAMODB_KEY_TYPES = ["S", "N", "B"];
|
|
5
|
+
// Re-export common types and utilities for downstream consumers
|
|
6
|
+
export { isPlainObject, isIdentifierRef, isExpressionRef, isCallRef, isParsedObject, asString, asNumber, asBoolean, asStringArray, asStringUnion, captureExtraProperties, } from "./astCommonParser.js";
|
|
7
|
+
export { findComputeResources, } from "./astComputeParser.js";
|
|
8
|
+
// Import sub-parsers
|
|
9
|
+
import { isAppGetAppCall, isExpressionRef, findFirstInAst, parseObjectLiteral, findVariableValue, evaluatePropertyAccess, } from "./astCommonParser.js";
|
|
10
|
+
import { findDatabaseResources, splitDynamoDBFromDatabases, convertDatabaseResources, convertDynamoDBResources, } from "./astDatabaseParser.js";
|
|
11
|
+
import { findS3Resources, findS3FactoryResources, convertS3Resources, S3_BUCKET_CLASSES, } from "./astStorageParser.js";
|
|
12
|
+
import { findComputeResources as findComputeResourcesFn, convertComputeResources, } from "./astComputeParser.js";
|
|
13
|
+
import { findSQSResources, convertSQSResources } from "./astMessagingParser.js";
|
|
14
|
+
import { findCDNResource, convertCDNResource } from "./astCdnParser.js";
|
|
15
|
+
import { findVpcId, findNetworkConfig, findBackupConfig, findTunnelConfig, findNetworkResources, convertNetworkConfig, convertBackupConfig, convertTunnelConfig, convertAdditionalNetworks, } from "./astNetworkParser.js";
|
|
16
|
+
import { findPatternResources, applyPatternConfig, } from "./astPatternParser.js";
|
|
17
|
+
// ---- Main entry point ----
|
|
213
18
|
export function parseInfrastructure(content, options) {
|
|
214
19
|
const sourceFile = ts.createSourceFile("infrastructure.ts", content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
215
20
|
const imports = extractImports(sourceFile);
|
|
@@ -227,7 +32,7 @@ export function parseInfrastructure(content, options) {
|
|
|
227
32
|
...findS3Resources(sourceFile),
|
|
228
33
|
...findS3FactoryResources(sourceFile),
|
|
229
34
|
];
|
|
230
|
-
const computeResources =
|
|
35
|
+
const computeResources = findComputeResourcesFn(sourceFile);
|
|
231
36
|
const patternResources = findPatternResources(sourceFile);
|
|
232
37
|
const tags = extractTags(sourceFile);
|
|
233
38
|
const result = {
|
|
@@ -255,6 +60,7 @@ export function parseInfrastructure(content, options) {
|
|
|
255
60
|
}
|
|
256
61
|
return result;
|
|
257
62
|
}
|
|
63
|
+
// ---- Import extraction ----
|
|
258
64
|
function extractImports(sourceFile) {
|
|
259
65
|
const imports = [];
|
|
260
66
|
ts.forEachChild(sourceFile, (node) => {
|
|
@@ -281,6 +87,7 @@ function extractImports(sourceFile) {
|
|
|
281
87
|
});
|
|
282
88
|
return imports;
|
|
283
89
|
}
|
|
90
|
+
// ---- App variable resolution ----
|
|
284
91
|
/** Resolve the app name from the first argument of App.getApp(). */
|
|
285
92
|
function resolveAppName(sourceFile, arg) {
|
|
286
93
|
if (ts.isStringLiteral(arg))
|
|
@@ -310,739 +117,7 @@ function findAppVariable(sourceFile) {
|
|
|
310
117
|
return undefined;
|
|
311
118
|
}) ?? { appVariableName: "", appName: "" });
|
|
312
119
|
}
|
|
313
|
-
|
|
314
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
315
|
-
if (!isAppGetAppCall(node) || node.arguments.length <= 1)
|
|
316
|
-
return undefined;
|
|
317
|
-
const optionsArg = node.arguments[1];
|
|
318
|
-
if (!ts.isObjectLiteralExpression(optionsArg))
|
|
319
|
-
return undefined;
|
|
320
|
-
for (const prop of optionsArg.properties) {
|
|
321
|
-
if (isNamedProperty(prop, "network") &&
|
|
322
|
-
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
323
|
-
for (const networkProp of prop.initializer.properties) {
|
|
324
|
-
if (isNamedProperty(networkProp, "useExisting") &&
|
|
325
|
-
ts.isStringLiteral(networkProp.initializer)) {
|
|
326
|
-
return networkProp.initializer.text;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
return undefined;
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
function parseNetworkConfigFields(parsed) {
|
|
335
|
-
return {
|
|
336
|
-
maxAzs: asNumber(parsed.maxAzs),
|
|
337
|
-
natGateways: parseBooleanOrConfig(parsed.natGateways, (v) => ({
|
|
338
|
-
count: asNumber(v.count),
|
|
339
|
-
})),
|
|
340
|
-
flowLogs: parseBooleanOrConfig(parsed.flowLogs, (v) => ({
|
|
341
|
-
destination: asString(v.destination),
|
|
342
|
-
retentionDays: asNumber(v.retentionDays),
|
|
343
|
-
})),
|
|
344
|
-
vpcEndpoints: isParsedObject(parsed.vpcEndpoints)
|
|
345
|
-
? parsed.vpcEndpoints
|
|
346
|
-
: undefined,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Extract the network configuration from App.getApp() options.
|
|
351
|
-
* This extracts the network config object (not useExisting, which is handled by findVpcId).
|
|
352
|
-
*/
|
|
353
|
-
function findNetworkConfig(sourceFile) {
|
|
354
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
355
|
-
if (!isAppGetAppCall(node) || node.arguments.length <= 1)
|
|
356
|
-
return undefined;
|
|
357
|
-
const optionsArg = node.arguments[1];
|
|
358
|
-
if (!ts.isObjectLiteralExpression(optionsArg))
|
|
359
|
-
return undefined;
|
|
360
|
-
for (const prop of optionsArg.properties) {
|
|
361
|
-
if (isNamedProperty(prop, "network")) {
|
|
362
|
-
if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
|
|
363
|
-
return undefined;
|
|
364
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
365
|
-
const hasUseExisting = prop.initializer.properties.some((p) => isNamedProperty(p, "useExisting"));
|
|
366
|
-
if (!hasUseExisting) {
|
|
367
|
-
const parsed = parseObjectLiteral(prop.initializer);
|
|
368
|
-
return parseNetworkConfigFields(parsed);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return undefined;
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Extract the backup configuration from App.getApp() options.
|
|
378
|
-
* Returns { tier: string } when backup is configured, false when explicitly disabled, undefined when absent.
|
|
379
|
-
*/
|
|
380
|
-
function findBackupConfig(sourceFile) {
|
|
381
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
382
|
-
if (!isAppGetAppCall(node) || node.arguments.length <= 1)
|
|
383
|
-
return undefined;
|
|
384
|
-
const optionsArg = node.arguments[1];
|
|
385
|
-
if (!ts.isObjectLiteralExpression(optionsArg))
|
|
386
|
-
return undefined;
|
|
387
|
-
for (const prop of optionsArg.properties) {
|
|
388
|
-
if (isNamedProperty(prop, "backup")) {
|
|
389
|
-
if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
|
|
390
|
-
return false;
|
|
391
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
392
|
-
const parsed = parseObjectLiteral(prop.initializer);
|
|
393
|
-
const tier = asString(parsed.tier);
|
|
394
|
-
if (tier)
|
|
395
|
-
return { tier };
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return undefined;
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
/**
|
|
403
|
-
* Extract the tunnel configuration from App.getApp() options.
|
|
404
|
-
* Returns true when `tunnel: true`, { instanceType?: string } when object, false when explicitly disabled, undefined when absent.
|
|
405
|
-
*/
|
|
406
|
-
function findTunnelConfig(sourceFile) {
|
|
407
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
408
|
-
if (!isAppGetAppCall(node) || node.arguments.length <= 1)
|
|
409
|
-
return undefined;
|
|
410
|
-
const optionsArg = node.arguments[1];
|
|
411
|
-
if (!ts.isObjectLiteralExpression(optionsArg))
|
|
412
|
-
return undefined;
|
|
413
|
-
for (const prop of optionsArg.properties) {
|
|
414
|
-
if (isNamedProperty(prop, "tunnel")) {
|
|
415
|
-
if (prop.initializer.kind === ts.SyntaxKind.FalseKeyword)
|
|
416
|
-
return false;
|
|
417
|
-
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword)
|
|
418
|
-
return true;
|
|
419
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
420
|
-
const parsed = parseObjectLiteral(prop.initializer);
|
|
421
|
-
const instanceType = asString(parsed.instanceType);
|
|
422
|
-
if (instanceType)
|
|
423
|
-
return { instanceType };
|
|
424
|
-
return {};
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return undefined;
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
/** Type guard for Factory.build() call expressions */
|
|
432
|
-
function isFactoryBuildCall(node, factoryName) {
|
|
433
|
-
return (ts.isCallExpression(node) &&
|
|
434
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
435
|
-
node.expression.name.text === "build" &&
|
|
436
|
-
ts.isIdentifier(node.expression.expression) &&
|
|
437
|
-
node.expression.expression.text === factoryName);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Find all app.addNetwork() calls and extract their configuration.
|
|
441
|
-
*/
|
|
442
|
-
function findNetworkResources(sourceFile) {
|
|
443
|
-
return collectFromAst(sourceFile, (node) => {
|
|
444
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addNetwork"))
|
|
445
|
-
return null;
|
|
446
|
-
const networkArg = node.arguments[0];
|
|
447
|
-
if (!isFactoryBuildCall(networkArg, "NetworkFactory"))
|
|
448
|
-
return null;
|
|
449
|
-
return extractNetworkResource(node, networkArg);
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Extract a network resource from a NetworkFactory.build() call.
|
|
454
|
-
*/
|
|
455
|
-
function extractNetworkResource(addNetworkCall, buildCall) {
|
|
456
|
-
if (buildCall.arguments.length < 2)
|
|
457
|
-
return null;
|
|
458
|
-
const nameArg = buildCall.arguments[0];
|
|
459
|
-
const configArg = buildCall.arguments[1];
|
|
460
|
-
if (!ts.isStringLiteral(nameArg))
|
|
461
|
-
return null;
|
|
462
|
-
const name = nameArg.text;
|
|
463
|
-
const parsed = ts.isObjectLiteralExpression(configArg)
|
|
464
|
-
? parseObjectLiteral(configArg)
|
|
465
|
-
: {};
|
|
466
|
-
const networkFields = parseNetworkConfigFields(parsed);
|
|
467
|
-
const resource = {
|
|
468
|
-
name,
|
|
469
|
-
config: omitUndefined({ ...networkFields }),
|
|
470
|
-
node: addNetworkCall,
|
|
471
|
-
};
|
|
472
|
-
const varName = extractVariableName(addNetworkCall);
|
|
473
|
-
if (varName)
|
|
474
|
-
resource.variableName = varName;
|
|
475
|
-
return resource;
|
|
476
|
-
}
|
|
477
|
-
function findVariableValue(sourceFile, varName) {
|
|
478
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
479
|
-
if (!ts.isVariableStatement(node))
|
|
480
|
-
return undefined;
|
|
481
|
-
for (const declaration of node.declarationList.declarations) {
|
|
482
|
-
if (ts.isIdentifier(declaration.name) &&
|
|
483
|
-
declaration.name.text === varName &&
|
|
484
|
-
declaration.initializer) {
|
|
485
|
-
if (ts.isStringLiteral(declaration.initializer)) {
|
|
486
|
-
return declaration.initializer.text;
|
|
487
|
-
}
|
|
488
|
-
if (ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
489
|
-
const obj = parseObjectLiteral(declaration.initializer);
|
|
490
|
-
return JSON.stringify(obj);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return undefined;
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
function findObjectDeclaration(sourceFile, objName) {
|
|
498
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
499
|
-
if (!ts.isVariableStatement(node))
|
|
500
|
-
return undefined;
|
|
501
|
-
for (const declaration of node.declarationList.declarations) {
|
|
502
|
-
if (ts.isIdentifier(declaration.name) &&
|
|
503
|
-
declaration.name.text === objName &&
|
|
504
|
-
declaration.initializer &&
|
|
505
|
-
ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
506
|
-
return parseObjectLiteral(declaration.initializer);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
return undefined;
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
function evaluatePropertyAccess(sourceFile, node) {
|
|
513
|
-
if (!ts.isIdentifier(node.expression))
|
|
514
|
-
return undefined;
|
|
515
|
-
const objValue = findObjectDeclaration(sourceFile, node.expression.text);
|
|
516
|
-
const propName = node.name.text;
|
|
517
|
-
if (objValue && objValue[propName] !== undefined) {
|
|
518
|
-
return String(objValue[propName]);
|
|
519
|
-
}
|
|
520
|
-
return undefined;
|
|
521
|
-
}
|
|
522
|
-
/** Derive database engine from parsed config */
|
|
523
|
-
function extractDatabaseEngine(config) {
|
|
524
|
-
const directEngine = asString(config.databaseEngine);
|
|
525
|
-
if (directEngine === "postgresql" || directEngine === "mysql")
|
|
526
|
-
return directEngine;
|
|
527
|
-
// Derive from engine expression (e.g. DatabaseInstanceEngine.postgres(...))
|
|
528
|
-
const engine = config.engine;
|
|
529
|
-
if (isCallRef(engine)) {
|
|
530
|
-
const callText = String(engine.__call);
|
|
531
|
-
if (callText.includes("postgres") || callText.includes("auroraPostgres")) {
|
|
532
|
-
return "postgresql";
|
|
533
|
-
}
|
|
534
|
-
if (callText.includes("mysql") || callText.includes("auroraMysql")) {
|
|
535
|
-
return "mysql";
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return undefined;
|
|
539
|
-
}
|
|
540
|
-
/** Extract raw engine expression text (e.g. "DatabaseInstanceEngine.postgres(...)") */
|
|
541
|
-
function extractEngineExpression(config) {
|
|
542
|
-
const engine = config.engine;
|
|
543
|
-
if (isCallRef(engine)) {
|
|
544
|
-
return String(engine.__call);
|
|
545
|
-
}
|
|
546
|
-
return undefined;
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Resolve a template literal expression to its string value.
|
|
550
|
-
* Walks spans: head.text + resolve each span.expression + span.literal.text.
|
|
551
|
-
* Falls back to stripping backticks from getText() if resolution fails.
|
|
552
|
-
*/
|
|
553
|
-
function resolveTemplateLiteral(sourceFile, templateExpr) {
|
|
554
|
-
let result = templateExpr.head.text;
|
|
555
|
-
for (const span of templateExpr.templateSpans) {
|
|
556
|
-
if (ts.isIdentifier(span.expression)) {
|
|
557
|
-
const resolved = findVariableValue(sourceFile, span.expression.text);
|
|
558
|
-
if (resolved) {
|
|
559
|
-
result += resolved;
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
// Can't resolve — fall back to raw getText()
|
|
563
|
-
return templateExpr.getText(sourceFile).replace(/^`|`$/g, "");
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
else if (ts.isPropertyAccessExpression(span.expression)) {
|
|
567
|
-
const resolved = evaluatePropertyAccess(sourceFile, span.expression);
|
|
568
|
-
if (resolved) {
|
|
569
|
-
result += resolved;
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
return templateExpr.getText(sourceFile).replace(/^`|`$/g, "");
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
return templateExpr.getText(sourceFile).replace(/^`|`$/g, "");
|
|
577
|
-
}
|
|
578
|
-
result += span.literal.text;
|
|
579
|
-
}
|
|
580
|
-
return result;
|
|
581
|
-
}
|
|
582
|
-
function findDatabaseResources(sourceFile) {
|
|
583
|
-
return collectFromAst(sourceFile, (node) => {
|
|
584
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addDatabase"))
|
|
585
|
-
return null;
|
|
586
|
-
const databaseArg = node.arguments[0];
|
|
587
|
-
if (!isFactoryBuildCall(databaseArg, "DatabaseFactory"))
|
|
588
|
-
return null;
|
|
589
|
-
return extractDatabaseResource(sourceFile, node, databaseArg);
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
function extractDatabaseResource(sourceFile, addDatabaseCall, buildCall) {
|
|
593
|
-
if (buildCall.arguments.length < 2)
|
|
594
|
-
return null;
|
|
595
|
-
const nameArg = buildCall.arguments[0];
|
|
596
|
-
const configArg = buildCall.arguments[1];
|
|
597
|
-
if (!ts.isStringLiteral(nameArg) && !ts.isTemplateExpression(nameArg)) {
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
const resourceName = ts.isStringLiteral(nameArg)
|
|
601
|
-
? nameArg.text
|
|
602
|
-
: resolveTemplateLiteral(sourceFile, nameArg);
|
|
603
|
-
if (!ts.isObjectLiteralExpression(configArg)) {
|
|
604
|
-
return null;
|
|
605
|
-
}
|
|
606
|
-
const config = parseObjectLiteral(configArg);
|
|
607
|
-
const resource = {
|
|
608
|
-
resourceName,
|
|
609
|
-
type: asString(config.type) ?? "",
|
|
610
|
-
databaseName: asString(config.databaseName) ?? "",
|
|
611
|
-
databaseEngine: extractDatabaseEngine(config),
|
|
612
|
-
engineExpression: extractEngineExpression(config),
|
|
613
|
-
port: asNumber(config.port),
|
|
614
|
-
deletionProtection: asBoolean(config.deletionProtection),
|
|
615
|
-
instanceType: asString(config.instanceType),
|
|
616
|
-
multiAz: asBoolean(config.multiAz),
|
|
617
|
-
publiclyAccessible: asBoolean(config.publiclyAccessible),
|
|
618
|
-
enableSecretRotation: asBoolean(config.enableSecretRotation),
|
|
619
|
-
encryption: parseOptionalConfig(config.encryption, typed()),
|
|
620
|
-
databaseInsights: parseBooleanOrConfig(config.databaseInsights, typed()),
|
|
621
|
-
proxy: parseBooleanOrConfig(config.proxy, typed()),
|
|
622
|
-
readReplica: parseBooleanOrConfig(config.readReplica, typed()),
|
|
623
|
-
credentials: parseOptionalConfig(config.credentials, typed()),
|
|
624
|
-
writer: parseOptionalConfig(config.writer, typed()),
|
|
625
|
-
readers: parseBooleanOrConfig(config.readers, typed()),
|
|
626
|
-
backupRetention: asNumber(config.backupRetention),
|
|
627
|
-
preferredMaintenanceWindow: asString(config.preferredMaintenanceWindow),
|
|
628
|
-
primaryRegion: asString(config.primaryRegion),
|
|
629
|
-
secondaryRegions: asStringArray(config.secondaryRegions),
|
|
630
|
-
globalClusterIdentifier: asString(config.globalClusterIdentifier),
|
|
631
|
-
enableGlobalWriteForwarding: asBoolean(config.enableGlobalWriteForwarding),
|
|
632
|
-
snapshotIdentifier: asString(config.snapshotIdentifier),
|
|
633
|
-
snapshotUsername: asString(config.snapshotUsername),
|
|
634
|
-
node: addDatabaseCall,
|
|
635
|
-
};
|
|
636
|
-
const varName = extractVariableName(addDatabaseCall);
|
|
637
|
-
if (varName)
|
|
638
|
-
resource.variableName = varName;
|
|
639
|
-
const DATABASE_KNOWN_KEYS = new Set([
|
|
640
|
-
"type",
|
|
641
|
-
"databaseName",
|
|
642
|
-
"databaseEngine",
|
|
643
|
-
"engine",
|
|
644
|
-
"vpc",
|
|
645
|
-
"port",
|
|
646
|
-
"deletionProtection",
|
|
647
|
-
"instanceType",
|
|
648
|
-
"multiAz",
|
|
649
|
-
"publiclyAccessible",
|
|
650
|
-
"enableSecretRotation",
|
|
651
|
-
"encryption",
|
|
652
|
-
"databaseInsights",
|
|
653
|
-
"proxy",
|
|
654
|
-
"readReplica",
|
|
655
|
-
"credentials",
|
|
656
|
-
"writer",
|
|
657
|
-
"readers",
|
|
658
|
-
"backupRetention",
|
|
659
|
-
"preferredMaintenanceWindow",
|
|
660
|
-
"primaryRegion",
|
|
661
|
-
"secondaryRegions",
|
|
662
|
-
"globalClusterIdentifier",
|
|
663
|
-
"enableGlobalWriteForwarding",
|
|
664
|
-
"snapshotIdentifier",
|
|
665
|
-
"snapshotUsername",
|
|
666
|
-
]);
|
|
667
|
-
const extras = captureExtraProperties(config, DATABASE_KNOWN_KEYS);
|
|
668
|
-
if (extras.length > 0)
|
|
669
|
-
resource.extraProperties = extras;
|
|
670
|
-
return resource;
|
|
671
|
-
}
|
|
672
|
-
function findS3Resources(sourceFile) {
|
|
673
|
-
return collectFromAst(sourceFile, (node) => {
|
|
674
|
-
if (!ts.isVariableDeclaration(node) || !node.initializer)
|
|
675
|
-
return null;
|
|
676
|
-
if (!ts.isNewExpression(node.initializer))
|
|
677
|
-
return null;
|
|
678
|
-
const newExpr = node.initializer;
|
|
679
|
-
if (!ts.isIdentifier(newExpr.expression))
|
|
680
|
-
return null;
|
|
681
|
-
const bucketClass = newExpr.expression.text;
|
|
682
|
-
if (!S3_BUCKET_CLASSES.has(bucketClass))
|
|
683
|
-
return null;
|
|
684
|
-
return extractS3Resource(node, newExpr, bucketClass);
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
function extractS3Resource(varDecl, newExpr, bucketClass) {
|
|
688
|
-
const variableName = ts.isIdentifier(varDecl.name) ? varDecl.name.text : "";
|
|
689
|
-
if (newExpr.arguments && newExpr.arguments.length >= 2) {
|
|
690
|
-
const nameArg = newExpr.arguments[1];
|
|
691
|
-
const resourceName = ts.isStringLiteral(nameArg) ? nameArg.text : "";
|
|
692
|
-
const configArg = newExpr.arguments.length >= 3 ? newExpr.arguments[2] : undefined;
|
|
693
|
-
const config = configArg && ts.isObjectLiteralExpression(configArg)
|
|
694
|
-
? parseObjectLiteral(configArg)
|
|
695
|
-
: {};
|
|
696
|
-
return {
|
|
697
|
-
variableName,
|
|
698
|
-
resourceName,
|
|
699
|
-
bucketClass,
|
|
700
|
-
config,
|
|
701
|
-
node: varDecl,
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
/** Find S3 resources created via app.addStorage(StorageFactory.build(...)) factory pattern */
|
|
707
|
-
function findS3FactoryResources(sourceFile) {
|
|
708
|
-
return collectFromAst(sourceFile, (node) => {
|
|
709
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addStorage"))
|
|
710
|
-
return null;
|
|
711
|
-
const storageArg = node.arguments[0];
|
|
712
|
-
if (!isFactoryBuildCall(storageArg, "StorageFactory"))
|
|
713
|
-
return null;
|
|
714
|
-
return extractS3FactoryResource(node, storageArg);
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
|
-
function extractS3FactoryResource(addStorageCall, buildCall) {
|
|
718
|
-
if (buildCall.arguments.length < 2)
|
|
719
|
-
return null;
|
|
720
|
-
const nameArg = buildCall.arguments[0];
|
|
721
|
-
const configArg = buildCall.arguments[1];
|
|
722
|
-
if (!ts.isStringLiteral(nameArg) || !ts.isObjectLiteralExpression(configArg))
|
|
723
|
-
return null;
|
|
724
|
-
const config = parseObjectLiteral(configArg);
|
|
725
|
-
const variableName = extractVariableName(addStorageCall) ?? "";
|
|
726
|
-
return {
|
|
727
|
-
variableName,
|
|
728
|
-
resourceName: nameArg.text,
|
|
729
|
-
bucketClass: "StorageFactory",
|
|
730
|
-
config,
|
|
731
|
-
node: addStorageCall,
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
function findComputeResources(sourceFile) {
|
|
735
|
-
return collectFromAst(sourceFile, (node) => {
|
|
736
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addCompute"))
|
|
737
|
-
return null;
|
|
738
|
-
const computeArg = node.arguments[0];
|
|
739
|
-
if (!isFactoryBuildCall(computeArg, "ComputeFactory"))
|
|
740
|
-
return null;
|
|
741
|
-
const resource = extractComputeResource(computeArg);
|
|
742
|
-
if (resource) {
|
|
743
|
-
const varName = extractVariableName(node);
|
|
744
|
-
if (varName)
|
|
745
|
-
resource.variableName = varName;
|
|
746
|
-
}
|
|
747
|
-
return resource;
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
function findSQSResources(sourceFile) {
|
|
751
|
-
return collectFromAst(sourceFile, (node) => {
|
|
752
|
-
if (!ts.isCallExpression(node) ||
|
|
753
|
-
!isFactoryMethodCall(node, "addMessaging"))
|
|
754
|
-
return null;
|
|
755
|
-
const messagingArg = node.arguments[0];
|
|
756
|
-
if (!isFactoryBuildCall(messagingArg, "MessagingFactory"))
|
|
757
|
-
return null;
|
|
758
|
-
return extractSQSResource(node, messagingArg);
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
function extractSQSResource(addMessagingCall, buildCall) {
|
|
762
|
-
if (buildCall.arguments.length < 2)
|
|
763
|
-
return null;
|
|
764
|
-
const nameArg = buildCall.arguments[0];
|
|
765
|
-
const configArg = buildCall.arguments[1];
|
|
766
|
-
if (!ts.isStringLiteral(nameArg) ||
|
|
767
|
-
!ts.isObjectLiteralExpression(configArg)) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
const config = parseObjectLiteral(configArg);
|
|
771
|
-
const resource = {
|
|
772
|
-
resourceName: nameArg.text,
|
|
773
|
-
queueType: asString(config.queueType) ?? "standard",
|
|
774
|
-
visibilityTimeout: asNumber(config.visibilityTimeout),
|
|
775
|
-
retentionPeriod: asNumber(config.messageRetentionPeriod),
|
|
776
|
-
contentBasedDeduplication: asBoolean(config.contentBasedDeduplication),
|
|
777
|
-
node: addMessagingCall,
|
|
778
|
-
};
|
|
779
|
-
const varName = extractVariableName(addMessagingCall);
|
|
780
|
-
if (varName)
|
|
781
|
-
resource.variableName = varName;
|
|
782
|
-
const SQS_KNOWN_KEYS = new Set([
|
|
783
|
-
"type",
|
|
784
|
-
"queueType",
|
|
785
|
-
"visibilityTimeout",
|
|
786
|
-
"messageRetentionPeriod",
|
|
787
|
-
"contentBasedDeduplication",
|
|
788
|
-
]);
|
|
789
|
-
const extras = captureExtraProperties(config, SQS_KNOWN_KEYS);
|
|
790
|
-
if (extras.length > 0)
|
|
791
|
-
resource.extraProperties = extras;
|
|
792
|
-
return resource;
|
|
793
|
-
}
|
|
794
|
-
function findCDNResource(sourceFile) {
|
|
795
|
-
return findFirstInAst(sourceFile, (node) => {
|
|
796
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addCdn"))
|
|
797
|
-
return undefined;
|
|
798
|
-
const cdnArg = node.arguments[0];
|
|
799
|
-
if (!isFactoryBuildCall(cdnArg, "CdnFactory"))
|
|
800
|
-
return undefined;
|
|
801
|
-
return extractCDNResource(cdnArg) ?? undefined;
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
function extractCDNResource(buildCall) {
|
|
805
|
-
if (buildCall.arguments.length < 2)
|
|
806
|
-
return null;
|
|
807
|
-
const nameArg = buildCall.arguments[0];
|
|
808
|
-
const configArg = buildCall.arguments[1];
|
|
809
|
-
if (!ts.isStringLiteral(nameArg) ||
|
|
810
|
-
!ts.isObjectLiteralExpression(configArg)) {
|
|
811
|
-
return null;
|
|
812
|
-
}
|
|
813
|
-
return {
|
|
814
|
-
resourceName: nameArg.text,
|
|
815
|
-
config: parseObjectLiteral(configArg),
|
|
816
|
-
node: buildCall,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
function extractComputeResource(buildCall) {
|
|
820
|
-
if (buildCall.arguments.length < 2)
|
|
821
|
-
return null;
|
|
822
|
-
const nameArg = buildCall.arguments[0];
|
|
823
|
-
const configArg = buildCall.arguments[1];
|
|
824
|
-
if (!ts.isStringLiteral(nameArg) ||
|
|
825
|
-
!ts.isObjectLiteralExpression(configArg)) {
|
|
826
|
-
return null;
|
|
827
|
-
}
|
|
828
|
-
const resourceName = nameArg.text;
|
|
829
|
-
const config = parseObjectLiteral(configArg);
|
|
830
|
-
return {
|
|
831
|
-
resourceName,
|
|
832
|
-
type: asString(config.type) ?? "ecs",
|
|
833
|
-
config,
|
|
834
|
-
node: buildCall,
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Find pattern resources from app.addPattern(PatternFactory.build(...)) calls.
|
|
839
|
-
* Patterns are high-level constructs that encapsulate multiple resources.
|
|
840
|
-
*/
|
|
841
|
-
function findPatternResources(sourceFile) {
|
|
842
|
-
return collectFromAst(sourceFile, (node) => {
|
|
843
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addPattern"))
|
|
844
|
-
return null;
|
|
845
|
-
const patternArg = node.arguments[0];
|
|
846
|
-
if (!isFactoryBuildCall(patternArg, "PatternFactory"))
|
|
847
|
-
return null;
|
|
848
|
-
return extractPatternResource(patternArg, node);
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
function parseLambdaConfig(obj) {
|
|
852
|
-
if (!isParsedObject(obj))
|
|
853
|
-
return undefined;
|
|
854
|
-
const config = {
|
|
855
|
-
...(obj.memorySize !== undefined && {
|
|
856
|
-
memorySize: asNumber(obj.memorySize),
|
|
857
|
-
}),
|
|
858
|
-
...(obj.timeout !== undefined && { timeout: asNumber(obj.timeout) }),
|
|
859
|
-
...(obj.ephemeralStorageSize !== undefined && {
|
|
860
|
-
ephemeralStorageSize: asNumber(obj.ephemeralStorageSize),
|
|
861
|
-
}),
|
|
862
|
-
};
|
|
863
|
-
return Object.keys(config).length > 0 ? config : undefined;
|
|
864
|
-
}
|
|
865
|
-
const VALID_DB_TYPES = ["Instance", "Aurora", "GlobalAurora"];
|
|
866
|
-
const VALID_NON_GLOBAL_DB_TYPES = ["Instance", "Aurora"];
|
|
867
|
-
const VALID_DB_ENGINES = ["postgresql", "mysql"];
|
|
868
|
-
function extractPatternDatabaseConfig(rawConfig) {
|
|
869
|
-
return extractSubConfig(rawConfig, "database", (db) => ({
|
|
870
|
-
type: asStringUnion(db.type, VALID_NON_GLOBAL_DB_TYPES),
|
|
871
|
-
databaseName: asString(db.databaseName),
|
|
872
|
-
databaseEngine: asStringUnion(db.databaseEngine, VALID_DB_ENGINES),
|
|
873
|
-
deletionProtection: asBoolean(db.deletionProtection),
|
|
874
|
-
backupRetention: asNumber(db.backupRetention),
|
|
875
|
-
port: asNumber(db.port),
|
|
876
|
-
publiclyAccessible: asBoolean(db.publiclyAccessible),
|
|
877
|
-
allowedIpCidr: asString(db.allowedIpCidr),
|
|
878
|
-
instanceType: asString(db.instanceType),
|
|
879
|
-
allocatedStorage: asNumber(db.allocatedStorage),
|
|
880
|
-
multiAz: asBoolean(db.multiAz),
|
|
881
|
-
allowVpcAccess: asBoolean(db.allowVpcAccess),
|
|
882
|
-
monitoringInterval: asNumber(db.monitoringInterval),
|
|
883
|
-
preferredMaintenanceWindow: asString(db.preferredMaintenanceWindow),
|
|
884
|
-
snapshotIdentifier: asString(db.snapshotIdentifier),
|
|
885
|
-
snapshotUsername: asString(db.snapshotUsername),
|
|
886
|
-
readReplica: asObjectOrFalse(db.readReplica),
|
|
887
|
-
writer: parseOptionalConfig(db.writer, typed()),
|
|
888
|
-
readers: asObjectOrFalse(db.readers),
|
|
889
|
-
databaseInsights: asObjectOrFalse(db.databaseInsights),
|
|
890
|
-
proxy: asObjectOrFalse(db.proxy),
|
|
891
|
-
credentials: parseOptionalConfig(db.credentials, typed()),
|
|
892
|
-
encryption: parseOptionalConfig(db.encryption, typed()),
|
|
893
|
-
}));
|
|
894
|
-
}
|
|
895
|
-
function extractPatternComputeConfig(rawConfig) {
|
|
896
|
-
return extractSubConfig(rawConfig, "compute", (compute) => ({
|
|
897
|
-
server: parseOptionalConfig(compute.server, parseLambdaConfig),
|
|
898
|
-
imageOptimisation: parseOptionalConfig(compute.imageOptimisation, parseLambdaConfig),
|
|
899
|
-
revalidation: parseOptionalConfig(compute.revalidation, parseLambdaConfig),
|
|
900
|
-
}));
|
|
901
|
-
}
|
|
902
|
-
function extractPatternStorageConfig(rawConfig) {
|
|
903
|
-
const parseVersionedConfig = (obj) => ({
|
|
904
|
-
versioned: asBoolean(obj.versioned),
|
|
905
|
-
});
|
|
906
|
-
return extractSubConfig(rawConfig, "storage", (storage) => ({
|
|
907
|
-
assets: parseOptionalConfig(storage.assets, parseVersionedConfig),
|
|
908
|
-
cache: parseOptionalConfig(storage.cache, parseVersionedConfig),
|
|
909
|
-
media: parseOptionalConfig(storage.media, parseVersionedConfig),
|
|
910
|
-
}));
|
|
911
|
-
}
|
|
912
|
-
function extractPatternMessagingConfig(rawConfig) {
|
|
913
|
-
return extractSubConfig(rawConfig, "messaging", (messaging) => ({
|
|
914
|
-
revalidationQueue: parseOptionalConfig(messaging.revalidationQueue, (queue) => ({
|
|
915
|
-
visibilityTimeout: asNumber(queue.visibilityTimeout),
|
|
916
|
-
messageRetentionPeriod: asNumber(queue.messageRetentionPeriod),
|
|
917
|
-
maxMessageSize: asNumber(queue.maxMessageSize),
|
|
918
|
-
deadLetterQueue: parseDeadLetterQueueConfig(queue.deadLetterQueue),
|
|
919
|
-
})),
|
|
920
|
-
}));
|
|
921
|
-
}
|
|
922
|
-
function extractPatternCdnConfig(rawConfig) {
|
|
923
|
-
return extractSubConfig(rawConfig, "cdn", (cdn) => ({
|
|
924
|
-
domainNames: asStringArray(cdn.domainNames),
|
|
925
|
-
certificateArn: asString(cdn.certificateArn),
|
|
926
|
-
}));
|
|
927
|
-
}
|
|
928
|
-
function extractPatternEnvironmentConfig(rawConfig) {
|
|
929
|
-
return extractSubConfig(rawConfig, "environment", (env) => {
|
|
930
|
-
const environment = Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
|
|
931
|
-
return Object.keys(environment).length > 0 ? environment : undefined;
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Extract pattern resource details from PatternFactory.build() call.
|
|
936
|
-
*/
|
|
937
|
-
function extractPatternResource(buildCall, addPatternCall) {
|
|
938
|
-
if (buildCall.arguments.length < 2)
|
|
939
|
-
return null;
|
|
940
|
-
const constructIdArg = buildCall.arguments[0];
|
|
941
|
-
const configArg = buildCall.arguments[1];
|
|
942
|
-
if (!ts.isStringLiteral(constructIdArg) ||
|
|
943
|
-
!ts.isObjectLiteralExpression(configArg)) {
|
|
944
|
-
return null;
|
|
945
|
-
}
|
|
946
|
-
const constructId = constructIdArg.text;
|
|
947
|
-
const rawConfig = parseObjectLiteral(configArg);
|
|
948
|
-
const patternType = rawConfig.type;
|
|
949
|
-
if (patternType !== "payload" && patternType !== "nextjs") {
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
const variableName = extractVariableName(addPatternCall);
|
|
953
|
-
const name = asString(rawConfig.name);
|
|
954
|
-
if (!name) {
|
|
955
|
-
return null;
|
|
956
|
-
}
|
|
957
|
-
return {
|
|
958
|
-
variableName,
|
|
959
|
-
constructId,
|
|
960
|
-
type: patternType,
|
|
961
|
-
config: {
|
|
962
|
-
name,
|
|
963
|
-
domain: asString(rawConfig.domain),
|
|
964
|
-
database: extractPatternDatabaseConfig(rawConfig),
|
|
965
|
-
compute: extractPatternComputeConfig(rawConfig),
|
|
966
|
-
storage: extractPatternStorageConfig(rawConfig),
|
|
967
|
-
messaging: extractPatternMessagingConfig(rawConfig),
|
|
968
|
-
cdn: extractPatternCdnConfig(rawConfig),
|
|
969
|
-
environment: extractPatternEnvironmentConfig(rawConfig),
|
|
970
|
-
},
|
|
971
|
-
node: buildCall,
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
function parseObjectLiteral(node) {
|
|
975
|
-
const result = {};
|
|
976
|
-
for (const prop of node.properties) {
|
|
977
|
-
if (!ts.isPropertyAssignment(prop))
|
|
978
|
-
continue;
|
|
979
|
-
const key = ts.isIdentifier(prop.name)
|
|
980
|
-
? prop.name.text
|
|
981
|
-
: ts.isStringLiteral(prop.name)
|
|
982
|
-
? prop.name.text
|
|
983
|
-
: undefined;
|
|
984
|
-
if (key === undefined)
|
|
985
|
-
continue;
|
|
986
|
-
result[key] = evaluateExpression(prop.initializer);
|
|
987
|
-
}
|
|
988
|
-
return result;
|
|
989
|
-
}
|
|
990
|
-
function evaluateExpression(node) {
|
|
991
|
-
if (ts.isStringLiteral(node)) {
|
|
992
|
-
return node.text;
|
|
993
|
-
}
|
|
994
|
-
else if (ts.isNumericLiteral(node)) {
|
|
995
|
-
return Number(node.text);
|
|
996
|
-
}
|
|
997
|
-
else if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
998
|
-
return true;
|
|
999
|
-
}
|
|
1000
|
-
else if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
1001
|
-
return false;
|
|
1002
|
-
}
|
|
1003
|
-
else if (ts.isArrayLiteralExpression(node)) {
|
|
1004
|
-
return node.elements.map((element) => evaluateExpression(element));
|
|
1005
|
-
}
|
|
1006
|
-
else if (ts.isObjectLiteralExpression(node)) {
|
|
1007
|
-
return parseObjectLiteral(node);
|
|
1008
|
-
}
|
|
1009
|
-
else if (ts.isIdentifier(node)) {
|
|
1010
|
-
return { __identifier: node.text };
|
|
1011
|
-
}
|
|
1012
|
-
else if (ts.isPropertyAccessExpression(node)) {
|
|
1013
|
-
return { __expression: node.getText() };
|
|
1014
|
-
}
|
|
1015
|
-
else if (ts.isCallExpression(node)) {
|
|
1016
|
-
return { __call: node.getText() };
|
|
1017
|
-
}
|
|
1018
|
-
else if (ts.isBinaryExpression(node) &&
|
|
1019
|
-
node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
1020
|
-
// Handle || operator - return the left side if it's a literal
|
|
1021
|
-
const left = evaluateExpression(node.left);
|
|
1022
|
-
const right = evaluateExpression(node.right);
|
|
1023
|
-
// For simplicity, prefer string literals from the right side for defaults
|
|
1024
|
-
if (typeof right === "string") {
|
|
1025
|
-
return right;
|
|
1026
|
-
}
|
|
1027
|
-
return left;
|
|
1028
|
-
}
|
|
1029
|
-
else if (ts.isConditionalExpression(node)) {
|
|
1030
|
-
// Handle ternary operator - for simplicity, try to evaluate the branches
|
|
1031
|
-
const whenTrue = evaluateExpression(node.whenTrue);
|
|
1032
|
-
const whenFalse = evaluateExpression(node.whenFalse);
|
|
1033
|
-
// Default to the false branch for simplicity
|
|
1034
|
-
if (typeof whenFalse === "string" || typeof whenFalse === "number") {
|
|
1035
|
-
return whenFalse;
|
|
1036
|
-
}
|
|
1037
|
-
return whenTrue;
|
|
1038
|
-
}
|
|
1039
|
-
else if (ts.isTemplateExpression(node) ||
|
|
1040
|
-
ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
1041
|
-
// Handle template literals to preserve them as expressions during round-trip parsing
|
|
1042
|
-
return { __expression: node.getText() };
|
|
1043
|
-
}
|
|
1044
|
-
return { __unknown: node.getText() };
|
|
1045
|
-
}
|
|
120
|
+
// ---- Tags extraction ----
|
|
1046
121
|
function extractTags(sourceFile) {
|
|
1047
122
|
const tags = {};
|
|
1048
123
|
function visit(node) {
|
|
@@ -1070,427 +145,68 @@ function extractTags(sourceFile) {
|
|
|
1070
145
|
visit(sourceFile);
|
|
1071
146
|
return tags;
|
|
1072
147
|
}
|
|
1073
|
-
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
...omitUndefined({
|
|
1078
|
-
maxAzs: config.maxAzs,
|
|
1079
|
-
natGateways: config.natGateways,
|
|
1080
|
-
vpcEndpoints: config.vpcEndpoints,
|
|
1081
|
-
}),
|
|
1082
|
-
...(flowLogs !== undefined && {
|
|
1083
|
-
flowLogs: flowLogs === false
|
|
1084
|
-
? false
|
|
1085
|
-
: omitUndefined({
|
|
1086
|
-
destination: asFlowLogDestination(flowLogs.destination),
|
|
1087
|
-
retentionDays: flowLogs.retentionDays,
|
|
1088
|
-
}),
|
|
1089
|
-
}),
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
function convertNetworkConfig(parsed) {
|
|
1093
|
-
if (!parsed.network)
|
|
1094
|
-
return undefined;
|
|
1095
|
-
return buildNetworkFields(parsed.network);
|
|
1096
|
-
}
|
|
1097
|
-
function convertBackupConfig(parsed) {
|
|
1098
|
-
if (parsed.backup === undefined)
|
|
1099
|
-
return undefined;
|
|
1100
|
-
if (parsed.backup === false)
|
|
1101
|
-
return false;
|
|
1102
|
-
const tier = parsed.backup.tier;
|
|
1103
|
-
if (constIncludes(BACKUP_VAULT_TIERS, tier)) {
|
|
1104
|
-
return { tier: tier };
|
|
148
|
+
// ---- Plan conversion ----
|
|
149
|
+
function applyTagsAndOwner(plan, tags) {
|
|
150
|
+
if (Object.keys(tags).length > 0) {
|
|
151
|
+
plan.tags = { ...tags };
|
|
1105
152
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
function convertTunnelConfig(parsed) {
|
|
1109
|
-
if (parsed.tunnel === undefined)
|
|
1110
|
-
return undefined;
|
|
1111
|
-
if (parsed.tunnel === false)
|
|
1112
|
-
return false;
|
|
1113
|
-
if (parsed.tunnel === true)
|
|
1114
|
-
return {};
|
|
1115
|
-
const result = {};
|
|
1116
|
-
if (parsed.tunnel.instanceType) {
|
|
1117
|
-
result.instanceType = parsed.tunnel.instanceType;
|
|
153
|
+
if (tags[COST_ALLOCATION_TAG]) {
|
|
154
|
+
plan.owner = tags[COST_ALLOCATION_TAG];
|
|
1118
155
|
}
|
|
1119
|
-
return result;
|
|
1120
156
|
}
|
|
1121
|
-
/**
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
157
|
+
/** Imports that the generator always handles -- don't preserve as "additional" */
|
|
158
|
+
const KNOWN_FJALL_IMPORTS = new Set([
|
|
159
|
+
"App",
|
|
160
|
+
"Architecture",
|
|
161
|
+
"DatabaseFactory",
|
|
162
|
+
"StorageFactory",
|
|
163
|
+
"ComputeFactory",
|
|
164
|
+
"getConfig",
|
|
165
|
+
"MessagingFactory",
|
|
166
|
+
"CdnFactory",
|
|
167
|
+
"Code",
|
|
168
|
+
"Runtime",
|
|
169
|
+
"FunctionUrlAuthType",
|
|
170
|
+
"NetworkFactory",
|
|
171
|
+
"PatternFactory",
|
|
172
|
+
]);
|
|
173
|
+
/** Check if a module specifier belongs to a managed (Fjall/CDK) package */
|
|
174
|
+
function isManagedModuleSpecifier(specifier) {
|
|
175
|
+
if (specifier.startsWith("@fjall/"))
|
|
176
|
+
return true;
|
|
177
|
+
if (specifier === "aws-cdk-lib" || specifier.startsWith("aws-cdk-lib/"))
|
|
178
|
+
return true;
|
|
179
|
+
if (specifier === "constructs")
|
|
180
|
+
return true;
|
|
181
|
+
return false;
|
|
1130
182
|
}
|
|
1131
|
-
/**
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Compute additional managed imports that the generator doesn't handle.
|
|
185
|
+
*/
|
|
186
|
+
function computeAdditionalManagedImports(imports) {
|
|
187
|
+
const additional = [];
|
|
188
|
+
for (const imp of imports) {
|
|
189
|
+
if (!isManagedModuleSpecifier(imp.moduleSpecifier))
|
|
190
|
+
continue;
|
|
191
|
+
if (imp.moduleSpecifier === "@fjall/components-infrastructure" ||
|
|
192
|
+
imp.moduleSpecifier === "@fjall/infrastructure") {
|
|
193
|
+
const extraNames = imp.namedImports.filter((n) => !KNOWN_FJALL_IMPORTS.has(n));
|
|
194
|
+
if (extraNames.length > 0) {
|
|
195
|
+
additional.push({
|
|
196
|
+
moduleSpecifier: imp.moduleSpecifier,
|
|
197
|
+
namedImports: extraNames,
|
|
198
|
+
defaultImport: imp.defaultImport,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
1140
201
|
}
|
|
1141
202
|
else {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
return { databases, dynamodb };
|
|
1146
|
-
}
|
|
1147
|
-
/** Extract DynamoDB-specific fields from a parsed database resource */
|
|
1148
|
-
function extractDynamoDBFields(resource) {
|
|
1149
|
-
// Re-read the raw config from the AST node to get DynamoDB-specific fields
|
|
1150
|
-
// The resource was extracted by extractDatabaseResource which parsed the full config
|
|
1151
|
-
const node = resource.node;
|
|
1152
|
-
if (!ts.isCallExpression(node))
|
|
1153
|
-
return null;
|
|
1154
|
-
const buildCallArg = node.arguments[0];
|
|
1155
|
-
if (!ts.isCallExpression(buildCallArg) || buildCallArg.arguments.length < 2)
|
|
1156
|
-
return null;
|
|
1157
|
-
const configArg = buildCallArg.arguments[1];
|
|
1158
|
-
if (!ts.isObjectLiteralExpression(configArg))
|
|
1159
|
-
return null;
|
|
1160
|
-
const config = parseObjectLiteral(configArg);
|
|
1161
|
-
const partitionKey = parseDynamoDBKey(config.partitionKey);
|
|
1162
|
-
if (!partitionKey)
|
|
1163
|
-
return null;
|
|
1164
|
-
const result = {
|
|
1165
|
-
resourceName: resource.resourceName,
|
|
1166
|
-
partitionKey,
|
|
1167
|
-
node: resource.node,
|
|
1168
|
-
};
|
|
1169
|
-
if (resource.variableName)
|
|
1170
|
-
result.variableName = resource.variableName;
|
|
1171
|
-
const sortKey = parseDynamoDBKey(config.sortKey);
|
|
1172
|
-
if (sortKey)
|
|
1173
|
-
result.sortKey = sortKey;
|
|
1174
|
-
if (Array.isArray(config.globalSecondaryIndexes)) {
|
|
1175
|
-
const gsis = config.globalSecondaryIndexes
|
|
1176
|
-
.filter(isParsedObject)
|
|
1177
|
-
.map((gsi) => {
|
|
1178
|
-
const pk = parseDynamoDBKey(gsi.partitionKey);
|
|
1179
|
-
if (!pk)
|
|
1180
|
-
return null;
|
|
1181
|
-
const indexName = asString(gsi.indexName);
|
|
1182
|
-
if (!indexName)
|
|
1183
|
-
return null;
|
|
1184
|
-
const entry = { indexName, partitionKey: pk };
|
|
1185
|
-
const sk = parseDynamoDBKey(gsi.sortKey);
|
|
1186
|
-
if (sk)
|
|
1187
|
-
entry.sortKey = sk;
|
|
1188
|
-
return entry;
|
|
1189
|
-
})
|
|
1190
|
-
.filter((g) => g !== null);
|
|
1191
|
-
if (gsis.length > 0)
|
|
1192
|
-
result.globalSecondaryIndexes = gsis;
|
|
1193
|
-
}
|
|
1194
|
-
const ttlAttribute = asString(config.ttlAttribute);
|
|
1195
|
-
if (ttlAttribute)
|
|
1196
|
-
result.ttlAttribute = ttlAttribute;
|
|
1197
|
-
const stream = asBoolean(config.stream);
|
|
1198
|
-
if (stream !== undefined)
|
|
1199
|
-
result.stream = stream;
|
|
1200
|
-
const DYNAMODB_KNOWN_KEYS = new Set([
|
|
1201
|
-
"type",
|
|
1202
|
-
"databaseName",
|
|
1203
|
-
"partitionKey",
|
|
1204
|
-
"sortKey",
|
|
1205
|
-
"globalSecondaryIndexes",
|
|
1206
|
-
"ttlAttribute",
|
|
1207
|
-
"stream",
|
|
1208
|
-
]);
|
|
1209
|
-
const extras = captureExtraProperties(config, DYNAMODB_KNOWN_KEYS);
|
|
1210
|
-
if (extras.length > 0)
|
|
1211
|
-
result.extraProperties = extras;
|
|
1212
|
-
return result;
|
|
1213
|
-
}
|
|
1214
|
-
/** Convert parsed DynamoDB resources to plan format */
|
|
1215
|
-
function convertDynamoDBResources(parsed) {
|
|
1216
|
-
if (parsed.dynamodbResources.length === 0)
|
|
1217
|
-
return undefined;
|
|
1218
|
-
return parsed.dynamodbResources.map((table) => ({
|
|
1219
|
-
name: table.resourceName,
|
|
1220
|
-
partitionKey: table.partitionKey,
|
|
1221
|
-
...omitUndefined({
|
|
1222
|
-
sortKey: table.sortKey,
|
|
1223
|
-
globalSecondaryIndexes: table.globalSecondaryIndexes,
|
|
1224
|
-
ttlAttribute: table.ttlAttribute,
|
|
1225
|
-
stream: table.stream,
|
|
1226
|
-
variableName: table.variableName,
|
|
1227
|
-
extraProperties: table.extraProperties,
|
|
1228
|
-
}),
|
|
1229
|
-
}));
|
|
1230
|
-
}
|
|
1231
|
-
/** Convert parsed SQS resources to plan format */
|
|
1232
|
-
function convertSQSResources(parsed) {
|
|
1233
|
-
if (parsed.sqsResources.length === 0)
|
|
1234
|
-
return undefined;
|
|
1235
|
-
return parsed.sqsResources.map((queue) => ({
|
|
1236
|
-
name: queue.resourceName,
|
|
1237
|
-
queueType: queue.queueType === "fifo" ? "fifo" : "standard",
|
|
1238
|
-
...omitUndefined({
|
|
1239
|
-
visibilityTimeout: queue.visibilityTimeout,
|
|
1240
|
-
retentionPeriod: queue.retentionPeriod,
|
|
1241
|
-
contentBasedDeduplication: queue.contentBasedDeduplication,
|
|
1242
|
-
variableName: queue.variableName,
|
|
1243
|
-
extraProperties: queue.extraProperties,
|
|
1244
|
-
}),
|
|
1245
|
-
}));
|
|
1246
|
-
}
|
|
1247
|
-
/** Resolve a CDN origin identifier back to its resource name */
|
|
1248
|
-
function resolveOriginIdentifier(identifier, s3Resources, computeResources) {
|
|
1249
|
-
const s3Match = s3Resources.find((s3) => s3.variableName === identifier);
|
|
1250
|
-
if (s3Match)
|
|
1251
|
-
return s3Match.resourceName;
|
|
1252
|
-
// Compute variable names are camelCase of resource name (e.g., myApi → MyApi)
|
|
1253
|
-
const computeMatch = computeResources.find((c) => c.variableName === identifier ||
|
|
1254
|
-
c.resourceName.charAt(0).toLowerCase() + c.resourceName.slice(1) ===
|
|
1255
|
-
identifier);
|
|
1256
|
-
if (computeMatch)
|
|
1257
|
-
return computeMatch.resourceName;
|
|
1258
|
-
return identifier;
|
|
1259
|
-
}
|
|
1260
|
-
const CDN_CACHE_POLICIES = ["CACHING_OPTIMIZED", "CACHING_DISABLED"];
|
|
1261
|
-
/** Convert parsed CDN resource to plan format */
|
|
1262
|
-
function convertCDNResource(parsed) {
|
|
1263
|
-
if (!parsed.cdnResource)
|
|
1264
|
-
return undefined;
|
|
1265
|
-
const cdn = parsed.cdnResource;
|
|
1266
|
-
const config = cdn.config;
|
|
1267
|
-
const resolveOrigin = (val) => {
|
|
1268
|
-
if (isIdentifierRef(val)) {
|
|
1269
|
-
return resolveOriginIdentifier(val.__identifier, parsed.s3Resources, parsed.computeResources);
|
|
1270
|
-
}
|
|
1271
|
-
return asString(val) ?? "";
|
|
1272
|
-
};
|
|
1273
|
-
const defaultOriginRef = resolveOrigin(config.origin);
|
|
1274
|
-
const result = {
|
|
1275
|
-
name: cdn.resourceName,
|
|
1276
|
-
defaultOriginRef,
|
|
1277
|
-
};
|
|
1278
|
-
if (Array.isArray(config.behaviours)) {
|
|
1279
|
-
const behaviours = config.behaviours.filter(isParsedObject).map((b) => ({
|
|
1280
|
-
pathPattern: asString(b.pathPattern) ?? "",
|
|
1281
|
-
originRef: resolveOrigin(b.origin),
|
|
1282
|
-
...omitUndefined({
|
|
1283
|
-
cachePolicy: asStringUnion(b.cachePolicy, CDN_CACHE_POLICIES),
|
|
1284
|
-
}),
|
|
1285
|
-
}));
|
|
1286
|
-
if (behaviours.length > 0)
|
|
1287
|
-
result.behaviours = behaviours;
|
|
1288
|
-
}
|
|
1289
|
-
// customDomain: direct string property or first element of domainNames array
|
|
1290
|
-
const customDomain = asString(config.customDomain);
|
|
1291
|
-
if (customDomain) {
|
|
1292
|
-
result.customDomain = customDomain;
|
|
1293
|
-
}
|
|
1294
|
-
else {
|
|
1295
|
-
const domainNames = asStringArray(config.domainNames);
|
|
1296
|
-
if (domainNames && domainNames.length > 0) {
|
|
1297
|
-
result.customDomain = domainNames[0];
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
const certificateArn = asString(config.certificateArn);
|
|
1301
|
-
if (certificateArn)
|
|
1302
|
-
result.certificateArn = certificateArn;
|
|
1303
|
-
// Extract accessGate — can be false or { type, username, password }
|
|
1304
|
-
if (config.accessGate !== undefined) {
|
|
1305
|
-
if (config.accessGate === false) {
|
|
1306
|
-
result.accessGate = false;
|
|
1307
|
-
}
|
|
1308
|
-
else if (isParsedObject(config.accessGate)) {
|
|
1309
|
-
const gate = config.accessGate;
|
|
1310
|
-
const gateType = asString(gate.type);
|
|
1311
|
-
const username = asString(gate.username);
|
|
1312
|
-
const password = asString(gate.password);
|
|
1313
|
-
if (gateType === "basic-auth" && username && password) {
|
|
1314
|
-
result.accessGate = { type: "basic-auth", username, password };
|
|
203
|
+
// aws-cdk-lib/*, constructs -- preserve entire import
|
|
204
|
+
if (imp.namedImports.length > 0 || imp.defaultImport) {
|
|
205
|
+
additional.push(imp);
|
|
1315
206
|
}
|
|
1316
207
|
}
|
|
1317
208
|
}
|
|
1318
|
-
|
|
1319
|
-
"originType",
|
|
1320
|
-
"origin",
|
|
1321
|
-
"behaviours",
|
|
1322
|
-
"customDomain",
|
|
1323
|
-
"domainNames",
|
|
1324
|
-
"certificateArn",
|
|
1325
|
-
"accessGate",
|
|
1326
|
-
]);
|
|
1327
|
-
const extras = captureExtraProperties(config, CDN_KNOWN_KEYS);
|
|
1328
|
-
if (extras.length > 0)
|
|
1329
|
-
result.extraProperties = extras;
|
|
1330
|
-
return result;
|
|
1331
|
-
}
|
|
1332
|
-
function convertDatabaseResources(parsed) {
|
|
1333
|
-
return parsed.databaseResources
|
|
1334
|
-
.filter((db) => db.type !== "DynamoDB")
|
|
1335
|
-
.map((database) => ({
|
|
1336
|
-
name: database.resourceName,
|
|
1337
|
-
type: constIncludes(VALID_DB_TYPES, database.type)
|
|
1338
|
-
? database.type
|
|
1339
|
-
: "Instance",
|
|
1340
|
-
databaseName: database.databaseName,
|
|
1341
|
-
...omitUndefined({
|
|
1342
|
-
port: database.port,
|
|
1343
|
-
deletionProtection: database.deletionProtection,
|
|
1344
|
-
instanceType: database.instanceType,
|
|
1345
|
-
multiAz: database.multiAz,
|
|
1346
|
-
publiclyAccessible: database.publiclyAccessible,
|
|
1347
|
-
enableSecretRotation: database.enableSecretRotation,
|
|
1348
|
-
encryption: database.encryption,
|
|
1349
|
-
databaseInsights: database.databaseInsights,
|
|
1350
|
-
proxy: database.proxy,
|
|
1351
|
-
readReplica: database.readReplica,
|
|
1352
|
-
credentials: database.credentials,
|
|
1353
|
-
writer: database.writer,
|
|
1354
|
-
readers: database.readers,
|
|
1355
|
-
backupRetention: database.backupRetention,
|
|
1356
|
-
preferredMaintenanceWindow: database.preferredMaintenanceWindow,
|
|
1357
|
-
primaryRegion: database.primaryRegion,
|
|
1358
|
-
secondaryRegions: database.secondaryRegions,
|
|
1359
|
-
globalClusterIdentifier: database.globalClusterIdentifier,
|
|
1360
|
-
enableGlobalWriteForwarding: database.enableGlobalWriteForwarding,
|
|
1361
|
-
variableName: database.variableName,
|
|
1362
|
-
databaseEngine: database.databaseEngine,
|
|
1363
|
-
engineExpression: database.engineExpression,
|
|
1364
|
-
extraProperties: database.extraProperties,
|
|
1365
|
-
}),
|
|
1366
|
-
}));
|
|
1367
|
-
}
|
|
1368
|
-
function convertS3Resources(parsed) {
|
|
1369
|
-
return parsed.s3Resources.map((s3) => ({
|
|
1370
|
-
name: s3.resourceName,
|
|
1371
|
-
...(asString(s3.config.bucketName) && {
|
|
1372
|
-
bucketName: asString(s3.config.bucketName),
|
|
1373
|
-
}),
|
|
1374
|
-
...(asBoolean(s3.config.publicReadAccess) === true && {
|
|
1375
|
-
publicReadAccess: true,
|
|
1376
|
-
}),
|
|
1377
|
-
...(isParsedObject(s3.config.websiteHosting) &&
|
|
1378
|
-
(() => {
|
|
1379
|
-
const hosting = s3.config.websiteHosting;
|
|
1380
|
-
return {
|
|
1381
|
-
websiteHosting: {
|
|
1382
|
-
indexDocument: asString(hosting.indexDocument) ?? "index.html",
|
|
1383
|
-
...(asString(hosting.errorDocument) && {
|
|
1384
|
-
errorDocument: asString(hosting.errorDocument),
|
|
1385
|
-
}),
|
|
1386
|
-
},
|
|
1387
|
-
};
|
|
1388
|
-
})()),
|
|
1389
|
-
...(asStringUnion(s3.config.backupVaultTier, BACKUP_VAULT_TIERS) && {
|
|
1390
|
-
backupVaultTier: asStringUnion(s3.config.backupVaultTier, BACKUP_VAULT_TIERS),
|
|
1391
|
-
}),
|
|
1392
|
-
...(asBoolean(s3.config.versioned) !== undefined && {
|
|
1393
|
-
versioned: asBoolean(s3.config.versioned),
|
|
1394
|
-
}),
|
|
1395
|
-
...(asStringUnion(s3.config.encryption, S3_ENCRYPTION_TYPES) && {
|
|
1396
|
-
encryption: asStringUnion(s3.config.encryption, S3_ENCRYPTION_TYPES),
|
|
1397
|
-
}),
|
|
1398
|
-
...(asString(s3.config.kmsKeyArn) && {
|
|
1399
|
-
kmsKeyArn: asString(s3.config.kmsKeyArn),
|
|
1400
|
-
}),
|
|
1401
|
-
...(Array.isArray(s3.config.cors) &&
|
|
1402
|
-
s3.config.cors.length > 0 && {
|
|
1403
|
-
cors: s3.config.cors.filter(isParsedObject).map((rule) => ({
|
|
1404
|
-
allowedOrigins: Array.isArray(rule.allowedOrigins)
|
|
1405
|
-
? rule.allowedOrigins.filter((o) => typeof o === "string")
|
|
1406
|
-
: [],
|
|
1407
|
-
allowedMethods: Array.isArray(rule.allowedMethods)
|
|
1408
|
-
? rule.allowedMethods.filter((m) => typeof m === "string")
|
|
1409
|
-
: [],
|
|
1410
|
-
})),
|
|
1411
|
-
}),
|
|
1412
|
-
...(isParsedObject(s3.config.deployment) &&
|
|
1413
|
-
(() => {
|
|
1414
|
-
const deployment = s3.config.deployment;
|
|
1415
|
-
return {
|
|
1416
|
-
deployment: {
|
|
1417
|
-
source: asString(deployment.source) ?? "",
|
|
1418
|
-
...(asBoolean(deployment.prune) !== undefined && {
|
|
1419
|
-
prune: asBoolean(deployment.prune),
|
|
1420
|
-
}),
|
|
1421
|
-
...(isParsedObject(deployment.cacheControl) &&
|
|
1422
|
-
(() => {
|
|
1423
|
-
const cache = deployment.cacheControl;
|
|
1424
|
-
return {
|
|
1425
|
-
cacheControl: {
|
|
1426
|
-
...(asNumber(cache.maxAge) !== undefined && {
|
|
1427
|
-
maxAge: asNumber(cache.maxAge),
|
|
1428
|
-
}),
|
|
1429
|
-
...(asBoolean(cache.immutable) !== undefined && {
|
|
1430
|
-
immutable: asBoolean(cache.immutable),
|
|
1431
|
-
}),
|
|
1432
|
-
},
|
|
1433
|
-
};
|
|
1434
|
-
})()),
|
|
1435
|
-
},
|
|
1436
|
-
};
|
|
1437
|
-
})()),
|
|
1438
|
-
...(asStringUnion(s3.config.stackPlacement, S3_STACK_PLACEMENTS) && {
|
|
1439
|
-
stackPlacement: asStringUnion(s3.config.stackPlacement, S3_STACK_PLACEMENTS),
|
|
1440
|
-
}),
|
|
1441
|
-
...(s3.variableName && { variableName: s3.variableName }),
|
|
1442
|
-
...(() => {
|
|
1443
|
-
const S3_KNOWN_KEYS = new Set([
|
|
1444
|
-
"bucketName",
|
|
1445
|
-
"publicReadAccess",
|
|
1446
|
-
"websiteHosting",
|
|
1447
|
-
"backupVaultTier",
|
|
1448
|
-
"versioned",
|
|
1449
|
-
"encryption",
|
|
1450
|
-
"kmsKeyArn",
|
|
1451
|
-
"cors",
|
|
1452
|
-
"deployment",
|
|
1453
|
-
"stackPlacement",
|
|
1454
|
-
]);
|
|
1455
|
-
const extras = captureExtraProperties(s3.config, S3_KNOWN_KEYS);
|
|
1456
|
-
return extras.length > 0 ? { extraProperties: extras } : {};
|
|
1457
|
-
})(),
|
|
1458
|
-
}));
|
|
1459
|
-
}
|
|
1460
|
-
function applyPatternConfig(plan, parsed) {
|
|
1461
|
-
if (!parsed.patternResources || parsed.patternResources.length === 0)
|
|
1462
|
-
return;
|
|
1463
|
-
const patternResource = parsed.patternResources[0];
|
|
1464
|
-
if (patternResource.type !== "payload" && patternResource.type !== "nextjs")
|
|
1465
|
-
return;
|
|
1466
|
-
plan.pattern = patternResource.type;
|
|
1467
|
-
plan.patternConfig = {
|
|
1468
|
-
type: patternResource.type,
|
|
1469
|
-
name: patternResource.config.name,
|
|
1470
|
-
domain: patternResource.config.domain,
|
|
1471
|
-
database: patternResource.config.database,
|
|
1472
|
-
compute: patternResource.config.compute,
|
|
1473
|
-
storage: patternResource.config.storage,
|
|
1474
|
-
messaging: patternResource.config.messaging,
|
|
1475
|
-
cdn: patternResource.config.cdn,
|
|
1476
|
-
environment: patternResource.config.environment,
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
function applyTagsAndOwner(plan, tags) {
|
|
1480
|
-
if (Object.keys(tags).length > 0) {
|
|
1481
|
-
plan.tags = { ...tags };
|
|
1482
|
-
}
|
|
1483
|
-
if (tags[COST_ALLOCATION_TAG]) {
|
|
1484
|
-
plan.owner = tags[COST_ALLOCATION_TAG];
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
function convertAdditionalNetworks(parsed) {
|
|
1488
|
-
if (!parsed.networkResources || parsed.networkResources.length === 0)
|
|
1489
|
-
return undefined;
|
|
1490
|
-
return parsed.networkResources.map((network) => ({
|
|
1491
|
-
name: network.name,
|
|
1492
|
-
...buildNetworkFields(network.config),
|
|
1493
|
-
}));
|
|
209
|
+
return additional.length > 0 ? additional : undefined;
|
|
1494
210
|
}
|
|
1495
211
|
export function convertToResourcePlan(parsed, appName, options) {
|
|
1496
212
|
const plan = {
|
|
@@ -1499,26 +215,24 @@ export function convertToResourcePlan(parsed, appName, options) {
|
|
|
1499
215
|
database: [],
|
|
1500
216
|
s3: [],
|
|
1501
217
|
compute: [],
|
|
1502
|
-
importedResources: [],
|
|
1503
218
|
};
|
|
1504
|
-
applyPatternConfig(plan, parsed);
|
|
219
|
+
applyPatternConfig(plan, parsed.patternResources);
|
|
1505
220
|
if (parsed.vpcId)
|
|
1506
221
|
plan.vpcId = parsed.vpcId;
|
|
1507
|
-
plan.network = convertNetworkConfig(parsed);
|
|
1508
|
-
plan.backup = convertBackupConfig(parsed);
|
|
1509
|
-
plan.tunnel = convertTunnelConfig(parsed);
|
|
1510
|
-
plan.additionalNetworks = convertAdditionalNetworks(parsed);
|
|
222
|
+
plan.network = convertNetworkConfig(parsed.network);
|
|
223
|
+
plan.backup = convertBackupConfig(parsed.backup);
|
|
224
|
+
plan.tunnel = convertTunnelConfig(parsed.tunnel);
|
|
225
|
+
plan.additionalNetworks = convertAdditionalNetworks(parsed.networkResources);
|
|
1511
226
|
applyTagsAndOwner(plan, parsed.tags);
|
|
1512
|
-
plan.database = convertDatabaseResources(parsed);
|
|
1513
|
-
plan.dynamodb = convertDynamoDBResources(parsed);
|
|
1514
|
-
plan.sqs = convertSQSResources(parsed);
|
|
1515
|
-
plan.cdn = convertCDNResource(parsed);
|
|
1516
|
-
plan.s3 = convertS3Resources(parsed);
|
|
1517
|
-
plan.compute = convertComputeResources(parsed, plan);
|
|
227
|
+
plan.database = convertDatabaseResources(parsed.databaseResources);
|
|
228
|
+
plan.dynamodb = convertDynamoDBResources(parsed.dynamodbResources);
|
|
229
|
+
plan.sqs = convertSQSResources(parsed.sqsResources);
|
|
230
|
+
plan.cdn = convertCDNResource(parsed.cdnResource, parsed.s3Resources, parsed.computeResources);
|
|
231
|
+
plan.s3 = convertS3Resources(parsed.s3Resources);
|
|
232
|
+
plan.compute = convertComputeResources(parsed.computeResources, parsed.databaseResources, parsed.s3Resources, plan);
|
|
1518
233
|
plan.additionalManagedImports = computeAdditionalManagedImports(parsed.imports);
|
|
1519
|
-
// Round-trip operations (parse
|
|
234
|
+
// Round-trip operations (parse -> modify -> regenerate) skip validation
|
|
1520
235
|
// to tolerate existing apps with non-standard names (e.g. PascalCase).
|
|
1521
|
-
// TODO: Remove once all apps use lowercase-first names (AppNameSchema).
|
|
1522
236
|
if (options?.skipValidation) {
|
|
1523
237
|
return plan;
|
|
1524
238
|
}
|
|
@@ -1535,7 +249,6 @@ export function convertToResourcePlan(parsed, appName, options) {
|
|
|
1535
249
|
}
|
|
1536
250
|
/**
|
|
1537
251
|
* Classify all top-level statements in an infrastructure file.
|
|
1538
|
-
* This identifies which statements are managed by Fjall vs custom user code.
|
|
1539
252
|
*/
|
|
1540
253
|
export function classifyStatements(sourceFile) {
|
|
1541
254
|
const classifications = [];
|
|
@@ -1552,19 +265,16 @@ export function classifyStatements(sourceFile) {
|
|
|
1552
265
|
});
|
|
1553
266
|
continue;
|
|
1554
267
|
}
|
|
1555
|
-
// Variable statements (app init, S3 buckets, etc.)
|
|
1556
268
|
if (ts.isVariableStatement(statement)) {
|
|
1557
269
|
const classification = classifyVariableStatement(statement, startPos, endPos);
|
|
1558
270
|
classifications.push(classification);
|
|
1559
271
|
continue;
|
|
1560
272
|
}
|
|
1561
|
-
// Expression statements (app.addDatabase, app.addTags, etc.)
|
|
1562
273
|
if (ts.isExpressionStatement(statement)) {
|
|
1563
274
|
const classification = classifyExpressionStatement(statement, startPos, endPos);
|
|
1564
275
|
classifications.push(classification);
|
|
1565
276
|
continue;
|
|
1566
277
|
}
|
|
1567
|
-
// Any other statement is custom code
|
|
1568
278
|
classifications.push({
|
|
1569
279
|
type: "custom",
|
|
1570
280
|
node: statement,
|
|
@@ -1575,74 +285,11 @@ export function classifyStatements(sourceFile) {
|
|
|
1575
285
|
}
|
|
1576
286
|
return classifications;
|
|
1577
287
|
}
|
|
1578
|
-
/** Imports that the generator always handles — don't preserve as "additional" */
|
|
1579
|
-
const KNOWN_FJALL_IMPORTS = new Set([
|
|
1580
|
-
"App",
|
|
1581
|
-
"Architecture",
|
|
1582
|
-
"DatabaseFactory",
|
|
1583
|
-
"StorageFactory",
|
|
1584
|
-
"ComputeFactory",
|
|
1585
|
-
"getConfig",
|
|
1586
|
-
"MessagingFactory",
|
|
1587
|
-
"CdnFactory",
|
|
1588
|
-
"Code",
|
|
1589
|
-
"Runtime",
|
|
1590
|
-
"FunctionUrlAuthType",
|
|
1591
|
-
"NetworkFactory",
|
|
1592
|
-
"PatternFactory",
|
|
1593
|
-
]);
|
|
1594
|
-
/** Check if a module specifier belongs to a managed (Fjall/CDK) package */
|
|
1595
|
-
function isManagedModuleSpecifier(specifier) {
|
|
1596
|
-
if (specifier.startsWith("@fjall/"))
|
|
1597
|
-
return true;
|
|
1598
|
-
if (specifier === "aws-cdk-lib" || specifier.startsWith("aws-cdk-lib/"))
|
|
1599
|
-
return true;
|
|
1600
|
-
if (specifier === "constructs")
|
|
1601
|
-
return true;
|
|
1602
|
-
return false;
|
|
1603
|
-
}
|
|
1604
|
-
/**
|
|
1605
|
-
* Compute additional managed imports that the generator doesn't handle.
|
|
1606
|
-
* For @fjall/components-infrastructure: keep named imports NOT in KNOWN_FJALL_IMPORTS.
|
|
1607
|
-
* For other managed modules (aws-cdk-lib/*, constructs): preserve entire import.
|
|
1608
|
-
*/
|
|
1609
|
-
function computeAdditionalManagedImports(imports) {
|
|
1610
|
-
const additional = [];
|
|
1611
|
-
for (const imp of imports) {
|
|
1612
|
-
if (!isManagedModuleSpecifier(imp.moduleSpecifier))
|
|
1613
|
-
continue;
|
|
1614
|
-
if (imp.moduleSpecifier === "@fjall/components-infrastructure" ||
|
|
1615
|
-
imp.moduleSpecifier === "@fjall/infrastructure") {
|
|
1616
|
-
const extraNames = imp.namedImports.filter((n) => !KNOWN_FJALL_IMPORTS.has(n));
|
|
1617
|
-
if (extraNames.length > 0) {
|
|
1618
|
-
additional.push({
|
|
1619
|
-
moduleSpecifier: imp.moduleSpecifier,
|
|
1620
|
-
namedImports: extraNames,
|
|
1621
|
-
defaultImport: imp.defaultImport,
|
|
1622
|
-
});
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
// aws-cdk-lib/*, constructs — preserve entire import
|
|
1627
|
-
if (imp.namedImports.length > 0 || imp.defaultImport) {
|
|
1628
|
-
additional.push(imp);
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
return additional.length > 0 ? additional : undefined;
|
|
1633
|
-
}
|
|
1634
|
-
/**
|
|
1635
|
-
* Check if an import is managed by Fjall.
|
|
1636
|
-
* Managed imports include: @fjall/infrastructure, aws-cdk-lib/*, constructs
|
|
1637
|
-
*/
|
|
1638
288
|
function isManagedImport(node) {
|
|
1639
289
|
if (!ts.isStringLiteral(node.moduleSpecifier))
|
|
1640
290
|
return false;
|
|
1641
291
|
return isManagedModuleSpecifier(node.moduleSpecifier.text);
|
|
1642
292
|
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Classify a variable statement (const/let/var declarations).
|
|
1645
|
-
*/
|
|
1646
293
|
function classifyVariableStatement(statement, startPos, endPos) {
|
|
1647
294
|
for (const declaration of statement.declarationList.declarations) {
|
|
1648
295
|
if (!declaration.initializer)
|
|
@@ -1706,9 +353,6 @@ function classifyVariableStatement(statement, startPos, endPos) {
|
|
|
1706
353
|
isManaged: false,
|
|
1707
354
|
};
|
|
1708
355
|
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Classify an expression statement (method calls like app.addDatabase).
|
|
1711
|
-
*/
|
|
1712
356
|
function classifyExpressionStatement(statement, startPos, endPos) {
|
|
1713
357
|
const expr = statement.expression;
|
|
1714
358
|
if (ts.isCallExpression(expr)) {
|
|
@@ -1722,9 +366,6 @@ function classifyExpressionStatement(statement, startPos, endPos) {
|
|
|
1722
366
|
isManaged: false,
|
|
1723
367
|
};
|
|
1724
368
|
}
|
|
1725
|
-
/**
|
|
1726
|
-
* Classify a call expression (e.g., app.addDatabase, app.addTags).
|
|
1727
|
-
*/
|
|
1728
369
|
function classifyCallExpression(call, statement, startPos, endPos) {
|
|
1729
370
|
const expression = call.expression;
|
|
1730
371
|
if (ts.isPropertyAccessExpression(expression) &&
|
|
@@ -1769,10 +410,6 @@ function classifyCallExpression(call, statement, startPos, endPos) {
|
|
|
1769
410
|
isManaged: false,
|
|
1770
411
|
};
|
|
1771
412
|
}
|
|
1772
|
-
/**
|
|
1773
|
-
* Extract the resource name from a Factory.build() call.
|
|
1774
|
-
* e.g., app.addDatabase(DatabaseFactory.build("MyDatabase", {...}))
|
|
1775
|
-
*/
|
|
1776
413
|
function extractResourceNameFromFactoryCall(addCall, factoryName) {
|
|
1777
414
|
if (addCall.arguments.length === 0)
|
|
1778
415
|
return undefined;
|
|
@@ -1815,7 +452,6 @@ function determineCustomBlockPosition(lastManaged) {
|
|
|
1815
452
|
}
|
|
1816
453
|
/**
|
|
1817
454
|
* Extract custom code blocks from an infrastructure file.
|
|
1818
|
-
* Returns an array of custom code blocks with their positions relative to managed resources.
|
|
1819
455
|
*/
|
|
1820
456
|
export function extractCustomCodeBlocks(sourceFile, precomputedClassifications) {
|
|
1821
457
|
const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
|
|
@@ -1842,9 +478,6 @@ export function extractCustomCodeBlocks(sourceFile, precomputedClassifications)
|
|
|
1842
478
|
}
|
|
1843
479
|
return customBlocks;
|
|
1844
480
|
}
|
|
1845
|
-
/**
|
|
1846
|
-
* Get the full statement text including any leading comments and whitespace.
|
|
1847
|
-
*/
|
|
1848
481
|
function getStatementTextWithComments(sourceFile, sourceText, node) {
|
|
1849
482
|
const fullStart = node.getFullStart();
|
|
1850
483
|
const end = node.getEnd();
|
|
@@ -1853,9 +486,6 @@ function getStatementTextWithComments(sourceFile, sourceText, node) {
|
|
|
1853
486
|
const startIndex = lines.findIndex((line) => line.trim() !== "");
|
|
1854
487
|
return lines.slice(startIndex === -1 ? 0 : startIndex).join("\n");
|
|
1855
488
|
}
|
|
1856
|
-
/**
|
|
1857
|
-
* Extract leading comments for a node.
|
|
1858
|
-
*/
|
|
1859
489
|
function extractLeadingComments(sourceFile, sourceText, node) {
|
|
1860
490
|
const comments = [];
|
|
1861
491
|
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1869,7 +499,6 @@ function extractLeadingComments(sourceFile, sourceText, node) {
|
|
|
1869
499
|
}
|
|
1870
500
|
/**
|
|
1871
501
|
* Find the position information for a specific managed resource.
|
|
1872
|
-
* Useful for surgical updates.
|
|
1873
502
|
*/
|
|
1874
503
|
export function findManagedResourcePosition(sourceFile, resourceType, resourceName, precomputedClassifications) {
|
|
1875
504
|
const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
|
|
@@ -1887,7 +516,6 @@ export function findManagedResourcePosition(sourceFile, resourceType, resourceNa
|
|
|
1887
516
|
}
|
|
1888
517
|
/**
|
|
1889
518
|
* Get the last managed statement of a specific type.
|
|
1890
|
-
* Used to determine insertion points for new resources.
|
|
1891
519
|
*/
|
|
1892
520
|
export function getLastManagedStatementOfType(sourceFile, type, precomputedClassifications) {
|
|
1893
521
|
const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
|
|
@@ -1901,7 +529,6 @@ export function getLastManagedStatementOfType(sourceFile, type, precomputedClass
|
|
|
1901
529
|
}
|
|
1902
530
|
/**
|
|
1903
531
|
* Get all managed resources grouped by type.
|
|
1904
|
-
* Useful for understanding the structure of an infrastructure file.
|
|
1905
532
|
*/
|
|
1906
533
|
export function getManagedResourcesByType(sourceFile, precomputedClassifications) {
|
|
1907
534
|
const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
|