@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,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Functions for generating database infrastructure code including
|
|
5
|
+
* RDS Instance, Aurora, and GlobalAurora configurations.
|
|
6
|
+
*/
|
|
7
|
+
import { buildProperty, getVariableName, emitExtraProperties, } from "./common.js";
|
|
8
|
+
export const DATABASE_ENV_VARS = Object.freeze({
|
|
9
|
+
HOST: "DATABASE_HOST",
|
|
10
|
+
PORT: "DATABASE_PORT",
|
|
11
|
+
NAME: "DATABASE_NAME",
|
|
12
|
+
SSL: "DATABASE_SSL",
|
|
13
|
+
USERNAME: "DATABASE_USERNAME",
|
|
14
|
+
PASSWORD: "DATABASE_PASSWORD",
|
|
15
|
+
});
|
|
16
|
+
export const CREDENTIAL_KEYS = Object.freeze({
|
|
17
|
+
USERNAME: "username",
|
|
18
|
+
PASSWORD: "password",
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Get databases connected to a compute resource
|
|
22
|
+
*/
|
|
23
|
+
export function getConnectedDatabases(plan, compute) {
|
|
24
|
+
const connected = compute.connectedDatabase;
|
|
25
|
+
if (!connected?.length)
|
|
26
|
+
return [];
|
|
27
|
+
return plan.database.filter((db) => connected.includes(db.name));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate credential expression for database secrets.
|
|
31
|
+
* Both ECS and Lambda now use the unified getImport() API.
|
|
32
|
+
* The infrastructure layer handles the difference:
|
|
33
|
+
* - ECS: native Secrets Manager injection via EcsSecret.fromSecretsManager()
|
|
34
|
+
* - Lambda: CloudFormation dynamic reference resolved at deploy time
|
|
35
|
+
*/
|
|
36
|
+
function getCredentialExpression(dbVar, credentialKey) {
|
|
37
|
+
return `${dbVar}.getCredentials().getImport("${credentialKey}")`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Generate database environment variable entries for a list of connected databases.
|
|
41
|
+
* Both ECS and Lambda use containerSecretsImport with getImport() for credentials.
|
|
42
|
+
* Credential entries are always marked as secrets (isSecret: true).
|
|
43
|
+
*/
|
|
44
|
+
export function generateDatabaseEnvVarEntries(connectedDatabases) {
|
|
45
|
+
const entries = [];
|
|
46
|
+
const addEntry = (key, expression, isSecret) => {
|
|
47
|
+
entries.push({ key, expression, isSecret });
|
|
48
|
+
};
|
|
49
|
+
if (connectedDatabases.length === 1) {
|
|
50
|
+
const dbVar = getVariableName(connectedDatabases[0]);
|
|
51
|
+
addEntry(DATABASE_ENV_VARS.HOST, `${dbVar}.getHostEndpoint()`, false);
|
|
52
|
+
addEntry(DATABASE_ENV_VARS.PORT, `${dbVar}.getHostPort()`, false);
|
|
53
|
+
addEntry(DATABASE_ENV_VARS.NAME, `${dbVar}.getDatabaseName()`, false);
|
|
54
|
+
addEntry(DATABASE_ENV_VARS.SSL, `"true"`, false);
|
|
55
|
+
addEntry(DATABASE_ENV_VARS.USERNAME, getCredentialExpression(dbVar, CREDENTIAL_KEYS.USERNAME), true);
|
|
56
|
+
addEntry(DATABASE_ENV_VARS.PASSWORD, getCredentialExpression(dbVar, CREDENTIAL_KEYS.PASSWORD), true);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
for (const [index, database] of connectedDatabases.entries()) {
|
|
60
|
+
const dbVar = getVariableName(database);
|
|
61
|
+
const suffix = index === 0 ? "" : `_${index + 1}`;
|
|
62
|
+
addEntry(`${DATABASE_ENV_VARS.HOST}${suffix}`, `${dbVar}.getHostEndpoint()`, false);
|
|
63
|
+
addEntry(`${DATABASE_ENV_VARS.PORT}${suffix}`, `${dbVar}.getHostPort()`, false);
|
|
64
|
+
addEntry(`${DATABASE_ENV_VARS.NAME}${suffix}`, `${dbVar}.getDatabaseName()`, false);
|
|
65
|
+
addEntry(`${DATABASE_ENV_VARS.SSL}${suffix}`, `"true"`, false);
|
|
66
|
+
addEntry(`${DATABASE_ENV_VARS.USERNAME}${suffix}`, getCredentialExpression(dbVar, CREDENTIAL_KEYS.USERNAME), true);
|
|
67
|
+
addEntry(`${DATABASE_ENV_VARS.PASSWORD}${suffix}`, getCredentialExpression(dbVar, CREDENTIAL_KEYS.PASSWORD), true);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return entries;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Format database connections array for generated infrastructure code
|
|
74
|
+
*/
|
|
75
|
+
export function formatConnectionsCode(connectedDatabases) {
|
|
76
|
+
const dbVars = connectedDatabases.map((db) => getVariableName(db)).join(", ");
|
|
77
|
+
return `connections: [${dbVars}],`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build database environment variables for a compute resource
|
|
81
|
+
*/
|
|
82
|
+
export function buildDatabaseEnvVars(plan, compute) {
|
|
83
|
+
const env = {};
|
|
84
|
+
const secrets = {};
|
|
85
|
+
if (!compute.needsConnection || !compute.connectedDatabase?.length) {
|
|
86
|
+
return { env, secrets };
|
|
87
|
+
}
|
|
88
|
+
const connectedDatabases = getConnectedDatabases(plan, compute);
|
|
89
|
+
const entries = generateDatabaseEnvVarEntries(connectedDatabases);
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const value = entry.expression === `"true"`
|
|
92
|
+
? "true"
|
|
93
|
+
: { __expression: entry.expression };
|
|
94
|
+
if (entry.isSecret) {
|
|
95
|
+
secrets[entry.key] = { __expression: entry.expression };
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
env[entry.key] = value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { env, secrets };
|
|
102
|
+
}
|
|
103
|
+
function generateDatabaseSharedProps(database) {
|
|
104
|
+
let code = "";
|
|
105
|
+
code += buildProperty(database.port !== undefined, "port", database.port);
|
|
106
|
+
code += buildProperty(database.deletionProtection !== undefined, "deletionProtection", database.deletionProtection);
|
|
107
|
+
code += buildProperty(database.snapshotIdentifier !== undefined, "snapshotIdentifier", database.snapshotIdentifier, "string");
|
|
108
|
+
code += buildProperty(database.snapshotUsername !== undefined, "snapshotUsername", database.snapshotUsername, "string");
|
|
109
|
+
return code;
|
|
110
|
+
}
|
|
111
|
+
function generateDatabaseInstanceProps(database) {
|
|
112
|
+
if (database.type !== "Instance")
|
|
113
|
+
return "";
|
|
114
|
+
let code = "";
|
|
115
|
+
code += buildProperty(database.instanceType !== undefined, "instanceType", database.instanceType, "string");
|
|
116
|
+
code += buildProperty(database.multiAz !== undefined, "multiAz", database.multiAz);
|
|
117
|
+
code += buildProperty(database.publiclyAccessible !== undefined, "publiclyAccessible", database.publiclyAccessible);
|
|
118
|
+
code += buildProperty(database.enableSecretRotation !== undefined, "enableSecretRotation", database.enableSecretRotation);
|
|
119
|
+
code += buildProperty(database.encryption !== undefined, "encryption", database.encryption, "object");
|
|
120
|
+
code += buildProperty(database.databaseInsights !== undefined, "databaseInsights", database.databaseInsights, "boolean-or-object");
|
|
121
|
+
code += buildProperty(database.proxy !== undefined, "proxy", database.proxy, "boolean-or-object");
|
|
122
|
+
code += buildProperty(database.readReplica !== undefined, "readReplica", database.readReplica, "boolean-or-object");
|
|
123
|
+
code += buildProperty(database.credentials !== undefined, "credentials", database.credentials, "object");
|
|
124
|
+
code += buildProperty(database.backupRetention !== undefined, "backupRetention", database.backupRetention);
|
|
125
|
+
return code;
|
|
126
|
+
}
|
|
127
|
+
function generateDatabaseAuroraProps(database) {
|
|
128
|
+
if (database.type !== "Aurora" && database.type !== "GlobalAurora")
|
|
129
|
+
return "";
|
|
130
|
+
let code = "";
|
|
131
|
+
code += buildProperty(database.encryption !== undefined, "encryption", database.encryption, "object");
|
|
132
|
+
code += buildProperty(database.databaseInsights !== undefined, "databaseInsights", database.databaseInsights, "boolean-or-object");
|
|
133
|
+
code += buildProperty(database.proxy !== undefined, "proxy", database.proxy, "boolean-or-object");
|
|
134
|
+
code += buildProperty(database.credentials !== undefined, "credentials", database.credentials, "object");
|
|
135
|
+
code += buildProperty(database.writer !== undefined, "writer", database.writer, "object");
|
|
136
|
+
code += buildProperty(database.readers !== undefined, "readers", database.readers, "boolean-or-object");
|
|
137
|
+
code += buildProperty(database.backupRetention !== undefined, "backupRetention", database.backupRetention);
|
|
138
|
+
code += buildProperty(database.preferredMaintenanceWindow !== undefined, "preferredMaintenanceWindow", database.preferredMaintenanceWindow, "string");
|
|
139
|
+
return code;
|
|
140
|
+
}
|
|
141
|
+
function generateDatabaseGlobalAuroraProps(database) {
|
|
142
|
+
if (database.type !== "GlobalAurora")
|
|
143
|
+
return "";
|
|
144
|
+
let code = "";
|
|
145
|
+
code += buildProperty(database.primaryRegion !== undefined, "primaryRegion", database.primaryRegion, "string");
|
|
146
|
+
if (database.secondaryRegions !== undefined &&
|
|
147
|
+
database.secondaryRegions.length > 0) {
|
|
148
|
+
code += `
|
|
149
|
+
secondaryRegions: [${database.secondaryRegions.map((r) => `"${r}"`).join(", ")}],`;
|
|
150
|
+
}
|
|
151
|
+
code += buildProperty(database.globalClusterIdentifier !== undefined, "globalClusterIdentifier", database.globalClusterIdentifier, "string");
|
|
152
|
+
code += buildProperty(database.enableGlobalWriteForwarding !== undefined, "enableGlobalWriteForwarding", database.enableGlobalWriteForwarding);
|
|
153
|
+
return code;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if a database needs a variable assignment (referenced by compute)
|
|
157
|
+
*/
|
|
158
|
+
export function databaseNeedsVariable(database, plan) {
|
|
159
|
+
return plan.compute.some((compute) => compute.needsConnection &&
|
|
160
|
+
compute.connectedDatabase?.includes(database.name));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generate database infrastructure code
|
|
164
|
+
*/
|
|
165
|
+
export function generateDatabaseCode(plan) {
|
|
166
|
+
if (plan.database.length === 0)
|
|
167
|
+
return "";
|
|
168
|
+
let code = "";
|
|
169
|
+
for (let dbIndex = 0; dbIndex < plan.database.length; dbIndex++) {
|
|
170
|
+
const database = plan.database[dbIndex];
|
|
171
|
+
const databaseVariable = getVariableName(database);
|
|
172
|
+
const hasConnections = databaseNeedsVariable(database, plan);
|
|
173
|
+
const needsLeadingNewline = dbIndex > 0;
|
|
174
|
+
const prefix = hasConnections ? `const ${databaseVariable} = ` : "";
|
|
175
|
+
const leadingNewline = needsLeadingNewline ? "\n" : "";
|
|
176
|
+
code += `${leadingNewline}${prefix}app.addDatabase(
|
|
177
|
+
DatabaseFactory.build("${database.name}", {
|
|
178
|
+
vpc: app.getVpc(),
|
|
179
|
+
type: "${database.type}",
|
|
180
|
+
databaseName: "${database.databaseName}",`;
|
|
181
|
+
// Engine expression round-trip: emit raw CDK expression if preserved
|
|
182
|
+
if (database.engineExpression) {
|
|
183
|
+
code += `
|
|
184
|
+
engine: ${database.engineExpression},`;
|
|
185
|
+
}
|
|
186
|
+
else if (database.databaseEngine) {
|
|
187
|
+
code += `
|
|
188
|
+
databaseEngine: "${database.databaseEngine}",`;
|
|
189
|
+
}
|
|
190
|
+
code += generateDatabaseSharedProps(database);
|
|
191
|
+
code += generateDatabaseInstanceProps(database);
|
|
192
|
+
code += generateDatabaseAuroraProps(database);
|
|
193
|
+
code += generateDatabaseGlobalAuroraProps(database);
|
|
194
|
+
code += emitExtraProperties(database.extraProperties);
|
|
195
|
+
code += `
|
|
196
|
+
})
|
|
197
|
+
);
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
return code;
|
|
201
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Generation Modules
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports all code generation utilities for generating
|
|
5
|
+
* infrastructure code from resource plans.
|
|
6
|
+
*/
|
|
7
|
+
export { type IdentifierValue, type ExpressionValue, type CallValue, type SpecialValue, isSpecialValue, toPascalCase, toKebab, toValidDatabaseName, toVariableName, formatValue, buildProperty, getVariableName, resolveResourceVariable, emitExtraProperties, } from "./common.js";
|
|
8
|
+
export { DATABASE_ENV_VARS, CREDENTIAL_KEYS, type DatabaseEnvVarEntry, type DatabaseEnvVars, getConnectedDatabases, generateDatabaseEnvVarEntries, formatConnectionsCode, buildDatabaseEnvVars, databaseNeedsVariable, generateDatabaseCode, } from "./database.js";
|
|
9
|
+
export { STORAGE_ENV_VARS, type StorageEnvVarEntry, type StorageEnvVars, getConnectedStorage, generateStorageEnvVarEntries, buildStorageEnvVars, formatAllConnectionsCode, } from "./storageConnections.js";
|
|
10
|
+
export { generateLambdaCode, generateLambdaConnectionsCode, generateEc2Code, generateEcsCode, generateComputeCode, } from "./compute.js";
|
|
11
|
+
export { generateS3Code, generateDynamoDBCode, generateSQSCode, s3NeedsVariable, } from "./storage.js";
|
|
12
|
+
export { generateNetworkCode, generateTags, generateAppInit, generateImports, generateCDNCode, collectCdnReferencedResources, usesPatternApproach, generatePatternCodeWithComments, } from "./infrastructure.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Generation Modules
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports all code generation utilities for generating
|
|
5
|
+
* infrastructure code from resource plans.
|
|
6
|
+
*/
|
|
7
|
+
// Common utilities
|
|
8
|
+
export { isSpecialValue, toPascalCase, toKebab, toValidDatabaseName, toVariableName, formatValue, buildProperty, getVariableName, resolveResourceVariable, emitExtraProperties, } from "./common.js";
|
|
9
|
+
// Database generation
|
|
10
|
+
export { DATABASE_ENV_VARS, CREDENTIAL_KEYS, getConnectedDatabases, generateDatabaseEnvVarEntries, formatConnectionsCode, buildDatabaseEnvVars, databaseNeedsVariable, generateDatabaseCode, } from "./database.js";
|
|
11
|
+
// Storage connections
|
|
12
|
+
export { STORAGE_ENV_VARS, getConnectedStorage, generateStorageEnvVarEntries, buildStorageEnvVars, formatAllConnectionsCode, } from "./storageConnections.js";
|
|
13
|
+
// Compute generation
|
|
14
|
+
export { generateLambdaCode, generateLambdaConnectionsCode, generateEc2Code, generateEcsCode, generateComputeCode, } from "./compute.js";
|
|
15
|
+
// Storage generation
|
|
16
|
+
export { generateS3Code, generateDynamoDBCode, generateSQSCode, s3NeedsVariable, } from "./storage.js";
|
|
17
|
+
// Infrastructure generation
|
|
18
|
+
export { generateNetworkCode, generateTags, generateAppInit, generateImports, generateCDNCode, collectCdnReferencedResources, usesPatternApproach, generatePatternCodeWithComments, } from "./infrastructure.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Functions for generating infrastructure setup code including
|
|
5
|
+
* network configuration, tags, imports, CDN, and app initialisation.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApplicationResourcePlan } from "../schemas/resourceSchemas.js";
|
|
8
|
+
export declare const COST_ALLOCATION_TAG: "fjall:costAllocation:owner";
|
|
9
|
+
export declare const DEFAULT_COST_ALLOCATION_OWNER: "engineering";
|
|
10
|
+
/**
|
|
11
|
+
* Generate network infrastructure code for additional networks
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateNetworkCode(plan: ApplicationResourcePlan): string;
|
|
14
|
+
/**
|
|
15
|
+
* Generate tags infrastructure code
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateTags(plan: ApplicationResourcePlan): string;
|
|
18
|
+
/**
|
|
19
|
+
* Generate app initialisation code
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateAppInit(plan: ApplicationResourcePlan): string;
|
|
22
|
+
/**
|
|
23
|
+
* Generate import statements for the infrastructure file
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateImports(plan: ApplicationResourcePlan): string;
|
|
26
|
+
/**
|
|
27
|
+
* Generate CDN infrastructure code
|
|
28
|
+
*/
|
|
29
|
+
export declare function generateCDNCode(plan: ApplicationResourcePlan): string;
|
|
30
|
+
/**
|
|
31
|
+
* Collect CDN-referenced resources for compute variable naming
|
|
32
|
+
*/
|
|
33
|
+
export declare function collectCdnReferencedResources(plan: ApplicationResourcePlan): Set<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a plan uses the pattern approach (PatternFactory).
|
|
36
|
+
* When patternConfig is present, we generate a single PatternFactory.build() call
|
|
37
|
+
* instead of individual factory calls for each resource.
|
|
38
|
+
*/
|
|
39
|
+
export declare function usesPatternApproach(plan: ApplicationResourcePlan): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Generate pattern infrastructure code with clean formatting.
|
|
42
|
+
* Handles both Payload and Next.js OpenNext patterns.
|
|
43
|
+
*/
|
|
44
|
+
export declare function generatePatternCodeWithComments(plan: ApplicationResourcePlan): string;
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infrastructure Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Functions for generating infrastructure setup code including
|
|
5
|
+
* network configuration, tags, imports, CDN, and app initialisation.
|
|
6
|
+
*/
|
|
7
|
+
import { toVariableName, toPascalCase, formatValue, resolveResourceVariable, emitExtraProperties, } from "./common.js";
|
|
8
|
+
import { COMPUTE_TYPE } from "../schemas/constants.js";
|
|
9
|
+
export const COST_ALLOCATION_TAG = "fjall:costAllocation:owner";
|
|
10
|
+
export const DEFAULT_COST_ALLOCATION_OWNER = "engineering";
|
|
11
|
+
/**
|
|
12
|
+
* Check if value is an empty object {}.
|
|
13
|
+
* Used to filter out meaningless config like `flowLogs: {}`.
|
|
14
|
+
*/
|
|
15
|
+
function isEmptyObject(value) {
|
|
16
|
+
return (typeof value === "object" &&
|
|
17
|
+
value !== null &&
|
|
18
|
+
Object.keys(value).length === 0);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Clean network config by removing empty flowLogs.
|
|
22
|
+
* `flowLogs: {}` means "use defaults" which is the same as omitting it.
|
|
23
|
+
* Only keep flowLogs when it's `false` (explicitly disabled) or has config.
|
|
24
|
+
*/
|
|
25
|
+
function cleanNetworkConfig(network) {
|
|
26
|
+
const cleaned = { ...network };
|
|
27
|
+
if (isEmptyObject(cleaned.flowLogs)) {
|
|
28
|
+
delete cleaned.flowLogs;
|
|
29
|
+
}
|
|
30
|
+
return cleaned;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate network infrastructure code for additional networks
|
|
34
|
+
*/
|
|
35
|
+
export function generateNetworkCode(plan) {
|
|
36
|
+
if (!plan.additionalNetworks || plan.additionalNetworks.length === 0) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
let code = "";
|
|
40
|
+
for (const network of plan.additionalNetworks) {
|
|
41
|
+
const varName = toVariableName(network.name);
|
|
42
|
+
const config = {};
|
|
43
|
+
if (network.maxAzs !== undefined)
|
|
44
|
+
config.maxAzs = network.maxAzs;
|
|
45
|
+
if (network.natGateways !== undefined)
|
|
46
|
+
config.natGateways = network.natGateways;
|
|
47
|
+
if (network.flowLogs !== undefined)
|
|
48
|
+
config.flowLogs = network.flowLogs;
|
|
49
|
+
if (network.vpcEndpoints !== undefined)
|
|
50
|
+
config.vpcEndpoints = network.vpcEndpoints;
|
|
51
|
+
const cleanedConfig = cleanNetworkConfig(config);
|
|
52
|
+
code += `const ${varName} = app.addNetwork(
|
|
53
|
+
NetworkFactory.build("${network.name}", ${formatValue(cleanedConfig, " ")})
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
return code;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate tags infrastructure code
|
|
62
|
+
*/
|
|
63
|
+
export function generateTags(plan) {
|
|
64
|
+
const tags = {};
|
|
65
|
+
if (plan.tags && Object.keys(plan.tags).length > 0) {
|
|
66
|
+
Object.assign(tags, plan.tags);
|
|
67
|
+
}
|
|
68
|
+
if (!tags[COST_ALLOCATION_TAG]) {
|
|
69
|
+
tags[COST_ALLOCATION_TAG] = plan.owner || DEFAULT_COST_ALLOCATION_OWNER;
|
|
70
|
+
}
|
|
71
|
+
const tagEntries = Object.entries(tags)
|
|
72
|
+
.map(([key, value]) => ` "${key}": "${value}"`)
|
|
73
|
+
.join(",\n");
|
|
74
|
+
return `app.addTags({
|
|
75
|
+
${tagEntries}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Generate app initialisation code
|
|
82
|
+
*/
|
|
83
|
+
export function generateAppInit(plan) {
|
|
84
|
+
let code = `
|
|
85
|
+
const appName = "${plan.appName}";
|
|
86
|
+
`;
|
|
87
|
+
const backupConfig = plan.backup && typeof plan.backup === "object" ? plan.backup : undefined;
|
|
88
|
+
const tunnelConfig = plan.tunnel && typeof plan.tunnel === "object" ? plan.tunnel : undefined;
|
|
89
|
+
const tunnelSnippet = tunnelConfig
|
|
90
|
+
? tunnelConfig.instanceType
|
|
91
|
+
? `tunnel: { instanceType: "${tunnelConfig.instanceType}" }`
|
|
92
|
+
: "tunnel: true"
|
|
93
|
+
: undefined;
|
|
94
|
+
if (plan.vpcId) {
|
|
95
|
+
code += `const app = App.getApp(appName, {
|
|
96
|
+
network: { useExisting: "${plan.vpcId}" }`;
|
|
97
|
+
if (backupConfig) {
|
|
98
|
+
code += `,
|
|
99
|
+
backup: { tier: "${backupConfig.tier}" }`;
|
|
100
|
+
}
|
|
101
|
+
if (tunnelSnippet) {
|
|
102
|
+
code += `,
|
|
103
|
+
${tunnelSnippet}`;
|
|
104
|
+
}
|
|
105
|
+
code += `
|
|
106
|
+
});
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
else if (plan.network) {
|
|
110
|
+
const cleanedNetwork = cleanNetworkConfig(plan.network);
|
|
111
|
+
code += `const app = App.getApp(appName, {
|
|
112
|
+
network: ${formatValue(cleanedNetwork, " ")}`;
|
|
113
|
+
if (backupConfig) {
|
|
114
|
+
code += `,
|
|
115
|
+
backup: { tier: "${backupConfig.tier}" }`;
|
|
116
|
+
}
|
|
117
|
+
if (tunnelSnippet) {
|
|
118
|
+
code += `,
|
|
119
|
+
${tunnelSnippet}`;
|
|
120
|
+
}
|
|
121
|
+
code += `
|
|
122
|
+
});
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
code += `const app = App.getApp(appName, { network: false`;
|
|
127
|
+
if (backupConfig) {
|
|
128
|
+
code += `, backup: { tier: "${backupConfig.tier}" }`;
|
|
129
|
+
}
|
|
130
|
+
if (tunnelSnippet) {
|
|
131
|
+
code += `, ${tunnelSnippet}`;
|
|
132
|
+
}
|
|
133
|
+
code += ` });
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
code += `
|
|
137
|
+
`;
|
|
138
|
+
return code;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Generate import statements for the infrastructure file
|
|
142
|
+
*/
|
|
143
|
+
export function generateImports(plan) {
|
|
144
|
+
const imports = [];
|
|
145
|
+
if (plan.patternConfig) {
|
|
146
|
+
imports.push("PatternFactory");
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
if (plan.database.length > 0) {
|
|
150
|
+
imports.push("DatabaseFactory");
|
|
151
|
+
}
|
|
152
|
+
if (plan.s3.length > 0) {
|
|
153
|
+
imports.push("StorageFactory");
|
|
154
|
+
}
|
|
155
|
+
if (plan.compute.length > 0) {
|
|
156
|
+
imports.push("ComputeFactory");
|
|
157
|
+
imports.push("getConfig");
|
|
158
|
+
}
|
|
159
|
+
if (plan.dynamodb &&
|
|
160
|
+
plan.dynamodb.length > 0 &&
|
|
161
|
+
plan.database.length === 0) {
|
|
162
|
+
imports.push("DatabaseFactory");
|
|
163
|
+
}
|
|
164
|
+
if (plan.sqs && plan.sqs.length > 0) {
|
|
165
|
+
imports.push("MessagingFactory");
|
|
166
|
+
}
|
|
167
|
+
if (plan.cdn) {
|
|
168
|
+
imports.push("CdnFactory");
|
|
169
|
+
}
|
|
170
|
+
const hasCodeBasedLambda = plan.compute.some((c) => c.type === COMPUTE_TYPE.LAMBDA && c.deployment === "code");
|
|
171
|
+
if (hasCodeBasedLambda) {
|
|
172
|
+
imports.push("Code");
|
|
173
|
+
imports.push("Runtime");
|
|
174
|
+
}
|
|
175
|
+
const hasFunctionUrl = plan.compute.some((c) => c.type === COMPUTE_TYPE.LAMBDA && c.functionUrl);
|
|
176
|
+
if (hasFunctionUrl) {
|
|
177
|
+
imports.push("FunctionUrlAuthType");
|
|
178
|
+
}
|
|
179
|
+
const hasArchitecture = plan.compute.some((c) => c.type === COMPUTE_TYPE.LAMBDA &&
|
|
180
|
+
(c.architecture || c.deployment === "container"));
|
|
181
|
+
if (hasArchitecture) {
|
|
182
|
+
imports.push("Architecture");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (plan.additionalNetworks && plan.additionalNetworks.length > 0) {
|
|
186
|
+
imports.push("NetworkFactory");
|
|
187
|
+
}
|
|
188
|
+
// Merge additional named imports for @fjall/components-infrastructure
|
|
189
|
+
if (plan.additionalManagedImports) {
|
|
190
|
+
for (const extra of plan.additionalManagedImports) {
|
|
191
|
+
if (extra.moduleSpecifier === "@fjall/components-infrastructure" ||
|
|
192
|
+
extra.moduleSpecifier === "@fjall/infrastructure") {
|
|
193
|
+
for (const name of extra.namedImports) {
|
|
194
|
+
if (!imports.includes(name)) {
|
|
195
|
+
imports.push(name);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const importList = imports.length > 0 ? `, ${imports.join(", ")}` : "";
|
|
202
|
+
let code = `#!/usr/bin/env node
|
|
203
|
+
|
|
204
|
+
import { App${importList} } from "@fjall/components-infrastructure";
|
|
205
|
+
`;
|
|
206
|
+
// Append separate lines for other managed modules (aws-cdk-lib/*, constructs)
|
|
207
|
+
if (plan.additionalManagedImports) {
|
|
208
|
+
for (const extra of plan.additionalManagedImports) {
|
|
209
|
+
if (extra.moduleSpecifier === "@fjall/components-infrastructure" ||
|
|
210
|
+
extra.moduleSpecifier === "@fjall/infrastructure") {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const namedPart = extra.namedImports.length > 0
|
|
214
|
+
? `{ ${extra.namedImports.join(", ")} }`
|
|
215
|
+
: "";
|
|
216
|
+
const defaultPart = extra.defaultImport ?? "";
|
|
217
|
+
const importParts = [defaultPart, namedPart].filter(Boolean).join(", ");
|
|
218
|
+
if (importParts) {
|
|
219
|
+
code += `import ${importParts} from "${extra.moduleSpecifier}";\n`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return code;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Generate CDN infrastructure code
|
|
227
|
+
*/
|
|
228
|
+
export function generateCDNCode(plan) {
|
|
229
|
+
if (!plan.cdn)
|
|
230
|
+
return "";
|
|
231
|
+
const defaultOriginVar = resolveResourceVariable(plan, plan.cdn.defaultOriginRef);
|
|
232
|
+
let code = `
|
|
233
|
+
app.addCdn(
|
|
234
|
+
CdnFactory.build("${plan.cdn.name}", {
|
|
235
|
+
originType: "auto",
|
|
236
|
+
origin: ${defaultOriginVar}`;
|
|
237
|
+
if (plan.cdn.behaviours && plan.cdn.behaviours.length > 0) {
|
|
238
|
+
const behavioursStr = plan.cdn.behaviours
|
|
239
|
+
.map((b) => {
|
|
240
|
+
const originVar = resolveResourceVariable(plan, b.originRef);
|
|
241
|
+
let bCode = `{\n pathPattern: "${b.pathPattern}",\n origin: ${originVar},`;
|
|
242
|
+
if (b.cachePolicy) {
|
|
243
|
+
bCode += `\n cachePolicy: "${b.cachePolicy}",`;
|
|
244
|
+
}
|
|
245
|
+
bCode += `\n }`;
|
|
246
|
+
return bCode;
|
|
247
|
+
})
|
|
248
|
+
.join(",\n ");
|
|
249
|
+
code += `,
|
|
250
|
+
behaviours: [
|
|
251
|
+
${behavioursStr}
|
|
252
|
+
]`;
|
|
253
|
+
}
|
|
254
|
+
if (plan.cdn.customDomain) {
|
|
255
|
+
code += `,
|
|
256
|
+
domainNames: ["${plan.cdn.customDomain}"]`;
|
|
257
|
+
}
|
|
258
|
+
if (plan.cdn.certificateArn) {
|
|
259
|
+
code += `,
|
|
260
|
+
certificateArn: "${plan.cdn.certificateArn}"`;
|
|
261
|
+
}
|
|
262
|
+
if (plan.cdn.accessGate !== undefined) {
|
|
263
|
+
if (plan.cdn.accessGate === false) {
|
|
264
|
+
code += `,
|
|
265
|
+
accessGate: false`;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
code += `,
|
|
269
|
+
accessGate: ${formatValue(plan.cdn.accessGate, " ")}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
code += emitExtraProperties(plan.cdn.extraProperties);
|
|
273
|
+
code += `
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
`;
|
|
277
|
+
return code;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Collect CDN-referenced resources for compute variable naming
|
|
281
|
+
*/
|
|
282
|
+
export function collectCdnReferencedResources(plan) {
|
|
283
|
+
const cdnReferencedResources = new Set();
|
|
284
|
+
if (plan.cdn) {
|
|
285
|
+
if (plan.cdn.defaultOriginRef) {
|
|
286
|
+
cdnReferencedResources.add(plan.cdn.defaultOriginRef);
|
|
287
|
+
}
|
|
288
|
+
if (plan.cdn.behaviours) {
|
|
289
|
+
for (const behaviour of plan.cdn.behaviours) {
|
|
290
|
+
cdnReferencedResources.add(behaviour.originRef);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return cdnReferencedResources;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Check if a plan uses the pattern approach (PatternFactory).
|
|
298
|
+
* When patternConfig is present, we generate a single PatternFactory.build() call
|
|
299
|
+
* instead of individual factory calls for each resource.
|
|
300
|
+
*/
|
|
301
|
+
export function usesPatternApproach(plan) {
|
|
302
|
+
return plan.patternConfig !== undefined;
|
|
303
|
+
}
|
|
304
|
+
/** Default values for OpenNext pattern code generation (matches tier presets). */
|
|
305
|
+
const OPENNEXT_DEFAULTS = Object.freeze({
|
|
306
|
+
database: {
|
|
307
|
+
type: "Instance",
|
|
308
|
+
backupRetention: 7,
|
|
309
|
+
deletionProtection: true,
|
|
310
|
+
},
|
|
311
|
+
compute: {
|
|
312
|
+
server: { memorySize: 1536, timeout: 30 },
|
|
313
|
+
imageOptimisation: { memorySize: 1536, timeout: 30 },
|
|
314
|
+
revalidation: { memorySize: 768, timeout: 300 },
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
/**
|
|
318
|
+
* Generate pattern infrastructure code with clean formatting.
|
|
319
|
+
* Handles both Payload and Next.js OpenNext patterns.
|
|
320
|
+
*/
|
|
321
|
+
export function generatePatternCodeWithComments(plan) {
|
|
322
|
+
if (!plan.patternConfig)
|
|
323
|
+
return "";
|
|
324
|
+
const config = plan.patternConfig;
|
|
325
|
+
const pascalName = toPascalCase(config.name);
|
|
326
|
+
const typeSuffix = config.type === "payload" ? "Payload" : "Nextjs";
|
|
327
|
+
const constructId = `${pascalName}${typeSuffix}`;
|
|
328
|
+
let code = `app.addPattern(
|
|
329
|
+
PatternFactory.build("${constructId}", {
|
|
330
|
+
type: "${config.type}",
|
|
331
|
+
name: "${config.name}",`;
|
|
332
|
+
if (config.domain) {
|
|
333
|
+
code += `
|
|
334
|
+
domain: "${config.domain}",`;
|
|
335
|
+
}
|
|
336
|
+
if (config.type === "payload" || config.type === "nextjs") {
|
|
337
|
+
const db = config.database || {};
|
|
338
|
+
const compute = config.compute || {};
|
|
339
|
+
const dbType = db.type || OPENNEXT_DEFAULTS.database.type;
|
|
340
|
+
const backupRetention = db.backupRetention ?? OPENNEXT_DEFAULTS.database.backupRetention;
|
|
341
|
+
const deletionProtection = db.deletionProtection ?? OPENNEXT_DEFAULTS.database.deletionProtection;
|
|
342
|
+
code += `
|
|
343
|
+
database: {
|
|
344
|
+
type: "${dbType}",`;
|
|
345
|
+
if (db.databaseName) {
|
|
346
|
+
code += `
|
|
347
|
+
databaseName: "${db.databaseName}",`;
|
|
348
|
+
}
|
|
349
|
+
if (dbType === "Instance" && db.instanceType) {
|
|
350
|
+
code += `
|
|
351
|
+
instanceType: "${db.instanceType}",`;
|
|
352
|
+
}
|
|
353
|
+
code += `
|
|
354
|
+
backupRetention: ${backupRetention},
|
|
355
|
+
deletionProtection: ${deletionProtection},
|
|
356
|
+
},`;
|
|
357
|
+
const serverMemory = compute.server?.memorySize ?? OPENNEXT_DEFAULTS.compute.server.memorySize;
|
|
358
|
+
const serverTimeout = compute.server?.timeout ?? OPENNEXT_DEFAULTS.compute.server.timeout;
|
|
359
|
+
const imageMemory = compute.imageOptimisation?.memorySize ??
|
|
360
|
+
OPENNEXT_DEFAULTS.compute.imageOptimisation.memorySize;
|
|
361
|
+
const imageTimeout = compute.imageOptimisation?.timeout ??
|
|
362
|
+
OPENNEXT_DEFAULTS.compute.imageOptimisation.timeout;
|
|
363
|
+
const revalidationMemory = compute.revalidation?.memorySize ??
|
|
364
|
+
OPENNEXT_DEFAULTS.compute.revalidation.memorySize;
|
|
365
|
+
const revalidationTimeout = compute.revalidation?.timeout ??
|
|
366
|
+
OPENNEXT_DEFAULTS.compute.revalidation.timeout;
|
|
367
|
+
code += `
|
|
368
|
+
compute: {
|
|
369
|
+
server: {
|
|
370
|
+
memorySize: ${serverMemory},
|
|
371
|
+
timeout: ${serverTimeout},
|
|
372
|
+
},
|
|
373
|
+
imageOptimisation: {
|
|
374
|
+
memorySize: ${imageMemory},
|
|
375
|
+
timeout: ${imageTimeout},
|
|
376
|
+
},
|
|
377
|
+
revalidation: {
|
|
378
|
+
memorySize: ${revalidationMemory},
|
|
379
|
+
timeout: ${revalidationTimeout},
|
|
380
|
+
},
|
|
381
|
+
},`;
|
|
382
|
+
}
|
|
383
|
+
code += `
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
`;
|
|
388
|
+
return code;
|
|
389
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Functions for generating storage infrastructure code including
|
|
5
|
+
* S3 buckets, DynamoDB tables, and SQS queues.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApplicationResourcePlan, S3ResourcePlan } from "../schemas/resourceSchemas.js";
|
|
8
|
+
/**
|
|
9
|
+
* Check if an S3 bucket needs a variable assignment (referenced by compute or CDN)
|
|
10
|
+
*/
|
|
11
|
+
export declare function s3NeedsVariable(bucket: S3ResourcePlan, plan: ApplicationResourcePlan): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Generate S3 bucket infrastructure code
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateS3Code(plan: ApplicationResourcePlan): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generate DynamoDB table infrastructure code
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateDynamoDBCode(plan: ApplicationResourcePlan): string;
|
|
20
|
+
/**
|
|
21
|
+
* Generate SQS queue infrastructure code
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateSQSCode(plan: ApplicationResourcePlan): string;
|