@fjall/generator 0.88.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/ast/astComputeParser.d.ts +4 -0
- package/dist/src/ast/astComputeParser.js +427 -0
- package/dist/src/ast/astInfrastructureParser.d.ts +357 -0
- package/dist/src/ast/astInfrastructureParser.js +1925 -0
- package/dist/src/ast/astSurgicalModification.d.ts +47 -0
- package/dist/src/ast/astSurgicalModification.js +400 -0
- package/dist/src/ast/index.d.ts +2 -0
- package/dist/src/ast/index.js +2 -0
- package/dist/src/aws/regions.d.ts +30 -0
- package/dist/src/aws/regions.js +254 -0
- package/dist/src/generation/common.d.ts +86 -0
- package/dist/src/generation/common.js +187 -0
- package/dist/src/generation/compute.d.ts +6 -0
- package/dist/src/generation/compute.js +547 -0
- package/dist/src/generation/database.d.ts +54 -0
- package/dist/src/generation/database.js +201 -0
- package/dist/src/generation/index.d.ts +12 -0
- package/dist/src/generation/index.js +18 -0
- package/dist/src/generation/infrastructure.d.ts +44 -0
- package/dist/src/generation/infrastructure.js +389 -0
- package/dist/src/generation/storage.d.ts +23 -0
- package/dist/src/generation/storage.js +174 -0
- package/dist/src/generation/storageConnections.d.ts +37 -0
- package/dist/src/generation/storageConnections.js +71 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +19 -0
- package/dist/src/planning/index.d.ts +1 -0
- package/dist/src/planning/index.js +1 -0
- package/dist/src/planning/resourcePlanning.d.ts +58 -0
- package/dist/src/planning/resourcePlanning.js +216 -0
- package/dist/src/presets/index.d.ts +3 -0
- package/dist/src/presets/index.js +3 -0
- package/dist/src/presets/patternTierPresets.d.ts +93 -0
- package/dist/src/presets/patternTierPresets.js +131 -0
- package/dist/src/presets/storagePresets.d.ts +11 -0
- package/dist/src/presets/storagePresets.js +36 -0
- package/dist/src/presets/tierPresets.d.ts +59 -0
- package/dist/src/presets/tierPresets.js +384 -0
- package/dist/src/presets/tierTypes.d.ts +301 -0
- package/dist/src/presets/tierTypes.js +7 -0
- package/dist/src/schemas/constants.d.ts +74 -0
- package/dist/src/schemas/constants.js +208 -0
- package/dist/src/schemas/index.d.ts +3 -0
- package/dist/src/schemas/index.js +3 -0
- package/dist/src/schemas/instanceTypeArchitecture.d.ts +35 -0
- package/dist/src/schemas/instanceTypeArchitecture.js +75 -0
- package/dist/src/schemas/resourceSchemas.d.ts +3534 -0
- package/dist/src/schemas/resourceSchemas.js +2015 -0
- package/dist/src/types/Result.d.ts +19 -0
- package/dist/src/types/Result.js +31 -0
- package/dist/src/util/errorUtils.d.ts +2 -0
- package/dist/src/util/errorUtils.js +15 -0
- package/dist/src/validation/patterns.d.ts +300 -0
- package/dist/src/validation/patterns.js +360 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { formatValue, getVariableName, emitExtraProperties } from "./common.js";
|
|
2
|
+
import { DATABASE_ENV_VARS, getConnectedDatabases, generateDatabaseEnvVarEntries, buildDatabaseEnvVars, } from "./database.js";
|
|
3
|
+
import { STORAGE_ENV_VARS, getConnectedStorage, generateStorageEnvVarEntries, buildStorageEnvVars, formatAllConnectionsCode, } from "./storageConnections.js";
|
|
4
|
+
import { COMPUTE_TYPE, DEPLOYMENT_TYPE, DEFAULT_COMPUTE_ARCHITECTURE, } from "../schemas/constants.js";
|
|
5
|
+
/** Check if a compute resource has database connections */
|
|
6
|
+
function computeHasDatabaseConnections(compute) {
|
|
7
|
+
return Boolean(compute.needsConnection && compute.connectedDatabase?.length);
|
|
8
|
+
}
|
|
9
|
+
/** Check if a compute resource has storage connections */
|
|
10
|
+
function computeHasStorageConnections(compute) {
|
|
11
|
+
return Boolean(compute.connectedStorage?.length);
|
|
12
|
+
}
|
|
13
|
+
/** Check if a compute resource has any connections (database or storage) */
|
|
14
|
+
function computeHasConnections(compute) {
|
|
15
|
+
return (computeHasDatabaseConnections(compute) ||
|
|
16
|
+
computeHasStorageConnections(compute));
|
|
17
|
+
}
|
|
18
|
+
const BASE_ENVIRONMENT_VARS = {
|
|
19
|
+
ENVIRONMENT: { __expression: "getConfig().environment" },
|
|
20
|
+
};
|
|
21
|
+
function formatEnvBlock(vars, indent) {
|
|
22
|
+
return Object.entries(vars)
|
|
23
|
+
.map(([key, value]) => `${indent}${key}: ${formatValue(value)},`)
|
|
24
|
+
.join("\n");
|
|
25
|
+
}
|
|
26
|
+
export function generateLambdaCode(compute, _plan) {
|
|
27
|
+
let code = "";
|
|
28
|
+
if (compute.deployment === DEPLOYMENT_TYPE.CODE && compute.codePath) {
|
|
29
|
+
code += `
|
|
30
|
+
deployment: "${DEPLOYMENT_TYPE.CODE}",
|
|
31
|
+
code: Code.fromAsset("${compute.codePath}"),`;
|
|
32
|
+
if (compute.handler) {
|
|
33
|
+
code += `
|
|
34
|
+
handler: "${compute.handler}",`;
|
|
35
|
+
}
|
|
36
|
+
if (compute.runtime) {
|
|
37
|
+
code += `
|
|
38
|
+
runtime: Runtime.${compute.runtime},`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
code += `
|
|
43
|
+
deployment: "${DEPLOYMENT_TYPE.CONTAINER}",
|
|
44
|
+
ecrRepository: app.getDefaultContainerRegistry(),`;
|
|
45
|
+
}
|
|
46
|
+
if (compute.timeout !== undefined) {
|
|
47
|
+
code += `
|
|
48
|
+
timeout: ${compute.timeout},`;
|
|
49
|
+
}
|
|
50
|
+
if (compute.memory !== undefined) {
|
|
51
|
+
code += `
|
|
52
|
+
memorySize: ${compute.memory},`;
|
|
53
|
+
}
|
|
54
|
+
if (compute.functionUrl) {
|
|
55
|
+
code += `
|
|
56
|
+
functionUrl: { authType: FunctionUrlAuthType.${compute.functionUrl.authType} },`;
|
|
57
|
+
}
|
|
58
|
+
if (compute.description) {
|
|
59
|
+
code += `
|
|
60
|
+
lambdaDescription: "${compute.description}",`;
|
|
61
|
+
}
|
|
62
|
+
if (compute.scheduleExpression) {
|
|
63
|
+
code += `
|
|
64
|
+
scheduleExpression: "${compute.scheduleExpression}",`;
|
|
65
|
+
}
|
|
66
|
+
if (compute.architecture ||
|
|
67
|
+
compute.deployment === DEPLOYMENT_TYPE.CONTAINER) {
|
|
68
|
+
const arch = compute.architecture ?? DEFAULT_COMPUTE_ARCHITECTURE;
|
|
69
|
+
code += `
|
|
70
|
+
architecture: Architecture.${arch},`;
|
|
71
|
+
}
|
|
72
|
+
if (compute.ephemeralStorageSize !== undefined) {
|
|
73
|
+
code += `
|
|
74
|
+
ephemeralStorageSize: ${compute.ephemeralStorageSize},`;
|
|
75
|
+
}
|
|
76
|
+
if (compute.functionName) {
|
|
77
|
+
code += `
|
|
78
|
+
functionName: "${compute.functionName}",`;
|
|
79
|
+
}
|
|
80
|
+
if (compute.ssmSecretsPath) {
|
|
81
|
+
code += `
|
|
82
|
+
ssmSecretsPath: "${compute.ssmSecretsPath}",`;
|
|
83
|
+
}
|
|
84
|
+
if (compute.ssmSecrets && compute.ssmSecrets.length > 0) {
|
|
85
|
+
const secretsList = compute.ssmSecrets.map((s) => `"${s}"`).join(", ");
|
|
86
|
+
code += `
|
|
87
|
+
secrets: [${secretsList}],`;
|
|
88
|
+
}
|
|
89
|
+
// When there are connections, generateLambdaConnectionsCode handles env vars.
|
|
90
|
+
// Otherwise, emit environment block here with base + user env vars.
|
|
91
|
+
if (!computeHasConnections(compute)) {
|
|
92
|
+
const allEnv = {
|
|
93
|
+
...BASE_ENVIRONMENT_VARS,
|
|
94
|
+
...compute.environment,
|
|
95
|
+
};
|
|
96
|
+
code += `
|
|
97
|
+
environment: {
|
|
98
|
+
${formatEnvBlock(allEnv, " ")}
|
|
99
|
+
},`;
|
|
100
|
+
}
|
|
101
|
+
code += emitExtraProperties(compute.extraProperties);
|
|
102
|
+
return code;
|
|
103
|
+
}
|
|
104
|
+
export function generateLambdaConnectionsCode(compute, plan) {
|
|
105
|
+
if (compute.type !== COMPUTE_TYPE.LAMBDA)
|
|
106
|
+
return "";
|
|
107
|
+
const hasDatabaseConnections = computeHasDatabaseConnections(compute);
|
|
108
|
+
const hasStorageConns = computeHasStorageConnections(compute);
|
|
109
|
+
if (!hasDatabaseConnections && !hasStorageConns)
|
|
110
|
+
return "";
|
|
111
|
+
const connectedDatabases = hasDatabaseConnections
|
|
112
|
+
? getConnectedDatabases(plan, compute)
|
|
113
|
+
: [];
|
|
114
|
+
const connectedStorage = hasStorageConns
|
|
115
|
+
? getConnectedStorage(plan, compute)
|
|
116
|
+
: [];
|
|
117
|
+
if (connectedDatabases.length === 0 && connectedStorage.length === 0) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
// All env/secret values are stored as typed objects so formatValue handles
|
|
121
|
+
// them uniformly: __expression objects emit raw code, strings get JSON-quoted.
|
|
122
|
+
const envVars = {
|
|
123
|
+
...BASE_ENVIRONMENT_VARS,
|
|
124
|
+
};
|
|
125
|
+
const secretVars = {};
|
|
126
|
+
// Database env vars
|
|
127
|
+
if (connectedDatabases.length > 0) {
|
|
128
|
+
const entries = generateDatabaseEnvVarEntries(connectedDatabases);
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
// "true" is a literal string value (DATABASE_SSL); expressions are code refs
|
|
131
|
+
const value = entry.expression === `"true"`
|
|
132
|
+
? "true"
|
|
133
|
+
: { __expression: entry.expression };
|
|
134
|
+
if (entry.isSecret) {
|
|
135
|
+
secretVars[entry.key] = { __expression: entry.expression };
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
envVars[entry.key] = value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Storage env vars (no secrets — S3 uses IAM)
|
|
143
|
+
if (connectedStorage.length > 0) {
|
|
144
|
+
const entries = generateStorageEnvVarEntries(connectedStorage);
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
envVars[entry.key] = { __expression: entry.expression };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Merge compute.environment values directly — formatValue handles all types
|
|
150
|
+
if (compute.environment) {
|
|
151
|
+
for (const [key, value] of Object.entries(compute.environment)) {
|
|
152
|
+
envVars[key] = value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
let code = "";
|
|
156
|
+
// containerSecretsImport for database credentials (resolved at deploy time)
|
|
157
|
+
if (Object.keys(secretVars).length > 0) {
|
|
158
|
+
code += `
|
|
159
|
+
containerSecretsImport: {`;
|
|
160
|
+
for (const [key, value] of Object.entries(secretVars)) {
|
|
161
|
+
code += `
|
|
162
|
+
${key}: ${formatValue(value)},`;
|
|
163
|
+
}
|
|
164
|
+
code += `
|
|
165
|
+
},`;
|
|
166
|
+
}
|
|
167
|
+
if (Object.keys(envVars).length > 0) {
|
|
168
|
+
code += `
|
|
169
|
+
environment: {`;
|
|
170
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
171
|
+
code += `
|
|
172
|
+
${key}: ${formatValue(value)},`;
|
|
173
|
+
}
|
|
174
|
+
code += `
|
|
175
|
+
},`;
|
|
176
|
+
}
|
|
177
|
+
code += `
|
|
178
|
+
${formatAllConnectionsCode(connectedDatabases, connectedStorage)}`;
|
|
179
|
+
return code;
|
|
180
|
+
}
|
|
181
|
+
export function generateEc2Code(compute) {
|
|
182
|
+
let code = "";
|
|
183
|
+
if (compute.instanceType) {
|
|
184
|
+
code += `
|
|
185
|
+
instanceType: "${compute.instanceType}",`;
|
|
186
|
+
}
|
|
187
|
+
if (compute.enableSSH !== undefined) {
|
|
188
|
+
code += compute.enableSSH
|
|
189
|
+
? `
|
|
190
|
+
ssh: {},`
|
|
191
|
+
: `
|
|
192
|
+
ssh: false,`;
|
|
193
|
+
}
|
|
194
|
+
code += emitExtraProperties(compute.extraProperties);
|
|
195
|
+
return code;
|
|
196
|
+
}
|
|
197
|
+
function generateEcsClusterCode(compute) {
|
|
198
|
+
if (!compute.cluster)
|
|
199
|
+
return "";
|
|
200
|
+
const parts = [];
|
|
201
|
+
if (compute.cluster.directAccess) {
|
|
202
|
+
parts.push(`directAccess: true`);
|
|
203
|
+
}
|
|
204
|
+
if (compute.cluster.domain) {
|
|
205
|
+
parts.push(`domain: "${compute.cluster.domain}"`);
|
|
206
|
+
}
|
|
207
|
+
if (compute.cluster.loadBalancer !== undefined) {
|
|
208
|
+
parts.push(compute.cluster.loadBalancer === false
|
|
209
|
+
? `loadBalancer: false`
|
|
210
|
+
: `loadBalancer: "${compute.cluster.loadBalancer}"`);
|
|
211
|
+
}
|
|
212
|
+
if (parts.length <= 1) {
|
|
213
|
+
return `
|
|
214
|
+
cluster: { ${parts[0] || ""} },`;
|
|
215
|
+
}
|
|
216
|
+
const inner = parts.map((p) => `\n ${p},`).join("");
|
|
217
|
+
return `
|
|
218
|
+
cluster: {${inner}
|
|
219
|
+
},`;
|
|
220
|
+
}
|
|
221
|
+
function generateEcsContainerCode(containerConfig, shouldAddConnectionEnv, dbEnvVars, storageEnvVars, baseEnvVars) {
|
|
222
|
+
let code = "";
|
|
223
|
+
if (containerConfig.name) {
|
|
224
|
+
code += `
|
|
225
|
+
name: "${containerConfig.name}",`;
|
|
226
|
+
}
|
|
227
|
+
if (containerConfig.image) {
|
|
228
|
+
code += `
|
|
229
|
+
image: "${containerConfig.image}",`;
|
|
230
|
+
}
|
|
231
|
+
if (containerConfig.port !== undefined) {
|
|
232
|
+
code += `
|
|
233
|
+
port: ${containerConfig.port},`;
|
|
234
|
+
}
|
|
235
|
+
if (containerConfig.essential !== undefined) {
|
|
236
|
+
code += `
|
|
237
|
+
essential: ${containerConfig.essential},`;
|
|
238
|
+
}
|
|
239
|
+
if (containerConfig.command?.length) {
|
|
240
|
+
code += `
|
|
241
|
+
command: ${JSON.stringify(containerConfig.command)},`;
|
|
242
|
+
}
|
|
243
|
+
if (containerConfig.entryPoint?.length) {
|
|
244
|
+
code += `
|
|
245
|
+
entryPoint: ${JSON.stringify(containerConfig.entryPoint)},`;
|
|
246
|
+
}
|
|
247
|
+
const connectionEnvKeys = [
|
|
248
|
+
DATABASE_ENV_VARS.HOST,
|
|
249
|
+
DATABASE_ENV_VARS.PORT,
|
|
250
|
+
DATABASE_ENV_VARS.NAME,
|
|
251
|
+
DATABASE_ENV_VARS.SSL,
|
|
252
|
+
DATABASE_ENV_VARS.USERNAME,
|
|
253
|
+
DATABASE_ENV_VARS.PASSWORD,
|
|
254
|
+
STORAGE_ENV_VARS.NAME,
|
|
255
|
+
];
|
|
256
|
+
const stripConnectionEnv = (env) => {
|
|
257
|
+
if (!env)
|
|
258
|
+
return env;
|
|
259
|
+
const filtered = {};
|
|
260
|
+
for (const [key, value] of Object.entries(env)) {
|
|
261
|
+
const isConnectionKey = connectionEnvKeys.some((connKey) => key === connKey || key.startsWith(`${connKey}_`));
|
|
262
|
+
if (!isConnectionKey) {
|
|
263
|
+
filtered[key] = value;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Object.keys(filtered).length > 0 ? filtered : undefined;
|
|
267
|
+
};
|
|
268
|
+
// Base env vars (e.g. ENVIRONMENT) are always injected into every container.
|
|
269
|
+
// Connection env vars are only added to the first container of the service that needs them.
|
|
270
|
+
const containerEnv = shouldAddConnectionEnv
|
|
271
|
+
? {
|
|
272
|
+
...baseEnvVars,
|
|
273
|
+
...dbEnvVars.env,
|
|
274
|
+
...storageEnvVars.env,
|
|
275
|
+
...containerConfig.environment,
|
|
276
|
+
}
|
|
277
|
+
: { ...baseEnvVars, ...stripConnectionEnv(containerConfig.environment) };
|
|
278
|
+
const containerSecrets = shouldAddConnectionEnv
|
|
279
|
+
? { ...dbEnvVars.secrets, ...containerConfig.secretsImport }
|
|
280
|
+
: stripConnectionEnv(containerConfig.secretsImport);
|
|
281
|
+
if (containerEnv && Object.keys(containerEnv).length > 0) {
|
|
282
|
+
code += `
|
|
283
|
+
environment: {
|
|
284
|
+
${formatEnvBlock(containerEnv, " ")}
|
|
285
|
+
},`;
|
|
286
|
+
}
|
|
287
|
+
if (containerSecrets && Object.keys(containerSecrets).length > 0) {
|
|
288
|
+
code += `
|
|
289
|
+
secretsImport: {
|
|
290
|
+
${formatEnvBlock(containerSecrets, " ")}
|
|
291
|
+
},`;
|
|
292
|
+
}
|
|
293
|
+
if (containerConfig.ssmSecrets && containerConfig.ssmSecrets.length > 0) {
|
|
294
|
+
const secretsList = containerConfig.ssmSecrets
|
|
295
|
+
.map((s) => `"${s}"`)
|
|
296
|
+
.join(", ");
|
|
297
|
+
code += `
|
|
298
|
+
secrets: [${secretsList}],`;
|
|
299
|
+
}
|
|
300
|
+
if (containerConfig.healthCheck) {
|
|
301
|
+
code += `
|
|
302
|
+
healthCheck: ${formatValue(containerConfig.healthCheck, " ")},`;
|
|
303
|
+
}
|
|
304
|
+
code += emitExtraProperties(containerConfig.extraProperties, " ");
|
|
305
|
+
return code;
|
|
306
|
+
}
|
|
307
|
+
function formatRoutingRule(rule, indent) {
|
|
308
|
+
const parts = [];
|
|
309
|
+
if (rule.path)
|
|
310
|
+
parts.push(`path: "${rule.path}"`);
|
|
311
|
+
if (rule.host)
|
|
312
|
+
parts.push(`host: "${rule.host}"`);
|
|
313
|
+
if (rule.priority !== undefined)
|
|
314
|
+
parts.push(`priority: ${rule.priority}`);
|
|
315
|
+
if (rule.healthCheckPath)
|
|
316
|
+
parts.push(`healthCheckPath: "${rule.healthCheckPath}"`);
|
|
317
|
+
if (parts.length <= 1) {
|
|
318
|
+
return `{ ${parts[0] || ""} }`;
|
|
319
|
+
}
|
|
320
|
+
const inner = parts.map((p) => `\n${indent} ${p},`).join("");
|
|
321
|
+
return `{${inner}\n${indent}}`;
|
|
322
|
+
}
|
|
323
|
+
function generateEcsServiceRoutingCode(routing) {
|
|
324
|
+
const rules = Array.isArray(routing) ? routing : [routing];
|
|
325
|
+
if (rules.length === 1) {
|
|
326
|
+
const formatted = formatRoutingRule(rules[0], " ");
|
|
327
|
+
return `
|
|
328
|
+
routing: ${formatted},`;
|
|
329
|
+
}
|
|
330
|
+
const entries = rules.map((r) => formatRoutingRule(r, " "));
|
|
331
|
+
return `
|
|
332
|
+
routing: [
|
|
333
|
+
${entries.join(",\n ")},
|
|
334
|
+
],`;
|
|
335
|
+
}
|
|
336
|
+
function generateEcsServiceScalingCode(scaling) {
|
|
337
|
+
if (scaling === false) {
|
|
338
|
+
return `
|
|
339
|
+
scaling: false,`;
|
|
340
|
+
}
|
|
341
|
+
if (typeof scaling !== "object")
|
|
342
|
+
return "";
|
|
343
|
+
const hasScalingConfig = scaling.minCapacity !== undefined ||
|
|
344
|
+
scaling.maxCapacity !== undefined ||
|
|
345
|
+
scaling.desiredCount !== undefined ||
|
|
346
|
+
scaling.scalingType !== undefined;
|
|
347
|
+
if (!hasScalingConfig)
|
|
348
|
+
return "";
|
|
349
|
+
const scalingParts = [];
|
|
350
|
+
if (scaling.minCapacity !== undefined)
|
|
351
|
+
scalingParts.push(`minCapacity: ${scaling.minCapacity}`);
|
|
352
|
+
if (scaling.maxCapacity !== undefined)
|
|
353
|
+
scalingParts.push(`maxCapacity: ${scaling.maxCapacity}`);
|
|
354
|
+
if (scaling.desiredCount !== undefined)
|
|
355
|
+
scalingParts.push(`desiredCount: ${scaling.desiredCount}`);
|
|
356
|
+
if (scaling.scalingType)
|
|
357
|
+
scalingParts.push(`scalingType: "${scaling.scalingType}"`);
|
|
358
|
+
if (scalingParts.length <= 1) {
|
|
359
|
+
return `
|
|
360
|
+
scaling: { ${scalingParts[0] || ""} },`;
|
|
361
|
+
}
|
|
362
|
+
const inner = scalingParts.map((p) => `\n ${p},`).join("");
|
|
363
|
+
return `
|
|
364
|
+
scaling: {${inner}
|
|
365
|
+
},`;
|
|
366
|
+
}
|
|
367
|
+
function generateEcsServiceCode(options) {
|
|
368
|
+
const { service, isFirstService, hasConnections, dbEnvVars, storageEnvVars, baseEnvVars, plan, compute, } = options;
|
|
369
|
+
let code = `
|
|
370
|
+
name: "${service.name}",`;
|
|
371
|
+
code += `
|
|
372
|
+
capacityProvider: "${service.capacityProvider}",`;
|
|
373
|
+
if (service.ec2Config) {
|
|
374
|
+
code += `
|
|
375
|
+
ec2Config: ${formatValue(service.ec2Config, " ")},`;
|
|
376
|
+
}
|
|
377
|
+
if (service.dockerfilePath) {
|
|
378
|
+
code += `
|
|
379
|
+
dockerfilePath: "${service.dockerfilePath}",`;
|
|
380
|
+
}
|
|
381
|
+
if (service.dockerTarget) {
|
|
382
|
+
code += `
|
|
383
|
+
dockerTarget: "${service.dockerTarget}",`;
|
|
384
|
+
}
|
|
385
|
+
if (service.image) {
|
|
386
|
+
code += `
|
|
387
|
+
image: "${service.image}",`;
|
|
388
|
+
}
|
|
389
|
+
if (service.ssmSecretsPath) {
|
|
390
|
+
code += `
|
|
391
|
+
ssmSecretsPath: "${service.ssmSecretsPath}",`;
|
|
392
|
+
}
|
|
393
|
+
if (service.containers?.length) {
|
|
394
|
+
code += `
|
|
395
|
+
containers: [{`;
|
|
396
|
+
for (let i = 0; i < service.containers.length; i++) {
|
|
397
|
+
const container = service.containers[i];
|
|
398
|
+
if (!container)
|
|
399
|
+
continue;
|
|
400
|
+
const serviceNeedsDb = service.needsDatabaseConnection !== undefined
|
|
401
|
+
? service.needsDatabaseConnection
|
|
402
|
+
: isFirstService && computeHasDatabaseConnections(compute);
|
|
403
|
+
const serviceNeedsStorage = service.needsStorageConnection !== undefined
|
|
404
|
+
? service.needsStorageConnection
|
|
405
|
+
: isFirstService && computeHasStorageConnections(compute);
|
|
406
|
+
const shouldAddConnectionEnv = i === 0 && (serviceNeedsDb || serviceNeedsStorage) && hasConnections;
|
|
407
|
+
// Build per-container env vars: only include the connection types this service needs
|
|
408
|
+
const containerDbEnvVars = serviceNeedsDb
|
|
409
|
+
? dbEnvVars
|
|
410
|
+
: { env: {}, secrets: {} };
|
|
411
|
+
const containerStorageEnvVars = serviceNeedsStorage
|
|
412
|
+
? storageEnvVars
|
|
413
|
+
: { env: {}, secrets: {} };
|
|
414
|
+
if (i > 0) {
|
|
415
|
+
code += `
|
|
416
|
+
}, {`;
|
|
417
|
+
}
|
|
418
|
+
code += generateEcsContainerCode(container, shouldAddConnectionEnv, containerDbEnvVars, containerStorageEnvVars, baseEnvVars);
|
|
419
|
+
}
|
|
420
|
+
code += `
|
|
421
|
+
}],`;
|
|
422
|
+
}
|
|
423
|
+
if (service.routing) {
|
|
424
|
+
code += generateEcsServiceRoutingCode(service.routing);
|
|
425
|
+
}
|
|
426
|
+
if (service.cpu !== undefined) {
|
|
427
|
+
code += `
|
|
428
|
+
cpu: ${service.cpu},`;
|
|
429
|
+
}
|
|
430
|
+
if (service.memoryLimitMiB !== undefined) {
|
|
431
|
+
code += `
|
|
432
|
+
memoryLimitMiB: ${service.memoryLimitMiB},`;
|
|
433
|
+
}
|
|
434
|
+
if (service.desiredCount !== undefined) {
|
|
435
|
+
code += `
|
|
436
|
+
desiredCount: ${service.desiredCount},`;
|
|
437
|
+
}
|
|
438
|
+
if (service.scaling !== undefined) {
|
|
439
|
+
code += generateEcsServiceScalingCode(service.scaling);
|
|
440
|
+
}
|
|
441
|
+
code += emitExtraProperties(service.extraProperties, " ");
|
|
442
|
+
const serviceNeedsDbConnection = service.needsDatabaseConnection !== undefined
|
|
443
|
+
? service.needsDatabaseConnection
|
|
444
|
+
: isFirstService && computeHasDatabaseConnections(compute);
|
|
445
|
+
const serviceNeedsStorageConnection = service.needsStorageConnection !== undefined
|
|
446
|
+
? service.needsStorageConnection
|
|
447
|
+
: isFirstService && computeHasStorageConnections(compute);
|
|
448
|
+
if (serviceNeedsDbConnection || serviceNeedsStorageConnection) {
|
|
449
|
+
const connectedDatabases = serviceNeedsDbConnection
|
|
450
|
+
? getConnectedDatabases(plan, compute)
|
|
451
|
+
: [];
|
|
452
|
+
const connectedStorage = serviceNeedsStorageConnection
|
|
453
|
+
? getConnectedStorage(plan, compute)
|
|
454
|
+
: [];
|
|
455
|
+
if (connectedDatabases.length > 0 || connectedStorage.length > 0) {
|
|
456
|
+
code += `
|
|
457
|
+
${formatAllConnectionsCode(connectedDatabases, connectedStorage)}`;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return code;
|
|
461
|
+
}
|
|
462
|
+
export function generateEcsCode(compute, plan) {
|
|
463
|
+
const hasConnections = computeHasConnections(compute);
|
|
464
|
+
const baseEnvVars = {
|
|
465
|
+
...BASE_ENVIRONMENT_VARS,
|
|
466
|
+
};
|
|
467
|
+
// Database env vars only injected into containers with database connections
|
|
468
|
+
const dbEnvVars = {
|
|
469
|
+
env: {},
|
|
470
|
+
secrets: {},
|
|
471
|
+
};
|
|
472
|
+
if (computeHasDatabaseConnections(compute)) {
|
|
473
|
+
const builtEnvVars = buildDatabaseEnvVars(plan, compute);
|
|
474
|
+
Object.assign(dbEnvVars.env, builtEnvVars.env);
|
|
475
|
+
Object.assign(dbEnvVars.secrets, builtEnvVars.secrets);
|
|
476
|
+
}
|
|
477
|
+
// Storage env vars (no secrets — S3 uses IAM)
|
|
478
|
+
const storageEnvVars = {
|
|
479
|
+
env: {},
|
|
480
|
+
secrets: {},
|
|
481
|
+
};
|
|
482
|
+
if (computeHasStorageConnections(compute)) {
|
|
483
|
+
const builtStorageEnvVars = buildStorageEnvVars(plan, compute);
|
|
484
|
+
Object.assign(storageEnvVars.env, builtStorageEnvVars.env);
|
|
485
|
+
}
|
|
486
|
+
let code = `
|
|
487
|
+
ecrRepository: app.getDefaultContainerRegistry(),`;
|
|
488
|
+
code += generateEcsClusterCode(compute);
|
|
489
|
+
if (compute.services?.length) {
|
|
490
|
+
code += `
|
|
491
|
+
services: [{`;
|
|
492
|
+
for (let serviceIndex = 0; serviceIndex < compute.services.length; serviceIndex++) {
|
|
493
|
+
const service = compute.services[serviceIndex];
|
|
494
|
+
if (!service)
|
|
495
|
+
continue;
|
|
496
|
+
if (serviceIndex > 0) {
|
|
497
|
+
code += `
|
|
498
|
+
}, {`;
|
|
499
|
+
}
|
|
500
|
+
code += generateEcsServiceCode({
|
|
501
|
+
service,
|
|
502
|
+
isFirstService: serviceIndex === 0,
|
|
503
|
+
hasConnections: !!hasConnections,
|
|
504
|
+
dbEnvVars,
|
|
505
|
+
storageEnvVars,
|
|
506
|
+
baseEnvVars,
|
|
507
|
+
plan,
|
|
508
|
+
compute,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
code += `
|
|
512
|
+
}],`;
|
|
513
|
+
}
|
|
514
|
+
code += emitExtraProperties(compute.extraProperties);
|
|
515
|
+
return code;
|
|
516
|
+
}
|
|
517
|
+
export function generateComputeCode(plan, cdnReferencedResources) {
|
|
518
|
+
if (plan.compute.length === 0)
|
|
519
|
+
return "";
|
|
520
|
+
let code = "";
|
|
521
|
+
for (let computeIndex = 0; computeIndex < plan.compute.length; computeIndex++) {
|
|
522
|
+
const compute = plan.compute[computeIndex];
|
|
523
|
+
const needsVariable = cdnReferencedResources.has(compute.name);
|
|
524
|
+
const computeVariable = getVariableName(compute);
|
|
525
|
+
const needsLeadingNewline = plan.database.length > 0 || plan.s3.length > 0 || computeIndex > 0;
|
|
526
|
+
const prefix = needsVariable ? `const ${computeVariable} = ` : "";
|
|
527
|
+
const leadingNewline = needsLeadingNewline ? "\n" : "";
|
|
528
|
+
code += `${leadingNewline}${prefix}app.addCompute(
|
|
529
|
+
ComputeFactory.build("${compute.name}", {
|
|
530
|
+
type: "${compute.type}",`;
|
|
531
|
+
if (compute.type === COMPUTE_TYPE.LAMBDA) {
|
|
532
|
+
code += generateLambdaCode(compute, plan);
|
|
533
|
+
}
|
|
534
|
+
else if (compute.type === COMPUTE_TYPE.EC2) {
|
|
535
|
+
code += generateEc2Code(compute);
|
|
536
|
+
}
|
|
537
|
+
else if (compute.type === COMPUTE_TYPE.ECS) {
|
|
538
|
+
code += generateEcsCode(compute, plan);
|
|
539
|
+
}
|
|
540
|
+
code += generateLambdaConnectionsCode(compute, plan);
|
|
541
|
+
code += `
|
|
542
|
+
})
|
|
543
|
+
);
|
|
544
|
+
`;
|
|
545
|
+
}
|
|
546
|
+
return code;
|
|
547
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Functions for generating database infrastructure code including
|
|
5
|
+
* RDS Instance, Aurora, and GlobalAurora configurations.
|
|
6
|
+
*/
|
|
7
|
+
import type { DatabaseResourcePlan, ApplicationResourcePlan, ComputeResourcePlan } from "../schemas/resourceSchemas.js";
|
|
8
|
+
export declare const DATABASE_ENV_VARS: Readonly<{
|
|
9
|
+
readonly HOST: "DATABASE_HOST";
|
|
10
|
+
readonly PORT: "DATABASE_PORT";
|
|
11
|
+
readonly NAME: "DATABASE_NAME";
|
|
12
|
+
readonly SSL: "DATABASE_SSL";
|
|
13
|
+
readonly USERNAME: "DATABASE_USERNAME";
|
|
14
|
+
readonly PASSWORD: "DATABASE_PASSWORD";
|
|
15
|
+
}>;
|
|
16
|
+
export declare const CREDENTIAL_KEYS: Readonly<{
|
|
17
|
+
readonly USERNAME: "username";
|
|
18
|
+
readonly PASSWORD: "password";
|
|
19
|
+
}>;
|
|
20
|
+
export type DatabaseEnvVarEntry = {
|
|
21
|
+
key: string;
|
|
22
|
+
expression: string;
|
|
23
|
+
isSecret: boolean;
|
|
24
|
+
};
|
|
25
|
+
export interface DatabaseEnvVars {
|
|
26
|
+
env: Record<string, unknown>;
|
|
27
|
+
secrets: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get databases connected to a compute resource
|
|
31
|
+
*/
|
|
32
|
+
export declare function getConnectedDatabases(plan: ApplicationResourcePlan, compute: ComputeResourcePlan): DatabaseResourcePlan[];
|
|
33
|
+
/**
|
|
34
|
+
* Generate database environment variable entries for a list of connected databases.
|
|
35
|
+
* Both ECS and Lambda use containerSecretsImport with getImport() for credentials.
|
|
36
|
+
* Credential entries are always marked as secrets (isSecret: true).
|
|
37
|
+
*/
|
|
38
|
+
export declare function generateDatabaseEnvVarEntries(connectedDatabases: DatabaseResourcePlan[]): DatabaseEnvVarEntry[];
|
|
39
|
+
/**
|
|
40
|
+
* Format database connections array for generated infrastructure code
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatConnectionsCode(connectedDatabases: DatabaseResourcePlan[]): string;
|
|
43
|
+
/**
|
|
44
|
+
* Build database environment variables for a compute resource
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildDatabaseEnvVars(plan: ApplicationResourcePlan, compute: ComputeResourcePlan): DatabaseEnvVars;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a database needs a variable assignment (referenced by compute)
|
|
49
|
+
*/
|
|
50
|
+
export declare function databaseNeedsVariable(database: DatabaseResourcePlan, plan: ApplicationResourcePlan): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Generate database infrastructure code
|
|
53
|
+
*/
|
|
54
|
+
export declare function generateDatabaseCode(plan: ApplicationResourcePlan): string;
|