@fjall/generator 0.88.4 → 0.89.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/LICENSE +21 -0
- package/dist/src/ast/astCdnParser.d.ts +15 -0
- package/dist/src/ast/astCdnParser.js +114 -0
- package/dist/src/ast/astCommonParser.d.ts +90 -0
- package/dist/src/ast/astCommonParser.js +351 -0
- package/dist/src/ast/astComputeParser.d.ts +14 -2
- package/dist/src/ast/astComputeParser.js +55 -9
- package/dist/src/ast/astDatabaseParser.d.ts +104 -0
- package/dist/src/ast/astDatabaseParser.js +275 -0
- package/dist/src/ast/astInfrastructureParser.d.ts +23 -277
- package/dist/src/ast/astInfrastructureParser.js +83 -1456
- package/dist/src/ast/astMessagingParser.d.ts +25 -0
- package/dist/src/ast/astMessagingParser.js +78 -0
- package/dist/src/ast/astNetworkParser.d.ts +70 -0
- package/dist/src/ast/astNetworkParser.js +219 -0
- package/dist/src/ast/astPatternParser.d.ts +80 -0
- package/dist/src/ast/astPatternParser.js +155 -0
- package/dist/src/ast/astStorageParser.d.ts +18 -0
- package/dist/src/ast/astStorageParser.js +164 -0
- package/dist/src/ast/index.d.ts +1 -0
- package/dist/src/ast/index.js +4 -0
- package/dist/src/dns/bindParser.d.ts +13 -0
- package/dist/src/dns/bindParser.js +224 -0
- package/dist/src/dns/bindWriter.d.ts +2 -0
- package/dist/src/dns/bindWriter.js +52 -0
- package/dist/src/dns/index.d.ts +4 -0
- package/dist/src/dns/index.js +4 -0
- package/dist/src/dns/infrastructureWriter.d.ts +2 -0
- package/dist/src/dns/infrastructureWriter.js +58 -0
- package/dist/src/dns/types.d.ts +82 -0
- package/dist/src/dns/types.js +52 -0
- package/dist/src/generation/common.d.ts +1 -16
- package/dist/src/generation/common.js +2 -28
- package/dist/src/generation/compute.js +77 -28
- package/dist/src/generation/index.d.ts +2 -1
- package/dist/src/generation/index.js +3 -1
- package/dist/src/generation/messagingConnections.d.ts +33 -0
- package/dist/src/generation/messagingConnections.js +73 -0
- package/dist/src/generation/storage.d.ts +5 -1
- package/dist/src/generation/storage.js +9 -1
- package/dist/src/generation/storageConnections.d.ts +3 -3
- package/dist/src/generation/storageConnections.js +8 -4
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/planning/resourcePlanning.js +0 -2
- package/dist/src/presets/tierTypes.d.ts +4 -1
- package/dist/src/schemas/applicationSchemas.d.ts +854 -0
- package/dist/src/schemas/applicationSchemas.js +80 -0
- package/dist/src/schemas/baseSchemas.d.ts +206 -0
- package/dist/src/schemas/baseSchemas.js +248 -0
- package/dist/src/schemas/cdnSchemas.d.ts +61 -0
- package/dist/src/schemas/cdnSchemas.js +62 -0
- package/dist/src/schemas/computeSchemas.d.ts +723 -0
- package/dist/src/schemas/computeSchemas.js +727 -0
- package/dist/src/schemas/constants.d.ts +12 -8
- package/dist/src/schemas/constants.js +14 -4
- package/dist/src/schemas/databaseSchemas.d.ts +638 -0
- package/dist/src/schemas/databaseSchemas.js +366 -0
- package/dist/src/schemas/messagingSchemas.d.ts +20 -0
- package/dist/src/schemas/messagingSchemas.js +29 -0
- package/dist/src/schemas/networkSchemas.d.ts +246 -0
- package/dist/src/schemas/networkSchemas.js +125 -0
- package/dist/src/schemas/patternSchemas.d.ts +708 -0
- package/dist/src/schemas/patternSchemas.js +294 -0
- package/dist/src/schemas/resourceSchemas.d.ts +24 -3530
- package/dist/src/schemas/resourceSchemas.js +24 -2011
- package/dist/src/schemas/storageSchemas.d.ts +93 -0
- package/dist/src/schemas/storageSchemas.js +119 -0
- package/dist/src/util/errorUtils.d.ts +1 -2
- package/dist/src/util/errorUtils.js +1 -15
- package/dist/src/validation/patterns.d.ts +9 -0
- package/dist/src/validation/patterns.js +9 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +5 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fjall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { ApplicationResourcePlan } from "../schemas/resourceSchemas.js";
|
|
3
|
+
import { type ParsedObject } from "./astCommonParser.js";
|
|
4
|
+
import type { ParsedS3Resource } from "./astStorageParser.js";
|
|
5
|
+
import type { ParsedComputeResource } from "./astComputeParser.js";
|
|
6
|
+
/** Parsed CDN resource from app.addCdn(CdnFactory.build(...)) */
|
|
7
|
+
export interface ParsedCDNResource {
|
|
8
|
+
resourceName: string;
|
|
9
|
+
config: ParsedObject;
|
|
10
|
+
node: ts.Node;
|
|
11
|
+
}
|
|
12
|
+
/** Find CDN resource from addCdn call */
|
|
13
|
+
export declare function findCDNResource(sourceFile: ts.SourceFile): ParsedCDNResource | undefined;
|
|
14
|
+
/** Convert parsed CDN resource to plan format */
|
|
15
|
+
export declare function convertCDNResource(cdnResource: ParsedCDNResource | undefined, s3Resources: ParsedS3Resource[], computeResources: ParsedComputeResource[]): ApplicationResourcePlan["cdn"];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { asString, asStringArray, asStringUnion, captureExtraProperties, findFirstInAst, isFactoryBuildCall, isFactoryMethodCall, isIdentifierRef, isParsedObject, parseObjectLiteral, } from "./astCommonParser.js";
|
|
3
|
+
// ---- Extraction helpers ----
|
|
4
|
+
function extractCDNResource(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
|
+
return {
|
|
14
|
+
resourceName: nameArg.text,
|
|
15
|
+
config: parseObjectLiteral(configArg),
|
|
16
|
+
node: buildCall,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Resolve a CDN origin identifier back to its resource name */
|
|
20
|
+
function resolveOriginIdentifier(identifier, s3Resources, computeResources) {
|
|
21
|
+
const s3Match = s3Resources.find((s3) => s3.variableName === identifier);
|
|
22
|
+
if (s3Match)
|
|
23
|
+
return s3Match.resourceName;
|
|
24
|
+
const computeMatch = computeResources.find((c) => c.variableName === identifier ||
|
|
25
|
+
c.resourceName.charAt(0).toLowerCase() + c.resourceName.slice(1) ===
|
|
26
|
+
identifier);
|
|
27
|
+
if (computeMatch)
|
|
28
|
+
return computeMatch.resourceName;
|
|
29
|
+
return identifier;
|
|
30
|
+
}
|
|
31
|
+
const CDN_CACHE_POLICIES = ["CACHING_OPTIMIZED", "CACHING_DISABLED"];
|
|
32
|
+
// ---- Public API ----
|
|
33
|
+
/** Find CDN resource from addCdn call */
|
|
34
|
+
export function findCDNResource(sourceFile) {
|
|
35
|
+
return findFirstInAst(sourceFile, (node) => {
|
|
36
|
+
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addCdn"))
|
|
37
|
+
return undefined;
|
|
38
|
+
const cdnArg = node.arguments[0];
|
|
39
|
+
if (!isFactoryBuildCall(cdnArg, "CdnFactory"))
|
|
40
|
+
return undefined;
|
|
41
|
+
return extractCDNResource(cdnArg) ?? undefined;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** Convert parsed CDN resource to plan format */
|
|
45
|
+
export function convertCDNResource(cdnResource, s3Resources, computeResources) {
|
|
46
|
+
if (!cdnResource)
|
|
47
|
+
return undefined;
|
|
48
|
+
const cdn = cdnResource;
|
|
49
|
+
const config = cdn.config;
|
|
50
|
+
const resolveOrigin = (val) => {
|
|
51
|
+
if (isIdentifierRef(val)) {
|
|
52
|
+
return resolveOriginIdentifier(val.__identifier, s3Resources, computeResources);
|
|
53
|
+
}
|
|
54
|
+
return asString(val) ?? "";
|
|
55
|
+
};
|
|
56
|
+
const defaultOriginRef = resolveOrigin(config.origin);
|
|
57
|
+
const result = {
|
|
58
|
+
name: cdn.resourceName,
|
|
59
|
+
defaultOriginRef,
|
|
60
|
+
};
|
|
61
|
+
if (Array.isArray(config.behaviours)) {
|
|
62
|
+
const behaviours = config.behaviours.filter(isParsedObject).map((b) => ({
|
|
63
|
+
pathPattern: asString(b.pathPattern) ?? "",
|
|
64
|
+
originRef: resolveOrigin(b.origin),
|
|
65
|
+
...{
|
|
66
|
+
...(asStringUnion(b.cachePolicy, CDN_CACHE_POLICIES) !== undefined && {
|
|
67
|
+
cachePolicy: asStringUnion(b.cachePolicy, CDN_CACHE_POLICIES),
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
if (behaviours.length > 0)
|
|
72
|
+
result.behaviours = behaviours;
|
|
73
|
+
}
|
|
74
|
+
const customDomain = asString(config.customDomain);
|
|
75
|
+
if (customDomain) {
|
|
76
|
+
result.customDomain = customDomain;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const domainNames = asStringArray(config.domainNames);
|
|
80
|
+
if (domainNames && domainNames.length > 0) {
|
|
81
|
+
result.customDomain = domainNames[0];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const certificateArn = asString(config.certificateArn);
|
|
85
|
+
if (certificateArn)
|
|
86
|
+
result.certificateArn = certificateArn;
|
|
87
|
+
if (config.accessGate !== undefined) {
|
|
88
|
+
if (config.accessGate === false) {
|
|
89
|
+
result.accessGate = false;
|
|
90
|
+
}
|
|
91
|
+
else if (isParsedObject(config.accessGate)) {
|
|
92
|
+
const gate = config.accessGate;
|
|
93
|
+
const gateType = asString(gate.type);
|
|
94
|
+
const username = asString(gate.username);
|
|
95
|
+
const password = asString(gate.password);
|
|
96
|
+
if (gateType === "basic-auth" && username && password) {
|
|
97
|
+
result.accessGate = { type: "basic-auth", username, password };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const CDN_KNOWN_KEYS = new Set([
|
|
102
|
+
"originType",
|
|
103
|
+
"origin",
|
|
104
|
+
"behaviours",
|
|
105
|
+
"customDomain",
|
|
106
|
+
"domainNames",
|
|
107
|
+
"certificateArn",
|
|
108
|
+
"accessGate",
|
|
109
|
+
]);
|
|
110
|
+
const extras = captureExtraProperties(config, CDN_KNOWN_KEYS);
|
|
111
|
+
if (extras.length > 0)
|
|
112
|
+
result.extraProperties = extras;
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { ExtraProperty } from "../schemas/resourceSchemas.js";
|
|
3
|
+
/** Special marker for identifier references in parsed AST */
|
|
4
|
+
export interface IdentifierRef {
|
|
5
|
+
__identifier: string;
|
|
6
|
+
}
|
|
7
|
+
/** Special marker for property access expressions in parsed AST */
|
|
8
|
+
export interface ExpressionRef {
|
|
9
|
+
__expression: string;
|
|
10
|
+
}
|
|
11
|
+
/** Special marker for function call expressions in parsed AST */
|
|
12
|
+
export interface CallRef {
|
|
13
|
+
__call: string;
|
|
14
|
+
}
|
|
15
|
+
/** Special marker for unknown/unsupported expressions in parsed AST */
|
|
16
|
+
interface UnknownRef {
|
|
17
|
+
__unknown: string;
|
|
18
|
+
}
|
|
19
|
+
/** Union of all possible values that can be parsed from AST expressions */
|
|
20
|
+
export type ParsedValue = string | number | boolean | null | undefined | IdentifierRef | ExpressionRef | CallRef | UnknownRef | ParsedValue[] | ParsedObject;
|
|
21
|
+
/** Object with string keys and parsed values */
|
|
22
|
+
export type ParsedObject = {
|
|
23
|
+
[key: string]: ParsedValue;
|
|
24
|
+
};
|
|
25
|
+
/** Base check for non-null, non-array objects */
|
|
26
|
+
export declare function isPlainObject(value: ParsedValue): value is Record<string, ParsedValue>;
|
|
27
|
+
/** Type guard for identifier references */
|
|
28
|
+
export declare function isIdentifierRef(value: ParsedValue): value is IdentifierRef;
|
|
29
|
+
/** Type guard for expression references */
|
|
30
|
+
export declare function isExpressionRef(value: ParsedValue): value is ExpressionRef;
|
|
31
|
+
/** Type guard for call references */
|
|
32
|
+
export declare function isCallRef(value: ParsedValue): value is CallRef;
|
|
33
|
+
/** Type guard for parsed objects */
|
|
34
|
+
export declare function isParsedObject(value: ParsedValue): value is ParsedObject;
|
|
35
|
+
/** Safely extract a string from ParsedValue */
|
|
36
|
+
export declare function asString(value: ParsedValue): string | undefined;
|
|
37
|
+
/** Safely extract a number from ParsedValue */
|
|
38
|
+
export declare function asNumber(value: ParsedValue): number | undefined;
|
|
39
|
+
/** Safely extract a boolean from ParsedValue */
|
|
40
|
+
export declare function asBoolean(value: ParsedValue): boolean | undefined;
|
|
41
|
+
/** Safely extract a string array from ParsedValue */
|
|
42
|
+
export declare function asStringArray(value: ParsedValue): string[] | undefined;
|
|
43
|
+
/** Narrow a string to a union member, returning undefined if not a valid member */
|
|
44
|
+
export declare function asStringUnion<T extends readonly string[]>(value: ParsedValue | undefined, validValues: T): T[number] | undefined;
|
|
45
|
+
/** Returns a shallow copy with all undefined-valued properties removed */
|
|
46
|
+
export declare function omitUndefined<T extends Record<string, unknown>>(obj: T): Partial<T>;
|
|
47
|
+
/** Reconstruct source text from a parsed AST value for round-trip preservation */
|
|
48
|
+
export declare function parsedValueToSourceText(value: ParsedValue): string;
|
|
49
|
+
/** Capture properties from a parsed config that are NOT in the known keys set */
|
|
50
|
+
export declare function captureExtraProperties(config: ParsedObject, knownKeys: Set<string>): ExtraProperty[];
|
|
51
|
+
/** Check if an object literal property is a named property assignment */
|
|
52
|
+
export declare function isNamedProperty(prop: ts.ObjectLiteralElementLike, name: string): prop is ts.PropertyAssignment;
|
|
53
|
+
/** Check if a node is an App.getApp() call expression */
|
|
54
|
+
export declare function isAppGetAppCall(node: ts.Node): node is ts.CallExpression;
|
|
55
|
+
/** Check if a node is a chained factory method call (e.g., app.addDatabase(...)) */
|
|
56
|
+
export declare function isFactoryMethodCall(node: ts.CallExpression, methodName: string): boolean;
|
|
57
|
+
/** Traverse AST and return the first non-undefined result from the finder. */
|
|
58
|
+
export declare function findFirstInAst<T>(sourceFile: ts.SourceFile, finder: (node: ts.Node) => T | undefined): T | undefined;
|
|
59
|
+
/** Traverse AST and collect all non-null results from the collector. */
|
|
60
|
+
export declare function collectFromAst<T>(sourceFile: ts.SourceFile, collector: (node: ts.Node) => T | null): T[];
|
|
61
|
+
/** Type guard for Factory.build() call expressions */
|
|
62
|
+
export declare function isFactoryBuildCall(node: ts.Node, factoryName: string): node is ts.CallExpression;
|
|
63
|
+
/** Extract variable name from the parent declaration of a node */
|
|
64
|
+
export declare function extractVariableName(node: ts.Node): string | undefined;
|
|
65
|
+
/** Identity cast - trusts that parsed CDK output matches the expected shape.
|
|
66
|
+
* Safe because input is always generated CDK code that was originally validated by Zod schemas. */
|
|
67
|
+
export declare const typed: <T>() => (v: ParsedObject) => T;
|
|
68
|
+
/** Safely extract an object or false from ParsedValue */
|
|
69
|
+
export declare function asObjectOrFalse(value: ParsedValue): object | false | undefined;
|
|
70
|
+
export declare function parseBooleanOrConfig<T>(value: ParsedValue | undefined, cast: (v: ParsedObject) => T): T | false | undefined;
|
|
71
|
+
export declare function parseOptionalConfig<T>(value: ParsedValue | undefined, parser: (obj: ParsedObject) => T): T | undefined;
|
|
72
|
+
/** Extract a named sub-object from a config, returning undefined if not a plain object */
|
|
73
|
+
export declare function extractSubConfig<T>(rawConfig: ParsedObject, field: string, transform: (obj: ParsedObject) => T): T | undefined;
|
|
74
|
+
/** Parse an object literal expression into a ParsedObject */
|
|
75
|
+
export declare function parseObjectLiteral(node: ts.ObjectLiteralExpression): ParsedObject;
|
|
76
|
+
/** Evaluate a TypeScript AST expression to a ParsedValue */
|
|
77
|
+
export declare function evaluateExpression(node: ts.Expression): ParsedValue;
|
|
78
|
+
/** Find a string variable value by name */
|
|
79
|
+
export declare function findVariableValue(sourceFile: ts.SourceFile, varName: string): string | undefined;
|
|
80
|
+
/** Find an object variable declaration by name */
|
|
81
|
+
export declare function findObjectDeclaration(sourceFile: ts.SourceFile, objName: string): ParsedObject | undefined;
|
|
82
|
+
/** Evaluate a property access expression to a string */
|
|
83
|
+
export declare function evaluatePropertyAccess(sourceFile: ts.SourceFile, node: ts.PropertyAccessExpression): string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve a template literal expression to its string value.
|
|
86
|
+
* Walks spans: head.text + resolve each span.expression + span.literal.text.
|
|
87
|
+
* Falls back to stripping backticks from getText() if resolution fails.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveTemplateLiteral(sourceFile: ts.SourceFile, templateExpr: ts.TemplateExpression): string;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { constIncludes } from "../schemas/constants.js";
|
|
3
|
+
/** Base check for non-null, non-array objects */
|
|
4
|
+
export function isPlainObject(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/** Type guard for identifier references */
|
|
8
|
+
export function isIdentifierRef(value) {
|
|
9
|
+
return isPlainObject(value) && "__identifier" in value;
|
|
10
|
+
}
|
|
11
|
+
/** Type guard for expression references */
|
|
12
|
+
export function isExpressionRef(value) {
|
|
13
|
+
return isPlainObject(value) && "__expression" in value;
|
|
14
|
+
}
|
|
15
|
+
/** Type guard for call references */
|
|
16
|
+
export function isCallRef(value) {
|
|
17
|
+
return isPlainObject(value) && "__call" in value;
|
|
18
|
+
}
|
|
19
|
+
/** Type guard for parsed objects */
|
|
20
|
+
export function isParsedObject(value) {
|
|
21
|
+
return (isPlainObject(value) &&
|
|
22
|
+
!("__identifier" in value) &&
|
|
23
|
+
!("__expression" in value) &&
|
|
24
|
+
!("__call" in value) &&
|
|
25
|
+
!("__unknown" in value));
|
|
26
|
+
}
|
|
27
|
+
/** Safely extract a string from ParsedValue */
|
|
28
|
+
export function asString(value) {
|
|
29
|
+
return typeof value === "string" ? value : undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Safely extract a number from ParsedValue */
|
|
32
|
+
export function asNumber(value) {
|
|
33
|
+
return typeof value === "number" ? value : undefined;
|
|
34
|
+
}
|
|
35
|
+
/** Safely extract a boolean from ParsedValue */
|
|
36
|
+
export function asBoolean(value) {
|
|
37
|
+
return typeof value === "boolean" ? value : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Safely extract a string array from ParsedValue */
|
|
40
|
+
export function asStringArray(value) {
|
|
41
|
+
if (!Array.isArray(value))
|
|
42
|
+
return undefined;
|
|
43
|
+
const result = [];
|
|
44
|
+
for (const item of value) {
|
|
45
|
+
if (typeof item === "string")
|
|
46
|
+
result.push(item);
|
|
47
|
+
}
|
|
48
|
+
return result.length > 0 ? result : undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Narrow a string to a union member, returning undefined if not a valid member */
|
|
51
|
+
export function asStringUnion(value, validValues) {
|
|
52
|
+
const str = asString(value);
|
|
53
|
+
if (str === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
return constIncludes(validValues, str) ? str : undefined;
|
|
56
|
+
}
|
|
57
|
+
/** Returns a shallow copy with all undefined-valued properties removed */
|
|
58
|
+
export function omitUndefined(obj) {
|
|
59
|
+
const result = {};
|
|
60
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
61
|
+
if (value !== undefined) {
|
|
62
|
+
result[key] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/** Reconstruct source text from a parsed AST value for round-trip preservation */
|
|
68
|
+
export function parsedValueToSourceText(value) {
|
|
69
|
+
if (value === null)
|
|
70
|
+
return "null";
|
|
71
|
+
if (value === undefined)
|
|
72
|
+
return "undefined";
|
|
73
|
+
if (typeof value === "string")
|
|
74
|
+
return JSON.stringify(value);
|
|
75
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
76
|
+
return String(value);
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
return `[${value.map(parsedValueToSourceText).join(", ")}]`;
|
|
79
|
+
}
|
|
80
|
+
if (!isPlainObject(value))
|
|
81
|
+
return String(value);
|
|
82
|
+
if (isIdentifierRef(value))
|
|
83
|
+
return value.__identifier;
|
|
84
|
+
if (isExpressionRef(value))
|
|
85
|
+
return value.__expression;
|
|
86
|
+
if (isCallRef(value))
|
|
87
|
+
return value.__call;
|
|
88
|
+
if ("__unknown" in value && typeof value.__unknown === "string")
|
|
89
|
+
return value.__unknown;
|
|
90
|
+
const entries = Object.entries(value);
|
|
91
|
+
if (entries.length === 0)
|
|
92
|
+
return "{}";
|
|
93
|
+
const inner = entries
|
|
94
|
+
.map(([k, v]) => `${k}: ${parsedValueToSourceText(v)}`)
|
|
95
|
+
.join(", ");
|
|
96
|
+
return `{ ${inner} }`;
|
|
97
|
+
}
|
|
98
|
+
/** Capture properties from a parsed config that are NOT in the known keys set */
|
|
99
|
+
export function captureExtraProperties(config, knownKeys) {
|
|
100
|
+
const extras = [];
|
|
101
|
+
for (const [key, value] of Object.entries(config)) {
|
|
102
|
+
if (knownKeys.has(key) || value === undefined)
|
|
103
|
+
continue;
|
|
104
|
+
extras.push({ key, sourceText: parsedValueToSourceText(value) });
|
|
105
|
+
}
|
|
106
|
+
return extras;
|
|
107
|
+
}
|
|
108
|
+
/** Check if an object literal property is a named property assignment */
|
|
109
|
+
export function isNamedProperty(prop, name) {
|
|
110
|
+
return (ts.isPropertyAssignment(prop) &&
|
|
111
|
+
ts.isIdentifier(prop.name) &&
|
|
112
|
+
prop.name.text === name);
|
|
113
|
+
}
|
|
114
|
+
/** Check if a node is an App.getApp() call expression */
|
|
115
|
+
export function isAppGetAppCall(node) {
|
|
116
|
+
return (ts.isCallExpression(node) &&
|
|
117
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
118
|
+
node.expression.name.text === "getApp" &&
|
|
119
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
120
|
+
node.expression.expression.text === "App");
|
|
121
|
+
}
|
|
122
|
+
/** Check if a node is a chained factory method call (e.g., app.addDatabase(...)) */
|
|
123
|
+
export function isFactoryMethodCall(node, methodName) {
|
|
124
|
+
const { expression } = node;
|
|
125
|
+
return (ts.isPropertyAccessExpression(expression) &&
|
|
126
|
+
expression.name.text === methodName &&
|
|
127
|
+
node.arguments.length > 0);
|
|
128
|
+
}
|
|
129
|
+
/** Traverse AST and return the first non-undefined result from the finder. */
|
|
130
|
+
export function findFirstInAst(sourceFile, finder) {
|
|
131
|
+
let result;
|
|
132
|
+
const visit = (node) => {
|
|
133
|
+
if (result !== undefined)
|
|
134
|
+
return;
|
|
135
|
+
result = finder(node);
|
|
136
|
+
if (result === undefined)
|
|
137
|
+
ts.forEachChild(node, visit);
|
|
138
|
+
};
|
|
139
|
+
visit(sourceFile);
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
/** Traverse AST and collect all non-null results from the collector. */
|
|
143
|
+
export function collectFromAst(sourceFile, collector) {
|
|
144
|
+
const results = [];
|
|
145
|
+
const visit = (node) => {
|
|
146
|
+
const item = collector(node);
|
|
147
|
+
if (item !== null)
|
|
148
|
+
results.push(item);
|
|
149
|
+
ts.forEachChild(node, visit);
|
|
150
|
+
};
|
|
151
|
+
visit(sourceFile);
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
/** Type guard for Factory.build() call expressions */
|
|
155
|
+
export function isFactoryBuildCall(node, factoryName) {
|
|
156
|
+
return (ts.isCallExpression(node) &&
|
|
157
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
158
|
+
node.expression.name.text === "build" &&
|
|
159
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
160
|
+
node.expression.expression.text === factoryName);
|
|
161
|
+
}
|
|
162
|
+
/** Extract variable name from the parent declaration of a node */
|
|
163
|
+
export function extractVariableName(node) {
|
|
164
|
+
const parent = node.parent;
|
|
165
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
166
|
+
return parent.name.text;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
/** Identity cast - trusts that parsed CDK output matches the expected shape.
|
|
171
|
+
* Safe because input is always generated CDK code that was originally validated by Zod schemas. */
|
|
172
|
+
export const typed = () => (v) => v;
|
|
173
|
+
/** Safely extract an object or false from ParsedValue */
|
|
174
|
+
export function asObjectOrFalse(value) {
|
|
175
|
+
if (value === undefined || value === null)
|
|
176
|
+
return undefined;
|
|
177
|
+
if (value === false)
|
|
178
|
+
return false;
|
|
179
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
180
|
+
return value;
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
export function parseBooleanOrConfig(value, cast) {
|
|
184
|
+
if (value === false)
|
|
185
|
+
return false;
|
|
186
|
+
return isParsedObject(value) ? cast(value) : undefined;
|
|
187
|
+
}
|
|
188
|
+
export function parseOptionalConfig(value, parser) {
|
|
189
|
+
return isParsedObject(value) ? parser(value) : undefined;
|
|
190
|
+
}
|
|
191
|
+
/** Extract a named sub-object from a config, returning undefined if not a plain object */
|
|
192
|
+
export function extractSubConfig(rawConfig, field, transform) {
|
|
193
|
+
const value = rawConfig[field];
|
|
194
|
+
return isParsedObject(value) ? transform(value) : undefined;
|
|
195
|
+
}
|
|
196
|
+
/** Parse an object literal expression into a ParsedObject */
|
|
197
|
+
export function parseObjectLiteral(node) {
|
|
198
|
+
const result = {};
|
|
199
|
+
for (const prop of node.properties) {
|
|
200
|
+
if (!ts.isPropertyAssignment(prop))
|
|
201
|
+
continue;
|
|
202
|
+
const key = ts.isIdentifier(prop.name)
|
|
203
|
+
? prop.name.text
|
|
204
|
+
: ts.isStringLiteral(prop.name)
|
|
205
|
+
? prop.name.text
|
|
206
|
+
: undefined;
|
|
207
|
+
if (key === undefined)
|
|
208
|
+
continue;
|
|
209
|
+
result[key] = evaluateExpression(prop.initializer);
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
/** Evaluate a TypeScript AST expression to a ParsedValue */
|
|
214
|
+
export function evaluateExpression(node) {
|
|
215
|
+
if (ts.isStringLiteral(node)) {
|
|
216
|
+
return node.text;
|
|
217
|
+
}
|
|
218
|
+
else if (ts.isNumericLiteral(node)) {
|
|
219
|
+
return Number(node.text);
|
|
220
|
+
}
|
|
221
|
+
else if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
else if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
else if (ts.isArrayLiteralExpression(node)) {
|
|
228
|
+
return node.elements.map((element) => evaluateExpression(element));
|
|
229
|
+
}
|
|
230
|
+
else if (ts.isObjectLiteralExpression(node)) {
|
|
231
|
+
return parseObjectLiteral(node);
|
|
232
|
+
}
|
|
233
|
+
else if (ts.isIdentifier(node)) {
|
|
234
|
+
return { __identifier: node.text };
|
|
235
|
+
}
|
|
236
|
+
else if (ts.isPropertyAccessExpression(node)) {
|
|
237
|
+
return { __expression: node.getText() };
|
|
238
|
+
}
|
|
239
|
+
else if (ts.isCallExpression(node)) {
|
|
240
|
+
return { __call: node.getText() };
|
|
241
|
+
}
|
|
242
|
+
else if (ts.isBinaryExpression(node) &&
|
|
243
|
+
node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
244
|
+
// Handle || operator - return the left side if it's a literal
|
|
245
|
+
const left = evaluateExpression(node.left);
|
|
246
|
+
const right = evaluateExpression(node.right);
|
|
247
|
+
// For simplicity, prefer string literals from the right side for defaults
|
|
248
|
+
if (typeof right === "string") {
|
|
249
|
+
return right;
|
|
250
|
+
}
|
|
251
|
+
return left;
|
|
252
|
+
}
|
|
253
|
+
else if (ts.isConditionalExpression(node)) {
|
|
254
|
+
// Handle ternary operator - for simplicity, try to evaluate the branches
|
|
255
|
+
const whenTrue = evaluateExpression(node.whenTrue);
|
|
256
|
+
const whenFalse = evaluateExpression(node.whenFalse);
|
|
257
|
+
// Default to the false branch for simplicity
|
|
258
|
+
if (typeof whenFalse === "string" || typeof whenFalse === "number") {
|
|
259
|
+
return whenFalse;
|
|
260
|
+
}
|
|
261
|
+
return whenTrue;
|
|
262
|
+
}
|
|
263
|
+
else if (ts.isTemplateExpression(node) ||
|
|
264
|
+
ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
265
|
+
// Handle template literals to preserve them as expressions during round-trip parsing
|
|
266
|
+
return { __expression: node.getText() };
|
|
267
|
+
}
|
|
268
|
+
return { __unknown: node.getText() };
|
|
269
|
+
}
|
|
270
|
+
/** Find a string variable value by name */
|
|
271
|
+
export function findVariableValue(sourceFile, varName) {
|
|
272
|
+
return findFirstInAst(sourceFile, (node) => {
|
|
273
|
+
if (!ts.isVariableStatement(node))
|
|
274
|
+
return undefined;
|
|
275
|
+
for (const declaration of node.declarationList.declarations) {
|
|
276
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
277
|
+
declaration.name.text === varName &&
|
|
278
|
+
declaration.initializer) {
|
|
279
|
+
if (ts.isStringLiteral(declaration.initializer)) {
|
|
280
|
+
return declaration.initializer.text;
|
|
281
|
+
}
|
|
282
|
+
if (ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
283
|
+
const obj = parseObjectLiteral(declaration.initializer);
|
|
284
|
+
return JSON.stringify(obj);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return undefined;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/** Find an object variable declaration by name */
|
|
292
|
+
export function findObjectDeclaration(sourceFile, objName) {
|
|
293
|
+
return findFirstInAst(sourceFile, (node) => {
|
|
294
|
+
if (!ts.isVariableStatement(node))
|
|
295
|
+
return undefined;
|
|
296
|
+
for (const declaration of node.declarationList.declarations) {
|
|
297
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
298
|
+
declaration.name.text === objName &&
|
|
299
|
+
declaration.initializer &&
|
|
300
|
+
ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
301
|
+
return parseObjectLiteral(declaration.initializer);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/** Evaluate a property access expression to a string */
|
|
308
|
+
export function evaluatePropertyAccess(sourceFile, node) {
|
|
309
|
+
if (!ts.isIdentifier(node.expression))
|
|
310
|
+
return undefined;
|
|
311
|
+
const objValue = findObjectDeclaration(sourceFile, node.expression.text);
|
|
312
|
+
const propName = node.name.text;
|
|
313
|
+
if (objValue && objValue[propName] !== undefined) {
|
|
314
|
+
return String(objValue[propName]);
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Resolve a template literal expression to its string value.
|
|
320
|
+
* Walks spans: head.text + resolve each span.expression + span.literal.text.
|
|
321
|
+
* Falls back to stripping backticks from getText() if resolution fails.
|
|
322
|
+
*/
|
|
323
|
+
export function resolveTemplateLiteral(sourceFile, templateExpr) {
|
|
324
|
+
let result = templateExpr.head.text;
|
|
325
|
+
for (const span of templateExpr.templateSpans) {
|
|
326
|
+
if (ts.isIdentifier(span.expression)) {
|
|
327
|
+
const resolved = findVariableValue(sourceFile, span.expression.text);
|
|
328
|
+
if (resolved) {
|
|
329
|
+
result += resolved;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
// Can't resolve -- fall back to raw getText()
|
|
333
|
+
return templateExpr.getText(sourceFile).replace(/^`|`$/g, "");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else if (ts.isPropertyAccessExpression(span.expression)) {
|
|
337
|
+
const resolved = evaluatePropertyAccess(sourceFile, span.expression);
|
|
338
|
+
if (resolved) {
|
|
339
|
+
result += resolved;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
return templateExpr.getText(sourceFile).replace(/^`|`$/g, "");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
return templateExpr.getText(sourceFile).replace(/^`|`$/g, "");
|
|
347
|
+
}
|
|
348
|
+
result += span.literal.text;
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { type ParsedObject } from "./astCommonParser.js";
|
|
2
3
|
import { type ApplicationResourcePlan } from "../schemas/resourceSchemas.js";
|
|
4
|
+
import type { ParsedDatabaseResource } from "./astDatabaseParser.js";
|
|
5
|
+
import type { ParsedS3Resource } from "./astStorageParser.js";
|
|
6
|
+
export interface ParsedComputeResource {
|
|
7
|
+
variableName?: string;
|
|
8
|
+
resourceName: string;
|
|
9
|
+
type: string;
|
|
10
|
+
config: ParsedObject;
|
|
11
|
+
node: ts.Node;
|
|
12
|
+
}
|
|
13
|
+
/** Find all compute resources from addCompute calls */
|
|
14
|
+
export declare function findComputeResources(sourceFile: ts.SourceFile): ParsedComputeResource[];
|
|
3
15
|
/** Convert parsed compute resources to plan format */
|
|
4
|
-
export declare function convertComputeResources(
|
|
16
|
+
export declare function convertComputeResources(computeResources: ParsedComputeResource[], databaseResources: ParsedDatabaseResource[], s3Resources: ParsedS3Resource[], plan: ApplicationResourcePlan): ApplicationResourcePlan["compute"];
|