@fjall/generator 0.89.5 → 0.94.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.
Files changed (168) hide show
  1. package/LICENSE +50 -21
  2. package/README.md +28 -0
  3. package/dist/.minified +1 -0
  4. package/dist/src/ast/astCdnParser.d.ts +5 -0
  5. package/dist/src/ast/astCdnParser.js +1 -114
  6. package/dist/src/ast/astCommonParser.d.ts +6 -17
  7. package/dist/src/ast/astCommonParser.js +1 -351
  8. package/dist/src/ast/astComputeConnectionParser.d.ts +18 -0
  9. package/dist/src/ast/astComputeConnectionParser.js +1 -0
  10. package/dist/src/ast/astComputeParser.d.ts +6 -0
  11. package/dist/src/ast/astComputeParser.js +1 -473
  12. package/dist/src/ast/astComputeParserHelpers.d.ts +21 -0
  13. package/dist/src/ast/astComputeParserHelpers.js +1 -0
  14. package/dist/src/ast/astDatabaseParser.d.ts +9 -24
  15. package/dist/src/ast/astDatabaseParser.js +1 -275
  16. package/dist/src/ast/astDomainParser.d.ts +139 -0
  17. package/dist/src/ast/astDomainParser.js +1 -0
  18. package/dist/src/ast/astDynamoDBParser.d.ts +35 -0
  19. package/dist/src/ast/astDynamoDBParser.js +1 -0
  20. package/dist/src/ast/astExpressionEvaluator.d.ts +23 -0
  21. package/dist/src/ast/astExpressionEvaluator.js +1 -0
  22. package/dist/src/ast/astInfrastructureParser.d.ts +12 -49
  23. package/dist/src/ast/astInfrastructureParser.js +1 -552
  24. package/dist/src/ast/astMessagingParser.d.ts +5 -0
  25. package/dist/src/ast/astMessagingParser.js +1 -78
  26. package/dist/src/ast/astNetworkParser.d.ts +6 -0
  27. package/dist/src/ast/astNetworkParser.js +1 -219
  28. package/dist/src/ast/astPatternParser.d.ts +6 -0
  29. package/dist/src/ast/astPatternParser.js +1 -155
  30. package/dist/src/ast/astPlanConverter.d.ts +11 -0
  31. package/dist/src/ast/astPlanConverter.js +2 -0
  32. package/dist/src/ast/astStatementClassifier.d.ts +24 -0
  33. package/dist/src/ast/astStatementClassifier.js +1 -0
  34. package/dist/src/ast/astStatementQueries.d.ts +21 -0
  35. package/dist/src/ast/astStatementQueries.js +3 -0
  36. package/dist/src/ast/astStorageParser.d.ts +5 -0
  37. package/dist/src/ast/astStorageParser.js +1 -164
  38. package/dist/src/ast/astSurgicalModification.js +19 -400
  39. package/dist/src/ast/astTestHelpers.d.ts +635 -0
  40. package/dist/src/ast/astTestHelpers.js +1 -0
  41. package/dist/src/ast/index.d.ts +1 -0
  42. package/dist/src/ast/index.js +1 -6
  43. package/dist/src/aws/regions.js +1 -254
  44. package/dist/src/codemod/_internal.d.ts +12 -0
  45. package/dist/src/codemod/_internal.js +1 -0
  46. package/dist/src/codemod/edits/addResource/bodyIndex.d.ts +34 -0
  47. package/dist/src/codemod/edits/addResource/bodyIndex.js +1 -0
  48. package/dist/src/codemod/edits/addResource/propertyBuilder.d.ts +7 -0
  49. package/dist/src/codemod/edits/addResource/propertyBuilder.js +1 -0
  50. package/dist/src/codemod/edits/addResource.d.ts +9 -0
  51. package/dist/src/codemod/edits/addResource.js +1 -0
  52. package/dist/src/codemod/edits/ensureImports.d.ts +26 -0
  53. package/dist/src/codemod/edits/ensureImports.js +1 -0
  54. package/dist/src/codemod/edits/findInsertionPosition.d.ts +39 -0
  55. package/dist/src/codemod/edits/findInsertionPosition.js +1 -0
  56. package/dist/src/codemod/edits/index.d.ts +5 -0
  57. package/dist/src/codemod/edits/index.js +1 -0
  58. package/dist/src/codemod/edits/modifyResource/literalConversion.d.ts +37 -0
  59. package/dist/src/codemod/edits/modifyResource/literalConversion.js +1 -0
  60. package/dist/src/codemod/edits/modifyResource.d.ts +9 -0
  61. package/dist/src/codemod/edits/modifyResource.js +1 -0
  62. package/dist/src/codemod/edits/removeResource/commentHeuristic.d.ts +31 -0
  63. package/dist/src/codemod/edits/removeResource/commentHeuristic.js +1 -0
  64. package/dist/src/codemod/edits/removeResource/importPruning.d.ts +8 -0
  65. package/dist/src/codemod/edits/removeResource/importPruning.js +1 -0
  66. package/dist/src/codemod/edits/removeResource.d.ts +10 -0
  67. package/dist/src/codemod/edits/removeResource.js +1 -0
  68. package/dist/src/codemod/fileRewriter/builders.d.ts +57 -0
  69. package/dist/src/codemod/fileRewriter/builders.js +1 -0
  70. package/dist/src/codemod/fileRewriter/index.d.ts +4 -0
  71. package/dist/src/codemod/fileRewriter/index.js +1 -0
  72. package/dist/src/codemod/fileRewriter/locateByRange.d.ts +65 -0
  73. package/dist/src/codemod/fileRewriter/locateByRange.js +1 -0
  74. package/dist/src/codemod/fileRewriter/parse.d.ts +18 -0
  75. package/dist/src/codemod/fileRewriter/parse.js +2 -0
  76. package/dist/src/codemod/fileRewriter/print.d.ts +46 -0
  77. package/dist/src/codemod/fileRewriter/print.js +4 -0
  78. package/dist/src/codemod/historyPaths.d.ts +2 -0
  79. package/dist/src/codemod/historyPaths.js +1 -0
  80. package/dist/src/codemod/index.d.ts +7 -0
  81. package/dist/src/codemod/index.js +1 -0
  82. package/dist/src/codemod/listResources.d.ts +4 -0
  83. package/dist/src/codemod/listResources.js +1 -0
  84. package/dist/src/codemod/registry.d.ts +42 -0
  85. package/dist/src/codemod/registry.js +1 -0
  86. package/dist/src/codemod/semanticIndex/findReferences.d.ts +15 -0
  87. package/dist/src/codemod/semanticIndex/findReferences.js +2 -0
  88. package/dist/src/codemod/semanticIndex/index.d.ts +4 -0
  89. package/dist/src/codemod/semanticIndex/index.js +1 -0
  90. package/dist/src/codemod/semanticIndex/listImports.d.ts +24 -0
  91. package/dist/src/codemod/semanticIndex/listImports.js +1 -0
  92. package/dist/src/codemod/semanticIndex/locateByShape.d.ts +28 -0
  93. package/dist/src/codemod/semanticIndex/locateByShape.js +1 -0
  94. package/dist/src/codemod/semanticIndex/projectCache.d.ts +14 -0
  95. package/dist/src/codemod/semanticIndex/projectCache.js +1 -0
  96. package/dist/src/codemod/types.d.ts +172 -0
  97. package/dist/src/codemod/types.js +1 -0
  98. package/dist/src/dns/bindParser.js +2 -224
  99. package/dist/src/dns/bindWriter.js +3 -52
  100. package/dist/src/dns/domainFileGenerator.d.ts +20 -0
  101. package/dist/src/dns/domainFileGenerator.js +207 -0
  102. package/dist/src/dns/domainRecords.d.ts +164 -0
  103. package/dist/src/dns/domainRecords.js +1 -0
  104. package/dist/src/dns/index.d.ts +2 -1
  105. package/dist/src/dns/index.js +1 -4
  106. package/dist/src/dns/types.js +1 -52
  107. package/dist/src/generation/common.js +6 -161
  108. package/dist/src/generation/compute.js +82 -590
  109. package/dist/src/generation/database.js +12 -198
  110. package/dist/src/generation/generatePatternCode.d.ts +58 -0
  111. package/dist/src/generation/generatePatternCode.js +33 -0
  112. package/dist/src/generation/index.js +1 -20
  113. package/dist/src/generation/infrastructure.d.ts +1 -5
  114. package/dist/src/generation/infrastructure.js +35 -377
  115. package/dist/src/generation/messagingConnections.js +1 -73
  116. package/dist/src/generation/storage.d.ts +0 -15
  117. package/dist/src/generation/storage.js +35 -168
  118. package/dist/src/generation/storageConnections.js +1 -75
  119. package/dist/src/planning/generateResourceChange.d.ts +21 -0
  120. package/dist/src/planning/generateResourceChange.js +1 -0
  121. package/dist/src/planning/index.d.ts +3 -0
  122. package/dist/src/planning/index.js +1 -1
  123. package/dist/src/planning/resourceAddition.d.ts +154 -0
  124. package/dist/src/planning/resourceAddition.js +1 -0
  125. package/dist/src/planning/resourceConnections.d.ts +19 -0
  126. package/dist/src/planning/resourceConnections.js +1 -0
  127. package/dist/src/planning/resourcePlanning.js +1 -214
  128. package/dist/src/presets/index.js +1 -3
  129. package/dist/src/presets/patternTierPresets.js +1 -131
  130. package/dist/src/presets/storagePresets.js +1 -36
  131. package/dist/src/presets/tierPresets.d.ts +5 -8
  132. package/dist/src/presets/tierPresets.js +1 -384
  133. package/dist/src/presets/tierTypes.d.ts +1 -1
  134. package/dist/src/presets/tierTypes.js +0 -7
  135. package/dist/src/schemas/alarmSchemas.d.ts +19 -0
  136. package/dist/src/schemas/alarmSchemas.js +1 -0
  137. package/dist/src/schemas/applicationSchemas.d.ts +22 -6
  138. package/dist/src/schemas/applicationSchemas.js +1 -80
  139. package/dist/src/schemas/baseSchemas.d.ts +8 -3
  140. package/dist/src/schemas/baseSchemas.js +2 -248
  141. package/dist/src/schemas/cdnSchemas.js +1 -62
  142. package/dist/src/schemas/computeSchemas.d.ts +25 -3
  143. package/dist/src/schemas/computeSchemas.js +1 -727
  144. package/dist/src/schemas/constants.d.ts +5 -7
  145. package/dist/src/schemas/constants.js +1 -218
  146. package/dist/src/schemas/databaseSchemas.d.ts +6 -1
  147. package/dist/src/schemas/databaseSchemas.js +1 -366
  148. package/dist/src/schemas/index.js +1 -3
  149. package/dist/src/schemas/instanceTypeArchitecture.js +1 -75
  150. package/dist/src/schemas/messagingSchemas.js +1 -29
  151. package/dist/src/schemas/networkSchemas.js +1 -125
  152. package/dist/src/schemas/patternSchemas.d.ts +1 -1
  153. package/dist/src/schemas/patternSchemas.js +1 -294
  154. package/dist/src/schemas/resourceSchemas.d.ts +1 -0
  155. package/dist/src/schemas/resourceSchemas.js +1 -28
  156. package/dist/src/schemas/sharedTypes.d.ts +18 -0
  157. package/dist/src/schemas/sharedTypes.js +1 -0
  158. package/dist/src/schemas/storageSchemas.d.ts +1 -0
  159. package/dist/src/schemas/storageSchemas.js +1 -119
  160. package/dist/src/types/Result.js +1 -31
  161. package/dist/src/util/errorUtils.js +1 -1
  162. package/dist/src/validation/patterns.d.ts +9 -0
  163. package/dist/src/validation/patterns.js +1 -369
  164. package/dist/src/version.d.ts +1 -1
  165. package/dist/src/version.js +1 -1
  166. package/package.json +29 -9
  167. package/dist/src/dns/infrastructureWriter.d.ts +0 -2
  168. package/dist/src/dns/infrastructureWriter.js +0 -58
@@ -1,552 +1 @@
1
- import * as ts from "typescript";
2
- import { ApplicationResourcePlanSchema, getZodErrorMessage, } from "../schemas/resourceSchemas.js";
3
- import { z } from "zod";
4
- import { COST_ALLOCATION_TAG } from "../generation/infrastructure.js";
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 ----
18
- export function parseInfrastructure(content, options) {
19
- const sourceFile = ts.createSourceFile("infrastructure.ts", content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
20
- const imports = extractImports(sourceFile);
21
- const { appVariableName, appName } = findAppVariable(sourceFile);
22
- const vpcId = findVpcId(sourceFile);
23
- const network = findNetworkConfig(sourceFile);
24
- const backup = findBackupConfig(sourceFile);
25
- const tunnel = findTunnelConfig(sourceFile);
26
- const networkResources = findNetworkResources(sourceFile);
27
- const allDatabaseResources = findDatabaseResources(sourceFile);
28
- const { databases: databaseResources, dynamodb: dynamodbResources } = splitDynamoDBFromDatabases(allDatabaseResources);
29
- const sqsResources = findSQSResources(sourceFile);
30
- const cdnResource = findCDNResource(sourceFile);
31
- const s3Resources = [
32
- ...findS3Resources(sourceFile),
33
- ...findS3FactoryResources(sourceFile),
34
- ];
35
- const computeResources = findComputeResourcesFn(sourceFile);
36
- const patternResources = findPatternResources(sourceFile);
37
- const tags = extractTags(sourceFile);
38
- const result = {
39
- sourceFile,
40
- imports,
41
- appVariableName,
42
- appName,
43
- vpcId,
44
- network,
45
- backup,
46
- tunnel,
47
- networkResources,
48
- databaseResources,
49
- dynamodbResources,
50
- sqsResources,
51
- cdnResource,
52
- s3Resources,
53
- computeResources,
54
- patternResources,
55
- tags,
56
- };
57
- if (options?.extractCustomCode) {
58
- result.customCodeBlocks = extractCustomCodeBlocks(sourceFile);
59
- result.classifiedStatements = classifyStatements(sourceFile);
60
- }
61
- return result;
62
- }
63
- // ---- Import extraction ----
64
- function extractImports(sourceFile) {
65
- const imports = [];
66
- ts.forEachChild(sourceFile, (node) => {
67
- if (ts.isImportDeclaration(node)) {
68
- if (!ts.isStringLiteral(node.moduleSpecifier))
69
- return;
70
- const moduleSpecifier = node.moduleSpecifier.text;
71
- const importInfo = {
72
- moduleSpecifier,
73
- namedImports: [],
74
- };
75
- if (node.importClause) {
76
- if (node.importClause.name) {
77
- importInfo.defaultImport = node.importClause.name.text;
78
- }
79
- if (node.importClause.namedBindings &&
80
- ts.isNamedImports(node.importClause.namedBindings)) {
81
- importInfo.namedImports =
82
- node.importClause.namedBindings.elements.map((element) => element.name.text);
83
- }
84
- }
85
- imports.push(importInfo);
86
- }
87
- });
88
- return imports;
89
- }
90
- // ---- App variable resolution ----
91
- /** Resolve the app name from the first argument of App.getApp(). */
92
- function resolveAppName(sourceFile, arg) {
93
- if (ts.isStringLiteral(arg))
94
- return arg.text;
95
- if (ts.isIdentifier(arg))
96
- return findVariableValue(sourceFile, arg.text) ?? "";
97
- if (ts.isPropertyAccessExpression(arg))
98
- return evaluatePropertyAccess(sourceFile, arg) ?? "";
99
- return "";
100
- }
101
- function findAppVariable(sourceFile) {
102
- return (findFirstInAst(sourceFile, (node) => {
103
- if (!ts.isVariableStatement(node))
104
- return undefined;
105
- for (const declaration of node.declarationList.declarations) {
106
- if (!declaration.initializer ||
107
- !isAppGetAppCall(declaration.initializer))
108
- continue;
109
- const varName = ts.isIdentifier(declaration.name)
110
- ? declaration.name.text
111
- : "";
112
- const appName = declaration.initializer.arguments.length > 0
113
- ? resolveAppName(sourceFile, declaration.initializer.arguments[0])
114
- : "";
115
- return { appVariableName: varName, appName };
116
- }
117
- return undefined;
118
- }) ?? { appVariableName: "", appName: "" });
119
- }
120
- // ---- Tags extraction ----
121
- function extractTags(sourceFile) {
122
- const tags = {};
123
- function visit(node) {
124
- if (ts.isCallExpression(node) &&
125
- ts.isPropertyAccessExpression(node.expression) &&
126
- node.expression.name.text === "addTags" &&
127
- ts.isIdentifier(node.expression.expression) &&
128
- node.arguments.length > 0) {
129
- const tagsArg = node.arguments[0];
130
- if (ts.isObjectLiteralExpression(tagsArg)) {
131
- const parsedTags = parseObjectLiteral(tagsArg);
132
- for (const [key, value] of Object.entries(parsedTags)) {
133
- if (typeof value === "string") {
134
- tags[key] = value;
135
- }
136
- else if (isExpressionRef(value)) {
137
- // process.env tag expressions can't be statically resolved; use placeholder
138
- tags[key] = "default-value";
139
- }
140
- }
141
- }
142
- }
143
- ts.forEachChild(node, visit);
144
- }
145
- visit(sourceFile);
146
- return tags;
147
- }
148
- // ---- Plan conversion ----
149
- function applyTagsAndOwner(plan, tags) {
150
- if (Object.keys(tags).length > 0) {
151
- plan.tags = { ...tags };
152
- }
153
- if (tags[COST_ALLOCATION_TAG]) {
154
- plan.owner = tags[COST_ALLOCATION_TAG];
155
- }
156
- }
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;
182
- }
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
- }
201
- }
202
- else {
203
- // aws-cdk-lib/*, constructs -- preserve entire import
204
- if (imp.namedImports.length > 0 || imp.defaultImport) {
205
- additional.push(imp);
206
- }
207
- }
208
- }
209
- return additional.length > 0 ? additional : undefined;
210
- }
211
- export function convertToResourcePlan(parsed, appName, options) {
212
- const plan = {
213
- appName,
214
- type: "standard",
215
- database: [],
216
- s3: [],
217
- compute: [],
218
- };
219
- applyPatternConfig(plan, parsed.patternResources);
220
- if (parsed.vpcId)
221
- plan.vpcId = parsed.vpcId;
222
- plan.network = convertNetworkConfig(parsed.network);
223
- plan.backup = convertBackupConfig(parsed.backup);
224
- plan.tunnel = convertTunnelConfig(parsed.tunnel);
225
- plan.additionalNetworks = convertAdditionalNetworks(parsed.networkResources);
226
- applyTagsAndOwner(plan, parsed.tags);
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);
233
- plan.additionalManagedImports = computeAdditionalManagedImports(parsed.imports);
234
- // Round-trip operations (parse -> modify -> regenerate) skip validation
235
- // to tolerate existing apps with non-standard names (e.g. PascalCase).
236
- if (options?.skipValidation) {
237
- return plan;
238
- }
239
- try {
240
- return ApplicationResourcePlanSchema.parse(plan);
241
- }
242
- catch (error) {
243
- if (error instanceof z.ZodError) {
244
- const errorMessage = getZodErrorMessage(error);
245
- throw new Error(`Invalid infrastructure configuration:\n${errorMessage}`);
246
- }
247
- throw error;
248
- }
249
- }
250
- /**
251
- * Classify all top-level statements in an infrastructure file.
252
- */
253
- export function classifyStatements(sourceFile) {
254
- const classifications = [];
255
- for (const statement of sourceFile.statements) {
256
- const startPos = statement.getFullStart();
257
- const endPos = statement.getEnd();
258
- if (ts.isImportDeclaration(statement)) {
259
- classifications.push({
260
- type: "import",
261
- node: statement,
262
- startPos,
263
- endPos,
264
- isManaged: isManagedImport(statement),
265
- });
266
- continue;
267
- }
268
- if (ts.isVariableStatement(statement)) {
269
- const classification = classifyVariableStatement(statement, startPos, endPos);
270
- classifications.push(classification);
271
- continue;
272
- }
273
- if (ts.isExpressionStatement(statement)) {
274
- const classification = classifyExpressionStatement(statement, startPos, endPos);
275
- classifications.push(classification);
276
- continue;
277
- }
278
- classifications.push({
279
- type: "custom",
280
- node: statement,
281
- startPos,
282
- endPos,
283
- isManaged: false,
284
- });
285
- }
286
- return classifications;
287
- }
288
- function isManagedImport(node) {
289
- if (!ts.isStringLiteral(node.moduleSpecifier))
290
- return false;
291
- return isManagedModuleSpecifier(node.moduleSpecifier.text);
292
- }
293
- function classifyVariableStatement(statement, startPos, endPos) {
294
- for (const declaration of statement.declarationList.declarations) {
295
- if (!declaration.initializer)
296
- continue;
297
- if (isAppGetAppCall(declaration.initializer)) {
298
- return {
299
- type: "app-init",
300
- node: statement,
301
- resourceName: ts.isIdentifier(declaration.name)
302
- ? declaration.name.text
303
- : undefined,
304
- startPos,
305
- endPos,
306
- isManaged: true,
307
- };
308
- }
309
- if (ts.isIdentifier(declaration.name) &&
310
- declaration.name.text === "appName" &&
311
- ts.isStringLiteral(declaration.initializer)) {
312
- return {
313
- type: "app-init",
314
- node: statement,
315
- startPos,
316
- endPos,
317
- isManaged: true,
318
- };
319
- }
320
- if (ts.isNewExpression(declaration.initializer)) {
321
- const newExpr = declaration.initializer;
322
- if (ts.isIdentifier(newExpr.expression)) {
323
- const className = newExpr.expression.text;
324
- if (S3_BUCKET_CLASSES.has(className)) {
325
- const resourceName = newExpr.arguments &&
326
- newExpr.arguments.length >= 2 &&
327
- ts.isStringLiteral(newExpr.arguments[1])
328
- ? newExpr.arguments[1].text
329
- : undefined;
330
- return {
331
- type: "storage",
332
- node: statement,
333
- resourceName,
334
- startPos,
335
- endPos,
336
- isManaged: true,
337
- };
338
- }
339
- }
340
- }
341
- if (ts.isCallExpression(declaration.initializer)) {
342
- const classification = classifyCallExpression(declaration.initializer, statement, startPos, endPos);
343
- if (classification.type !== "custom") {
344
- return classification;
345
- }
346
- }
347
- }
348
- return {
349
- type: "custom",
350
- node: statement,
351
- startPos,
352
- endPos,
353
- isManaged: false,
354
- };
355
- }
356
- function classifyExpressionStatement(statement, startPos, endPos) {
357
- const expr = statement.expression;
358
- if (ts.isCallExpression(expr)) {
359
- return classifyCallExpression(expr, statement, startPos, endPos);
360
- }
361
- return {
362
- type: "custom",
363
- node: statement,
364
- startPos,
365
- endPos,
366
- isManaged: false,
367
- };
368
- }
369
- function classifyCallExpression(call, statement, startPos, endPos) {
370
- const expression = call.expression;
371
- if (ts.isPropertyAccessExpression(expression) &&
372
- ts.isIdentifier(expression.expression)) {
373
- const methodName = expression.name.text;
374
- if (methodName === "addTags") {
375
- return {
376
- type: "tags",
377
- node: statement,
378
- startPos,
379
- endPos,
380
- isManaged: true,
381
- };
382
- }
383
- const factoryMethods = {
384
- addDatabase: { type: "database", factory: "DatabaseFactory" },
385
- addCompute: { type: "compute", factory: "ComputeFactory" },
386
- addNetwork: { type: "network", factory: "NetworkFactory" },
387
- addMessaging: { type: "messaging", factory: "MessagingFactory" },
388
- addCdn: { type: "cdn", factory: "CdnFactory" },
389
- addPattern: { type: "pattern", factory: "PatternFactory" },
390
- addStorage: { type: "storage", factory: "StorageFactory" },
391
- };
392
- const factoryMethod = factoryMethods[methodName];
393
- if (factoryMethod) {
394
- const resourceName = extractResourceNameFromFactoryCall(call, factoryMethod.factory);
395
- return {
396
- type: factoryMethod.type,
397
- node: statement,
398
- resourceName,
399
- startPos,
400
- endPos,
401
- isManaged: true,
402
- };
403
- }
404
- }
405
- return {
406
- type: "custom",
407
- node: statement,
408
- startPos,
409
- endPos,
410
- isManaged: false,
411
- };
412
- }
413
- function extractResourceNameFromFactoryCall(addCall, factoryName) {
414
- if (addCall.arguments.length === 0)
415
- return undefined;
416
- const factoryArg = addCall.arguments[0];
417
- if (!ts.isCallExpression(factoryArg))
418
- return undefined;
419
- const factoryExpr = factoryArg.expression;
420
- if (!ts.isPropertyAccessExpression(factoryExpr) ||
421
- factoryExpr.name.text !== "build" ||
422
- !ts.isIdentifier(factoryExpr.expression) ||
423
- factoryExpr.expression.text !== factoryName) {
424
- return undefined;
425
- }
426
- if (factoryArg.arguments.length === 0)
427
- return undefined;
428
- const nameArg = factoryArg.arguments[0];
429
- if (ts.isStringLiteral(nameArg)) {
430
- return nameArg.text;
431
- }
432
- return undefined;
433
- }
434
- const POSITION_BY_MANAGED_TYPE = {
435
- import: "after-imports",
436
- "app-init": "after-app-init",
437
- tags: "after-tags",
438
- };
439
- function determineCustomBlockPosition(lastManaged) {
440
- if (!lastManaged)
441
- return { position: "before-imports" };
442
- const knownPosition = POSITION_BY_MANAGED_TYPE[lastManaged.type];
443
- if (knownPosition)
444
- return { position: knownPosition };
445
- return {
446
- position: "after-resource",
447
- afterManagedResource: {
448
- type: lastManaged.type,
449
- name: lastManaged.resourceName,
450
- },
451
- };
452
- }
453
- /**
454
- * Extract custom code blocks from an infrastructure file.
455
- */
456
- export function extractCustomCodeBlocks(sourceFile, precomputedClassifications) {
457
- const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
458
- const customBlocks = [];
459
- const sourceText = sourceFile.getFullText();
460
- let lastManagedStatement = null;
461
- for (const current of classifications) {
462
- if (current.type === "custom" && !current.isManaged) {
463
- const { position, afterManagedResource } = determineCustomBlockPosition(lastManagedStatement);
464
- const fullText = getStatementTextWithComments(sourceFile, sourceText, current.node);
465
- const leadingComments = extractLeadingComments(sourceFile, sourceText, current.node);
466
- const lineAndChar = sourceFile.getLineAndCharacterOfPosition(current.node.getStart());
467
- customBlocks.push({
468
- sourceText: fullText,
469
- position,
470
- afterManagedResource,
471
- originalLine: lineAndChar.line + 1,
472
- leadingComments: leadingComments.length > 0 ? leadingComments : undefined,
473
- });
474
- }
475
- else if (current.isManaged) {
476
- lastManagedStatement = current;
477
- }
478
- }
479
- return customBlocks;
480
- }
481
- function getStatementTextWithComments(sourceFile, sourceText, node) {
482
- const fullStart = node.getFullStart();
483
- const end = node.getEnd();
484
- const text = sourceText.slice(fullStart, end);
485
- const lines = text.split("\n");
486
- const startIndex = lines.findIndex((line) => line.trim() !== "");
487
- return lines.slice(startIndex === -1 ? 0 : startIndex).join("\n");
488
- }
489
- function extractLeadingComments(sourceFile, sourceText, node) {
490
- const comments = [];
491
- const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
492
- if (commentRanges) {
493
- for (const range of commentRanges) {
494
- const commentText = sourceText.slice(range.pos, range.end);
495
- comments.push(commentText);
496
- }
497
- }
498
- return comments;
499
- }
500
- /**
501
- * Find the position information for a specific managed resource.
502
- */
503
- export function findManagedResourcePosition(sourceFile, resourceType, resourceName, precomputedClassifications) {
504
- const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
505
- for (const classification of classifications) {
506
- if (classification.type === resourceType &&
507
- classification.resourceName === resourceName) {
508
- return {
509
- startPos: classification.startPos,
510
- endPos: classification.endPos,
511
- node: classification.node,
512
- };
513
- }
514
- }
515
- return null;
516
- }
517
- /**
518
- * Get the last managed statement of a specific type.
519
- */
520
- export function getLastManagedStatementOfType(sourceFile, type, precomputedClassifications) {
521
- const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
522
- let lastOfType = null;
523
- for (const classification of classifications) {
524
- if (classification.type === type && classification.isManaged) {
525
- lastOfType = classification;
526
- }
527
- }
528
- return lastOfType;
529
- }
530
- /**
531
- * Get all managed resources grouped by type.
532
- */
533
- export function getManagedResourcesByType(sourceFile, precomputedClassifications) {
534
- const classifications = precomputedClassifications ?? classifyStatements(sourceFile);
535
- const byType = {
536
- import: [],
537
- "app-init": [],
538
- tags: [],
539
- database: [],
540
- compute: [],
541
- storage: [],
542
- network: [],
543
- messaging: [],
544
- cdn: [],
545
- pattern: [],
546
- custom: [],
547
- };
548
- for (const classification of classifications) {
549
- byType[classification.type].push(classification);
550
- }
551
- return byType;
552
- }
1
+ import*as s from"typescript";import{isPlainObject as ne,isIdentifierRef as ae,isExpressionRef as ce,isCallRef as pe,isParsedObject as me,asString as fe,asNumber as ue,asBoolean as le,asStringArray as de,asStringUnion as xe,captureExtraProperties as ge}from"./astCommonParser.js";import{findComputeResources as Ce}from"./astComputeParser.js";import{classifyStatements as be}from"./astStatementClassifier.js";import{extractCustomCodeBlocks as Ie,findManagedResourcePosition as Ne,getLastManagedStatementOfType as ke,getManagedResourcesByType as Ae}from"./astStatementQueries.js";import{convertToResourcePlan as Ee}from"./astPlanConverter.js";import{isAppGetAppCall as I,isExpressionRef as N,findFirstInAst as k,parseObjectLiteral as A,findVariableValue as B,evaluatePropertyAccess as E}from"./astCommonParser.js";import{findDatabaseResources as P,splitDynamoDBFromDatabases as T}from"./astDatabaseParser.js";import{findS3Resources as v,findS3FactoryResources as V}from"./astStorageParser.js";import{findComputeResources as h}from"./astComputeParser.js";import{findSQSResources as D}from"./astMessagingParser.js";import{findCDNResource as L}from"./astCdnParser.js";import{findVpcId as O,findNetworkConfig as j,findBackupConfig as w,findTunnelConfig as z,findNetworkResources as F}from"./astNetworkParser.js";import{findPatternResources as M}from"./astPatternParser.js";import{classifyStatements as q}from"./astStatementClassifier.js";import{extractCustomCodeBlocks as G}from"./astStatementQueries.js";function re(i,t){const e=s.createSourceFile("infrastructure.ts",i,s.ScriptTarget.Latest,!0,s.ScriptKind.TS),r=K(e),{appVariableName:o,appName:n}=U(e),c=O(e),a=j(e),m=w(e),f=z(e),u=F(e),l=P(e),{databases:d,dynamodb:x}=T(l),g=D(e),R=L(e),C=[...v(e),...V(e)],S=h(e),b=M(e),y=H(e),p={sourceFile:e,imports:r,appVariableName:o,appName:n,vpcId:c,network:a,backup:m,tunnel:f,networkResources:u,databaseResources:d,dynamodbResources:x,sqsResources:g,cdnResource:R,s3Resources:C,computeResources:S,patternResources:b,tags:y};return t?.extractCustomCode&&(p.customCodeBlocks=G(e),p.classifiedStatements=q(e)),p}function K(i){const t=[];return s.forEachChild(i,e=>{if(s.isImportDeclaration(e)){if(!s.isStringLiteral(e.moduleSpecifier))return;const o={moduleSpecifier:e.moduleSpecifier.text,namedImports:[]};e.importClause&&(e.importClause.name&&(o.defaultImport=e.importClause.name.text),e.importClause.namedBindings&&s.isNamedImports(e.importClause.namedBindings)&&(o.namedImports=e.importClause.namedBindings.elements.map(n=>n.name.text))),t.push(o)}}),t}function Q(i,t){return s.isStringLiteral(t)?t.text:s.isIdentifier(t)?B(i,t.text)??"":s.isPropertyAccessExpression(t)?E(i,t)??"":""}function U(i){return k(i,t=>{if(s.isVariableStatement(t))for(const e of t.declarationList.declarations){if(!e.initializer||!I(e.initializer))continue;const r=s.isIdentifier(e.name)?e.name.text:"",o=e.initializer.arguments.length>0?Q(i,e.initializer.arguments[0]):"";return{appVariableName:r,appName:o}}})??{appVariableName:"",appName:""}}function H(i){const t={};function e(r){if(s.isCallExpression(r)&&s.isPropertyAccessExpression(r.expression)&&r.expression.name.text==="addTags"&&s.isIdentifier(r.expression.expression)&&r.arguments.length>0){const o=r.arguments[0];if(s.isObjectLiteralExpression(o)){const n=A(o);for(const[c,a]of Object.entries(n))typeof a=="string"?t[c]=a:N(a)&&(t[c]="default-value")}}s.forEachChild(r,e)}return e(i),t}export{le as asBoolean,ue as asNumber,fe as asString,de as asStringArray,xe as asStringUnion,ge as captureExtraProperties,be as classifyStatements,Ee as convertToResourcePlan,Ie as extractCustomCodeBlocks,Ce as findComputeResources,Ne as findManagedResourcePosition,ke as getLastManagedStatementOfType,Ae as getManagedResourcesByType,pe as isCallRef,ce as isExpressionRef,ae as isIdentifierRef,me as isParsedObject,ne as isPlainObject,re as parseInfrastructure};
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Read-only AST parser for the messaging (SQS) section of infrastructure.ts.
3
+ * Used by fjall list and by the deploy-worker's convertToResourcePlan. Write
4
+ * paths live in src/codemod/. Do not add mutation helpers here.
5
+ */
1
6
  import * as ts from "typescript";
2
7
  import type { ApplicationResourcePlan } from "../schemas/resourceSchemas.js";
3
8
  import { type ParsedValue } from "./astCommonParser.js";
@@ -1,78 +1 @@
1
- import * as ts from "typescript";
2
- import { asBoolean, asNumber, asString, captureExtraProperties, collectFromAst, extractVariableName, isFactoryBuildCall, isFactoryMethodCall, isParsedObject, omitUndefined, parseObjectLiteral, } from "./astCommonParser.js";
3
- // ---- Extraction helpers ----
4
- function extractSQSResource(addMessagingCall, buildCall) {
5
- if (buildCall.arguments.length < 2)
6
- return null;
7
- const nameArg = buildCall.arguments[0];
8
- const configArg = buildCall.arguments[1];
9
- if (!ts.isStringLiteral(nameArg) ||
10
- !ts.isObjectLiteralExpression(configArg)) {
11
- return null;
12
- }
13
- const config = parseObjectLiteral(configArg);
14
- const resource = {
15
- resourceName: nameArg.text,
16
- queueType: asString(config.queueType) ?? "standard",
17
- visibilityTimeout: asNumber(config.visibilityTimeout),
18
- retentionPeriod: asNumber(config.messageRetentionPeriod),
19
- contentBasedDeduplication: asBoolean(config.contentBasedDeduplication),
20
- node: addMessagingCall,
21
- };
22
- const varName = extractVariableName(addMessagingCall);
23
- if (varName)
24
- resource.variableName = varName;
25
- const SQS_KNOWN_KEYS = new Set([
26
- "type",
27
- "queueType",
28
- "visibilityTimeout",
29
- "messageRetentionPeriod",
30
- "contentBasedDeduplication",
31
- ]);
32
- const extras = captureExtraProperties(config, SQS_KNOWN_KEYS);
33
- if (extras.length > 0)
34
- resource.extraProperties = extras;
35
- return resource;
36
- }
37
- // ---- Parsing for dead letter queue config (used by pattern parser) ----
38
- export function parseDeadLetterQueueConfig(value) {
39
- if (value === undefined || value === null)
40
- return undefined;
41
- if (value === false)
42
- return false;
43
- if (!isParsedObject(value))
44
- return undefined;
45
- return omitUndefined({
46
- enabled: asBoolean(value.enabled),
47
- maxReceiveCount: asNumber(value.maxReceiveCount),
48
- });
49
- }
50
- // ---- Public API ----
51
- /** Find all SQS resources from addMessaging calls */
52
- export function findSQSResources(sourceFile) {
53
- return collectFromAst(sourceFile, (node) => {
54
- if (!ts.isCallExpression(node) ||
55
- !isFactoryMethodCall(node, "addMessaging"))
56
- return null;
57
- const messagingArg = node.arguments[0];
58
- if (!isFactoryBuildCall(messagingArg, "MessagingFactory"))
59
- return null;
60
- return extractSQSResource(node, messagingArg);
61
- });
62
- }
63
- /** Convert parsed SQS resources to plan format */
64
- export function convertSQSResources(sqsResources) {
65
- if (sqsResources.length === 0)
66
- return undefined;
67
- return sqsResources.map((queue) => ({
68
- name: queue.resourceName,
69
- queueType: queue.queueType === "fifo" ? "fifo" : "standard",
70
- ...omitUndefined({
71
- visibilityTimeout: queue.visibilityTimeout,
72
- retentionPeriod: queue.retentionPeriod,
73
- contentBasedDeduplication: queue.contentBasedDeduplication,
74
- variableName: queue.variableName,
75
- extraProperties: queue.extraProperties,
76
- }),
77
- }));
78
- }
1
+ import*as o from"typescript";import{asBoolean as d,asNumber as a,asString as l,captureExtraProperties as m,collectFromAst as p,extractVariableName as g,isFactoryBuildCall as x,isFactoryMethodCall as S,isParsedObject as y,omitUndefined as f,parseObjectLiteral as b}from"./astCommonParser.js";function N(t,e){if(e.arguments.length<2)return null;const i=e.arguments[0],s=e.arguments[1];if(!o.isStringLiteral(i)||!o.isObjectLiteralExpression(s))return null;const r=b(s),n={resourceName:i.text,queueType:l(r.queueType)??"standard",visibilityTimeout:a(r.visibilityTimeout),retentionPeriod:a(r.messageRetentionPeriod),contentBasedDeduplication:d(r.contentBasedDeduplication),node:t},c=g(t);c&&(n.variableName=c);const u=m(r,new Set(["type","queueType","visibilityTimeout","messageRetentionPeriod","contentBasedDeduplication"]));return u.length>0&&(n.extraProperties=u),n}function v(t){if(t!=null){if(t===!1)return!1;if(y(t))return f({enabled:d(t.enabled),maxReceiveCount:a(t.maxReceiveCount)})}}function B(t){return p(t,e=>{if(!o.isCallExpression(e)||!S(e,"addMessaging"))return null;const i=e.arguments[0];return x(i,"MessagingFactory")?N(e,i):null})}function D(t){if(t.length!==0)return t.map(e=>({name:e.resourceName,queueType:e.queueType==="fifo"?"fifo":"standard",...f({visibilityTimeout:e.visibilityTimeout,retentionPeriod:e.retentionPeriod,contentBasedDeduplication:e.contentBasedDeduplication,variableName:e.variableName,extraProperties:e.extraProperties})}))}export{D as convertSQSResources,B as findSQSResources,v as parseDeadLetterQueueConfig};
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Read-only AST parser for the network section of infrastructure.ts (primary
3
+ * network, backup, tunnel, additional networks). Used by fjall list and by the
4
+ * deploy-worker's convertToResourcePlan. Write paths live in src/codemod/. Do
5
+ * not add mutation helpers here.
6
+ */
1
7
  import * as ts from "typescript";
2
8
  import type { ApplicationResourcePlan, NetworkResourcePlan } from "../schemas/resourceSchemas.js";
3
9
  import { type ParsedObject } from "./astCommonParser.js";