@fjall/generator 0.89.4 → 0.89.6
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 +50 -21
- package/README.md +28 -0
- package/dist/.minified +1 -0
- package/dist/src/ast/astCdnParser.d.ts +5 -0
- package/dist/src/ast/astCdnParser.js +1 -114
- package/dist/src/ast/astCommonParser.d.ts +6 -17
- package/dist/src/ast/astCommonParser.js +1 -351
- package/dist/src/ast/astComputeConnectionParser.d.ts +18 -0
- package/dist/src/ast/astComputeConnectionParser.js +1 -0
- package/dist/src/ast/astComputeParser.d.ts +6 -0
- package/dist/src/ast/astComputeParser.js +1 -473
- package/dist/src/ast/astComputeParserHelpers.d.ts +21 -0
- package/dist/src/ast/astComputeParserHelpers.js +1 -0
- package/dist/src/ast/astDatabaseParser.d.ts +9 -24
- package/dist/src/ast/astDatabaseParser.js +1 -275
- package/dist/src/ast/astDomainParser.d.ts +139 -0
- package/dist/src/ast/astDomainParser.js +1 -0
- package/dist/src/ast/astDynamoDBParser.d.ts +35 -0
- package/dist/src/ast/astDynamoDBParser.js +1 -0
- package/dist/src/ast/astExpressionEvaluator.d.ts +23 -0
- package/dist/src/ast/astExpressionEvaluator.js +1 -0
- package/dist/src/ast/astInfrastructureParser.d.ts +12 -49
- package/dist/src/ast/astInfrastructureParser.js +1 -552
- package/dist/src/ast/astMessagingParser.d.ts +5 -0
- package/dist/src/ast/astMessagingParser.js +1 -78
- package/dist/src/ast/astNetworkParser.d.ts +6 -0
- package/dist/src/ast/astNetworkParser.js +1 -219
- package/dist/src/ast/astPatternParser.d.ts +6 -0
- package/dist/src/ast/astPatternParser.js +1 -155
- package/dist/src/ast/astPlanConverter.d.ts +11 -0
- package/dist/src/ast/astPlanConverter.js +2 -0
- package/dist/src/ast/astStatementClassifier.d.ts +24 -0
- package/dist/src/ast/astStatementClassifier.js +1 -0
- package/dist/src/ast/astStatementQueries.d.ts +21 -0
- package/dist/src/ast/astStatementQueries.js +3 -0
- package/dist/src/ast/astStorageParser.d.ts +5 -0
- package/dist/src/ast/astStorageParser.js +1 -164
- package/dist/src/ast/astSurgicalModification.js +19 -400
- package/dist/src/ast/astTestHelpers.d.ts +635 -0
- package/dist/src/ast/astTestHelpers.js +1 -0
- package/dist/src/ast/index.d.ts +1 -0
- package/dist/src/ast/index.js +1 -6
- package/dist/src/aws/regions.js +1 -254
- package/dist/src/codemod/_internal.d.ts +12 -0
- package/dist/src/codemod/_internal.js +1 -0
- package/dist/src/codemod/edits/addResource/bodyIndex.d.ts +34 -0
- package/dist/src/codemod/edits/addResource/bodyIndex.js +1 -0
- package/dist/src/codemod/edits/addResource/propertyBuilder.d.ts +7 -0
- package/dist/src/codemod/edits/addResource/propertyBuilder.js +1 -0
- package/dist/src/codemod/edits/addResource.d.ts +9 -0
- package/dist/src/codemod/edits/addResource.js +1 -0
- package/dist/src/codemod/edits/ensureImports.d.ts +26 -0
- package/dist/src/codemod/edits/ensureImports.js +1 -0
- package/dist/src/codemod/edits/findInsertionPosition.d.ts +39 -0
- package/dist/src/codemod/edits/findInsertionPosition.js +1 -0
- package/dist/src/codemod/edits/index.d.ts +6 -0
- package/dist/src/codemod/edits/index.js +1 -0
- package/dist/src/codemod/edits/modifyResource/literalConversion.d.ts +37 -0
- package/dist/src/codemod/edits/modifyResource/literalConversion.js +1 -0
- package/dist/src/codemod/edits/modifyResource.d.ts +9 -0
- package/dist/src/codemod/edits/modifyResource.js +1 -0
- package/dist/src/codemod/edits/removeResource/commentHeuristic.d.ts +31 -0
- package/dist/src/codemod/edits/removeResource/commentHeuristic.js +1 -0
- package/dist/src/codemod/edits/removeResource/importPruning.d.ts +8 -0
- package/dist/src/codemod/edits/removeResource/importPruning.js +1 -0
- package/dist/src/codemod/edits/removeResource.d.ts +10 -0
- package/dist/src/codemod/edits/removeResource.js +1 -0
- package/dist/src/codemod/edits/schemaFragments.d.ts +9 -0
- package/dist/src/codemod/edits/schemaFragments.js +1 -0
- package/dist/src/codemod/fileRewriter/builders.d.ts +57 -0
- package/dist/src/codemod/fileRewriter/builders.js +1 -0
- package/dist/src/codemod/fileRewriter/index.d.ts +4 -0
- package/dist/src/codemod/fileRewriter/index.js +1 -0
- package/dist/src/codemod/fileRewriter/locateByRange.d.ts +65 -0
- package/dist/src/codemod/fileRewriter/locateByRange.js +1 -0
- package/dist/src/codemod/fileRewriter/parse.d.ts +18 -0
- package/dist/src/codemod/fileRewriter/parse.js +2 -0
- package/dist/src/codemod/fileRewriter/print.d.ts +46 -0
- package/dist/src/codemod/fileRewriter/print.js +4 -0
- package/dist/src/codemod/historyPaths.d.ts +2 -0
- package/dist/src/codemod/historyPaths.js +1 -0
- package/dist/src/codemod/index.d.ts +7 -0
- package/dist/src/codemod/index.js +1 -0
- package/dist/src/codemod/listResources.d.ts +4 -0
- package/dist/src/codemod/listResources.js +1 -0
- package/dist/src/codemod/semanticIndex/findReferences.d.ts +15 -0
- package/dist/src/codemod/semanticIndex/findReferences.js +2 -0
- package/dist/src/codemod/semanticIndex/index.d.ts +4 -0
- package/dist/src/codemod/semanticIndex/index.js +1 -0
- package/dist/src/codemod/semanticIndex/listImports.d.ts +24 -0
- package/dist/src/codemod/semanticIndex/listImports.js +1 -0
- package/dist/src/codemod/semanticIndex/locateByShape.d.ts +28 -0
- package/dist/src/codemod/semanticIndex/locateByShape.js +1 -0
- package/dist/src/codemod/semanticIndex/projectCache.d.ts +14 -0
- package/dist/src/codemod/semanticIndex/projectCache.js +1 -0
- package/dist/src/codemod/types.d.ts +172 -0
- package/dist/src/codemod/types.js +1 -0
- package/dist/src/dns/bindParser.js +2 -224
- package/dist/src/dns/bindWriter.js +3 -52
- package/dist/src/dns/domainFileGenerator.d.ts +20 -0
- package/dist/src/dns/domainFileGenerator.js +207 -0
- package/dist/src/dns/domainRecords.d.ts +164 -0
- package/dist/src/dns/domainRecords.js +1 -0
- package/dist/src/dns/index.d.ts +2 -1
- package/dist/src/dns/index.js +1 -4
- package/dist/src/dns/types.js +1 -52
- package/dist/src/generation/common.js +6 -161
- package/dist/src/generation/compute.js +82 -590
- package/dist/src/generation/database.js +12 -198
- package/dist/src/generation/generatePatternCode.d.ts +58 -0
- package/dist/src/generation/generatePatternCode.js +33 -0
- package/dist/src/generation/index.js +1 -20
- package/dist/src/generation/infrastructure.d.ts +1 -5
- package/dist/src/generation/infrastructure.js +35 -377
- package/dist/src/generation/messagingConnections.js +1 -73
- package/dist/src/generation/storage.d.ts +0 -15
- package/dist/src/generation/storage.js +35 -168
- package/dist/src/generation/storageConnections.js +1 -75
- package/dist/src/planning/generateResourceChange.d.ts +21 -0
- package/dist/src/planning/generateResourceChange.js +1 -0
- package/dist/src/planning/index.d.ts +3 -0
- package/dist/src/planning/index.js +1 -1
- package/dist/src/planning/resourceAddition.d.ts +154 -0
- package/dist/src/planning/resourceAddition.js +1 -0
- package/dist/src/planning/resourceConnections.d.ts +19 -0
- package/dist/src/planning/resourceConnections.js +1 -0
- package/dist/src/planning/resourcePlanning.js +1 -214
- package/dist/src/presets/index.js +1 -3
- package/dist/src/presets/patternTierPresets.js +1 -131
- package/dist/src/presets/storagePresets.js +1 -36
- package/dist/src/presets/tierPresets.d.ts +5 -8
- package/dist/src/presets/tierPresets.js +1 -384
- package/dist/src/presets/tierTypes.d.ts +1 -1
- package/dist/src/presets/tierTypes.js +0 -7
- package/dist/src/schemas/alarmSchemas.d.ts +19 -0
- package/dist/src/schemas/alarmSchemas.js +1 -0
- package/dist/src/schemas/applicationSchemas.d.ts +22 -6
- package/dist/src/schemas/applicationSchemas.js +1 -80
- package/dist/src/schemas/baseSchemas.d.ts +8 -3
- package/dist/src/schemas/baseSchemas.js +2 -248
- package/dist/src/schemas/cdnSchemas.js +1 -62
- package/dist/src/schemas/computeSchemas.d.ts +25 -3
- package/dist/src/schemas/computeSchemas.js +1 -727
- package/dist/src/schemas/constants.d.ts +5 -7
- package/dist/src/schemas/constants.js +1 -218
- package/dist/src/schemas/databaseSchemas.d.ts +6 -1
- package/dist/src/schemas/databaseSchemas.js +1 -366
- package/dist/src/schemas/index.js +1 -3
- package/dist/src/schemas/instanceTypeArchitecture.js +1 -75
- package/dist/src/schemas/messagingSchemas.js +1 -29
- package/dist/src/schemas/networkSchemas.js +1 -125
- package/dist/src/schemas/patternSchemas.d.ts +1 -1
- package/dist/src/schemas/patternSchemas.js +1 -294
- package/dist/src/schemas/resourceSchemas.d.ts +1 -0
- package/dist/src/schemas/resourceSchemas.js +1 -28
- package/dist/src/schemas/sharedTypes.d.ts +18 -0
- package/dist/src/schemas/sharedTypes.js +1 -0
- package/dist/src/schemas/storageSchemas.d.ts +1 -0
- package/dist/src/schemas/storageSchemas.js +1 -119
- package/dist/src/types/Result.js +1 -31
- package/dist/src/util/errorUtils.js +1 -1
- package/dist/src/validation/patterns.d.ts +9 -0
- package/dist/src/validation/patterns.js +1 -369
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +29 -9
- package/dist/src/dns/infrastructureWriter.d.ts +0 -2
- package/dist/src/dns/infrastructureWriter.js +0 -58
|
@@ -1,164 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { S3_STACK_PLACEMENTS, } from "../schemas/resourceSchemas.js";
|
|
3
|
-
import { S3_ENCRYPTION_TYPES, BACKUP_VAULT_TIERS, } from "../schemas/constants.js";
|
|
4
|
-
import { asBoolean, asNumber, asString, asStringUnion, captureExtraProperties, collectFromAst, extractVariableName, isFactoryBuildCall, isFactoryMethodCall, isParsedObject, parseObjectLiteral, } from "./astCommonParser.js";
|
|
5
|
-
const S3_BUCKET_CLASSES = new Set(["S3Bucket"]);
|
|
6
|
-
export { S3_BUCKET_CLASSES };
|
|
7
|
-
// ---- Extraction helpers ----
|
|
8
|
-
function extractS3Resource(varDecl, newExpr, bucketClass) {
|
|
9
|
-
const variableName = ts.isIdentifier(varDecl.name) ? varDecl.name.text : "";
|
|
10
|
-
if (newExpr.arguments && newExpr.arguments.length >= 2) {
|
|
11
|
-
const nameArg = newExpr.arguments[1];
|
|
12
|
-
const resourceName = ts.isStringLiteral(nameArg) ? nameArg.text : "";
|
|
13
|
-
const configArg = newExpr.arguments.length >= 3 ? newExpr.arguments[2] : undefined;
|
|
14
|
-
const config = configArg && ts.isObjectLiteralExpression(configArg)
|
|
15
|
-
? parseObjectLiteral(configArg)
|
|
16
|
-
: {};
|
|
17
|
-
return {
|
|
18
|
-
variableName,
|
|
19
|
-
resourceName,
|
|
20
|
-
bucketClass,
|
|
21
|
-
config,
|
|
22
|
-
node: varDecl,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
function extractS3FactoryResource(addStorageCall, buildCall) {
|
|
28
|
-
if (buildCall.arguments.length < 2)
|
|
29
|
-
return null;
|
|
30
|
-
const nameArg = buildCall.arguments[0];
|
|
31
|
-
const configArg = buildCall.arguments[1];
|
|
32
|
-
if (!ts.isStringLiteral(nameArg) || !ts.isObjectLiteralExpression(configArg))
|
|
33
|
-
return null;
|
|
34
|
-
const config = parseObjectLiteral(configArg);
|
|
35
|
-
const variableName = extractVariableName(addStorageCall) ?? "";
|
|
36
|
-
return {
|
|
37
|
-
variableName,
|
|
38
|
-
resourceName: nameArg.text,
|
|
39
|
-
bucketClass: "StorageFactory",
|
|
40
|
-
config,
|
|
41
|
-
node: addStorageCall,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
// ---- Public API ----
|
|
45
|
-
/** Find S3 resources created via new S3Bucket() constructor pattern */
|
|
46
|
-
export function findS3Resources(sourceFile) {
|
|
47
|
-
return collectFromAst(sourceFile, (node) => {
|
|
48
|
-
if (!ts.isVariableDeclaration(node) || !node.initializer)
|
|
49
|
-
return null;
|
|
50
|
-
if (!ts.isNewExpression(node.initializer))
|
|
51
|
-
return null;
|
|
52
|
-
const newExpr = node.initializer;
|
|
53
|
-
if (!ts.isIdentifier(newExpr.expression))
|
|
54
|
-
return null;
|
|
55
|
-
const bucketClass = newExpr.expression.text;
|
|
56
|
-
if (!S3_BUCKET_CLASSES.has(bucketClass))
|
|
57
|
-
return null;
|
|
58
|
-
return extractS3Resource(node, newExpr, bucketClass);
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
/** Find S3 resources created via app.addStorage(StorageFactory.build(...)) factory pattern */
|
|
62
|
-
export function findS3FactoryResources(sourceFile) {
|
|
63
|
-
return collectFromAst(sourceFile, (node) => {
|
|
64
|
-
if (!ts.isCallExpression(node) || !isFactoryMethodCall(node, "addStorage"))
|
|
65
|
-
return null;
|
|
66
|
-
const storageArg = node.arguments[0];
|
|
67
|
-
if (!isFactoryBuildCall(storageArg, "StorageFactory"))
|
|
68
|
-
return null;
|
|
69
|
-
return extractS3FactoryResource(node, storageArg);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
/** Convert parsed S3 resources to plan format */
|
|
73
|
-
export function convertS3Resources(s3Resources) {
|
|
74
|
-
return s3Resources.map((s3) => ({
|
|
75
|
-
name: s3.resourceName,
|
|
76
|
-
...(asString(s3.config.bucketName) && {
|
|
77
|
-
bucketName: asString(s3.config.bucketName),
|
|
78
|
-
}),
|
|
79
|
-
...(asBoolean(s3.config.publicReadAccess) === true && {
|
|
80
|
-
publicReadAccess: true,
|
|
81
|
-
}),
|
|
82
|
-
...(isParsedObject(s3.config.websiteHosting) &&
|
|
83
|
-
(() => {
|
|
84
|
-
const hosting = s3.config.websiteHosting;
|
|
85
|
-
return {
|
|
86
|
-
websiteHosting: {
|
|
87
|
-
indexDocument: asString(hosting.indexDocument) ?? "index.html",
|
|
88
|
-
...(asString(hosting.errorDocument) && {
|
|
89
|
-
errorDocument: asString(hosting.errorDocument),
|
|
90
|
-
}),
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
})()),
|
|
94
|
-
...(asStringUnion(s3.config.backupVaultTier, BACKUP_VAULT_TIERS) && {
|
|
95
|
-
backupVaultTier: asStringUnion(s3.config.backupVaultTier, BACKUP_VAULT_TIERS),
|
|
96
|
-
}),
|
|
97
|
-
...(asBoolean(s3.config.versioned) !== undefined && {
|
|
98
|
-
versioned: asBoolean(s3.config.versioned),
|
|
99
|
-
}),
|
|
100
|
-
...(asStringUnion(s3.config.encryption, S3_ENCRYPTION_TYPES) && {
|
|
101
|
-
encryption: asStringUnion(s3.config.encryption, S3_ENCRYPTION_TYPES),
|
|
102
|
-
}),
|
|
103
|
-
...(asString(s3.config.kmsKeyArn) && {
|
|
104
|
-
kmsKeyArn: asString(s3.config.kmsKeyArn),
|
|
105
|
-
}),
|
|
106
|
-
...(Array.isArray(s3.config.cors) &&
|
|
107
|
-
s3.config.cors.length > 0 && {
|
|
108
|
-
cors: s3.config.cors.filter(isParsedObject).map((rule) => ({
|
|
109
|
-
allowedOrigins: Array.isArray(rule.allowedOrigins)
|
|
110
|
-
? rule.allowedOrigins.filter((o) => typeof o === "string")
|
|
111
|
-
: [],
|
|
112
|
-
allowedMethods: Array.isArray(rule.allowedMethods)
|
|
113
|
-
? rule.allowedMethods.filter((m) => typeof m === "string")
|
|
114
|
-
: [],
|
|
115
|
-
})),
|
|
116
|
-
}),
|
|
117
|
-
...(isParsedObject(s3.config.deployment) &&
|
|
118
|
-
(() => {
|
|
119
|
-
const deployment = s3.config.deployment;
|
|
120
|
-
return {
|
|
121
|
-
deployment: {
|
|
122
|
-
source: asString(deployment.source) ?? "",
|
|
123
|
-
...(asBoolean(deployment.prune) !== undefined && {
|
|
124
|
-
prune: asBoolean(deployment.prune),
|
|
125
|
-
}),
|
|
126
|
-
...(isParsedObject(deployment.cacheControl) &&
|
|
127
|
-
(() => {
|
|
128
|
-
const cache = deployment.cacheControl;
|
|
129
|
-
return {
|
|
130
|
-
cacheControl: {
|
|
131
|
-
...(asNumber(cache.maxAge) !== undefined && {
|
|
132
|
-
maxAge: asNumber(cache.maxAge),
|
|
133
|
-
}),
|
|
134
|
-
...(asBoolean(cache.immutable) !== undefined && {
|
|
135
|
-
immutable: asBoolean(cache.immutable),
|
|
136
|
-
}),
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
})()),
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
})()),
|
|
143
|
-
...(asStringUnion(s3.config.stackPlacement, S3_STACK_PLACEMENTS) && {
|
|
144
|
-
stackPlacement: asStringUnion(s3.config.stackPlacement, S3_STACK_PLACEMENTS),
|
|
145
|
-
}),
|
|
146
|
-
...(s3.variableName && { variableName: s3.variableName }),
|
|
147
|
-
...(() => {
|
|
148
|
-
const S3_KNOWN_KEYS = new Set([
|
|
149
|
-
"bucketName",
|
|
150
|
-
"publicReadAccess",
|
|
151
|
-
"websiteHosting",
|
|
152
|
-
"backupVaultTier",
|
|
153
|
-
"versioned",
|
|
154
|
-
"encryption",
|
|
155
|
-
"kmsKeyArn",
|
|
156
|
-
"cors",
|
|
157
|
-
"deployment",
|
|
158
|
-
"stackPlacement",
|
|
159
|
-
]);
|
|
160
|
-
const extras = captureExtraProperties(s3.config, S3_KNOWN_KEYS);
|
|
161
|
-
return extras.length > 0 ? { extraProperties: extras } : {};
|
|
162
|
-
})(),
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
1
|
+
import*as i from"typescript";import{S3_STACK_PLACEMENTS as g}from"../schemas/resourceSchemas.js";import{S3_ENCRYPTION_TYPES as f,BACKUP_VAULT_TIERS as p}from"../schemas/constants.js";import{asBoolean as c,asNumber as d,asString as o,asStringUnion as a,captureExtraProperties as x,collectFromAst as b,extractVariableName as N,isFactoryBuildCall as k,isFactoryMethodCall as h,isParsedObject as u,parseObjectLiteral as y}from"./astCommonParser.js";const A=new Set(["S3Bucket"]);function C(n,e,r){const t=i.isIdentifier(n.name)?n.name.text:"";if(e.arguments&&e.arguments.length>=2){const s=e.arguments[1],m=i.isStringLiteral(s)?s.text:"",l=e.arguments.length>=3?e.arguments[2]:void 0,S=l&&i.isObjectLiteralExpression(l)?y(l):{};return{variableName:t,resourceName:m,bucketClass:r,config:S,node:n}}return null}function P(n,e){if(e.arguments.length<2)return null;const r=e.arguments[0],t=e.arguments[1];if(!i.isStringLiteral(r)||!i.isObjectLiteralExpression(t))return null;const s=y(t);return{variableName:N(n)??"",resourceName:r.text,bucketClass:"StorageFactory",config:s,node:n}}function R(n){return b(n,e=>{if(!i.isVariableDeclaration(e)||!e.initializer||!i.isNewExpression(e.initializer))return null;const r=e.initializer;if(!i.isIdentifier(r.expression))return null;const t=r.expression.text;return A.has(t)?C(e,r,t):null})}function _(n){return b(n,e=>{if(!i.isCallExpression(e)||!h(e,"addStorage"))return null;const r=e.arguments[0];return k(r,"StorageFactory")?P(e,r):null})}function v(n){return n.map(e=>({name:e.resourceName,...o(e.config.bucketName)&&{bucketName:o(e.config.bucketName)},...c(e.config.publicReadAccess)===!0&&{publicReadAccess:!0},...u(e.config.websiteHosting)&&(()=>{const r=e.config.websiteHosting;return{websiteHosting:{indexDocument:o(r.indexDocument)??"index.html",...o(r.errorDocument)&&{errorDocument:o(r.errorDocument)}}}})(),...a(e.config.backupVaultTier,p)&&{backupVaultTier:a(e.config.backupVaultTier,p)},...c(e.config.versioned)!==void 0&&{versioned:c(e.config.versioned)},...a(e.config.encryption,f)&&{encryption:a(e.config.encryption,f)},...o(e.config.kmsKeyArn)&&{kmsKeyArn:o(e.config.kmsKeyArn)},...Array.isArray(e.config.cors)&&e.config.cors.length>0&&{cors:e.config.cors.filter(u).map(r=>({allowedOrigins:Array.isArray(r.allowedOrigins)?r.allowedOrigins.filter(t=>typeof t=="string"):[],allowedMethods:Array.isArray(r.allowedMethods)?r.allowedMethods.filter(t=>typeof t=="string"):[]}))},...u(e.config.deployment)&&(()=>{const r=e.config.deployment;return{deployment:{source:o(r.source)??"",...c(r.prune)!==void 0&&{prune:c(r.prune)},...u(r.cacheControl)&&(()=>{const t=r.cacheControl;return{cacheControl:{...d(t.maxAge)!==void 0&&{maxAge:d(t.maxAge)},...c(t.immutable)!==void 0&&{immutable:c(t.immutable)}}}})()}}})(),...a(e.config.stackPlacement,g)&&{stackPlacement:a(e.config.stackPlacement,g)},...e.variableName&&{variableName:e.variableName},...(()=>{const r=new Set(["bucketName","publicReadAccess","websiteHosting","backupVaultTier","versioned","encryption","kmsKeyArn","cors","deployment","stackPlacement"]),t=x(e.config,r);return t.length>0?{extraProperties:t}:{}})()}))}export{A as S3_BUCKET_CLASSES,v as convertS3Resources,_ as findS3FactoryResources,R as findS3Resources};
|
|
@@ -1,400 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (!beforeText.endsWith("\n")) {
|
|
21
|
-
return "\n\n";
|
|
22
|
-
}
|
|
23
|
-
return "\n";
|
|
24
|
-
}
|
|
25
|
-
function getContextBeforePosition(content, position) {
|
|
26
|
-
return content.slice(Math.max(0, position - 2), position);
|
|
27
|
-
}
|
|
28
|
-
// getErrorMessage is now imported from errorUtils.ts
|
|
29
|
-
function formatResourceId(type, name) {
|
|
30
|
-
return name ? `${type}:${name}` : type;
|
|
31
|
-
}
|
|
32
|
-
function createErrorResult(error, content) {
|
|
33
|
-
return { content, success: false, error: getErrorMessage(error) };
|
|
34
|
-
}
|
|
35
|
-
function findResourceOrFail(sourceFile, content, resourceType, resourceName) {
|
|
36
|
-
const resourcePos = findManagedResourcePosition(sourceFile, resourceType, resourceName);
|
|
37
|
-
if (!resourcePos) {
|
|
38
|
-
return createErrorResult(new Error(`Resource not found: ${formatResourceId(resourceType, resourceName)}`), content);
|
|
39
|
-
}
|
|
40
|
-
return resourcePos;
|
|
41
|
-
}
|
|
42
|
-
function isFailureResult(result) {
|
|
43
|
-
return "success" in result && !result.success;
|
|
44
|
-
}
|
|
45
|
-
export function addResourceSurgically(content, options) {
|
|
46
|
-
const { resourceType, code, afterResource } = options;
|
|
47
|
-
try {
|
|
48
|
-
const sourceFile = parseSourceFile(content);
|
|
49
|
-
let insertPosition;
|
|
50
|
-
if (afterResource) {
|
|
51
|
-
const afterPos = findManagedResourcePosition(sourceFile, afterResource.type, afterResource.name);
|
|
52
|
-
if (!afterPos) {
|
|
53
|
-
return createErrorResult(new Error(`Insertion target not found: ${formatResourceId(afterResource.type, afterResource.name)}`), content);
|
|
54
|
-
}
|
|
55
|
-
insertPosition = afterPos.endPos;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
insertPosition = findInsertionPositionByType(sourceFile, resourceType);
|
|
59
|
-
}
|
|
60
|
-
const codeToInsert = formatCodeForInsertion(content, insertPosition, code);
|
|
61
|
-
const newContent = insertAtPosition(content, insertPosition, codeToInsert);
|
|
62
|
-
return {
|
|
63
|
-
content: newContent,
|
|
64
|
-
success: true,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
return createErrorResult(error, content);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Find the best insertion position for a resource type.
|
|
73
|
-
* Resources are ordered: imports > app-init > tags > database > storage > compute > network > pattern
|
|
74
|
-
*/
|
|
75
|
-
function findInsertionPositionByType(sourceFile, resourceType) {
|
|
76
|
-
const classifications = classifyStatements(sourceFile);
|
|
77
|
-
// Define ordering for resource types
|
|
78
|
-
const typeOrder = [
|
|
79
|
-
"import",
|
|
80
|
-
"app-init",
|
|
81
|
-
"tags",
|
|
82
|
-
"database",
|
|
83
|
-
"storage",
|
|
84
|
-
"messaging",
|
|
85
|
-
"compute",
|
|
86
|
-
"network",
|
|
87
|
-
"cdn",
|
|
88
|
-
"pattern",
|
|
89
|
-
];
|
|
90
|
-
const targetIndex = typeOrder.indexOf(resourceType);
|
|
91
|
-
// Find the last statement of the same type or earlier types
|
|
92
|
-
let lastRelevantStatement = null;
|
|
93
|
-
for (const classification of classifications) {
|
|
94
|
-
if (classification.isManaged) {
|
|
95
|
-
const classIndex = typeOrder.indexOf(classification.type);
|
|
96
|
-
if (classIndex !== -1 && classIndex <= targetIndex) {
|
|
97
|
-
lastRelevantStatement = classification;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (lastRelevantStatement) {
|
|
102
|
-
return lastRelevantStatement.endPos;
|
|
103
|
-
}
|
|
104
|
-
// Fall back to end of file
|
|
105
|
-
return sourceFile.getEnd();
|
|
106
|
-
}
|
|
107
|
-
function formatCodeForInsertion(content, position, code) {
|
|
108
|
-
const beforeText = getContextBeforePosition(content, position);
|
|
109
|
-
const afterText = content.slice(position, Math.min(content.length, position + 2));
|
|
110
|
-
const leadingNewlines = formatLeadingNewlines(beforeText, code);
|
|
111
|
-
const trailingNewlines = afterText.startsWith("\n") ? "" : "\n";
|
|
112
|
-
return leadingNewlines + code + trailingNewlines;
|
|
113
|
-
}
|
|
114
|
-
export function updateResourceSurgically(content, options) {
|
|
115
|
-
const { resourceType, resourceName, newCode } = options;
|
|
116
|
-
try {
|
|
117
|
-
const sourceFile = parseSourceFile(content);
|
|
118
|
-
const result = findResourceOrFail(sourceFile, content, resourceType, resourceName);
|
|
119
|
-
if (isFailureResult(result))
|
|
120
|
-
return result;
|
|
121
|
-
const resourcePos = result;
|
|
122
|
-
// getFullStart() includes leading comments/whitespace
|
|
123
|
-
const fullStart = resourcePos.node.getFullStart();
|
|
124
|
-
const end = resourcePos.endPos;
|
|
125
|
-
// Preserve any leading whitespace from the original
|
|
126
|
-
const originalLeading = content.slice(fullStart, resourcePos.node.getStart());
|
|
127
|
-
const leadingWhitespace = extractLeadingWhitespace(originalLeading);
|
|
128
|
-
const newContent = content.slice(0, fullStart) +
|
|
129
|
-
leadingWhitespace +
|
|
130
|
-
newCode +
|
|
131
|
-
content.slice(end);
|
|
132
|
-
return {
|
|
133
|
-
content: newContent,
|
|
134
|
-
success: true,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
return createErrorResult(error, content);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function extractLeadingWhitespace(text) {
|
|
142
|
-
const match = text.match(/^[\s]*[\n\r]+[\s]*/);
|
|
143
|
-
return match ? match[0] : "";
|
|
144
|
-
}
|
|
145
|
-
// Track search offset to avoid matching the same location twice when
|
|
146
|
-
// similar text appears in multiple places.
|
|
147
|
-
function insertOrphanMarkers(content, blocks, resourceType, resourceName) {
|
|
148
|
-
let result = content;
|
|
149
|
-
let searchFrom = 0;
|
|
150
|
-
for (const block of blocks) {
|
|
151
|
-
const orphanComment = `// [ORPHANED: was after ${formatResourceId(resourceType, resourceName)}]\n`;
|
|
152
|
-
const trimmedText = block.sourceText.trim();
|
|
153
|
-
const blockPos = result.indexOf(trimmedText, searchFrom);
|
|
154
|
-
if (blockPos !== -1) {
|
|
155
|
-
result = insertAtPosition(result, blockPos, orphanComment);
|
|
156
|
-
searchFrom = blockPos + orphanComment.length + trimmedText.length;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return result;
|
|
160
|
-
}
|
|
161
|
-
export function removeResourceSurgically(content, options) {
|
|
162
|
-
const { resourceType, resourceName, orphanHandling } = options;
|
|
163
|
-
const warnings = [];
|
|
164
|
-
try {
|
|
165
|
-
const sourceFile = parseSourceFile(content);
|
|
166
|
-
const result = findResourceOrFail(sourceFile, content, resourceType, resourceName);
|
|
167
|
-
if (isFailureResult(result))
|
|
168
|
-
return result;
|
|
169
|
-
const resourcePos = result;
|
|
170
|
-
// Check for custom code blocks that reference this resource
|
|
171
|
-
const parsed = parseInfrastructure(content, { extractCustomCode: true });
|
|
172
|
-
const affectedCustomBlocks = (parsed.customCodeBlocks ?? []).filter((block) => block.position === "after-resource" &&
|
|
173
|
-
block.afterManagedResource?.type === resourceType &&
|
|
174
|
-
block.afterManagedResource?.name === resourceName);
|
|
175
|
-
if (affectedCustomBlocks.length > 0) {
|
|
176
|
-
if (orphanHandling === "preserve-with-warning") {
|
|
177
|
-
warnings.push(`Custom code after ${formatResourceId(resourceType, resourceName)} will be orphaned. ` +
|
|
178
|
-
`Added // [ORPHANED] marker comment.`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Get the removal range including leading whitespace
|
|
182
|
-
const fullStart = resourcePos.node.getFullStart();
|
|
183
|
-
const end = resourcePos.endPos;
|
|
184
|
-
// Check if there's a trailing newline to clean up
|
|
185
|
-
const removeEnd = content[end] === "\n" ? end + 1 : end;
|
|
186
|
-
let newContent = content.slice(0, fullStart) + content.slice(removeEnd);
|
|
187
|
-
if (affectedCustomBlocks.length > 0 &&
|
|
188
|
-
orphanHandling === "preserve-with-warning") {
|
|
189
|
-
newContent = insertOrphanMarkers(newContent, affectedCustomBlocks, resourceType, resourceName);
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
content: newContent,
|
|
193
|
-
success: true,
|
|
194
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
return createErrorResult(error, content);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
export function ensureImports(content, requiredImports) {
|
|
202
|
-
try {
|
|
203
|
-
const sourceFile = parseSourceFile(content);
|
|
204
|
-
const existingImports = new Set();
|
|
205
|
-
for (const statement of sourceFile.statements) {
|
|
206
|
-
if (ts.isImportDeclaration(statement)) {
|
|
207
|
-
existingImports.add(statement.getText(sourceFile).trim());
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
const importsToAdd = [];
|
|
211
|
-
for (const importStmt of requiredImports) {
|
|
212
|
-
const normalisedImport = importStmt.trim();
|
|
213
|
-
const exists = [...existingImports].some((existing) => importsAreEquivalent(existing, normalisedImport));
|
|
214
|
-
if (!exists) {
|
|
215
|
-
importsToAdd.push(normalisedImport);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (importsToAdd.length === 0) {
|
|
219
|
-
return {
|
|
220
|
-
content,
|
|
221
|
-
success: true,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
const lastImportEnd = sourceFile.statements.filter(ts.isImportDeclaration).at(-1)?.getEnd() ??
|
|
225
|
-
0;
|
|
226
|
-
const importText = "\n" + importsToAdd.join("\n");
|
|
227
|
-
const newContent = insertAtPosition(content, lastImportEnd, importText);
|
|
228
|
-
return {
|
|
229
|
-
content: newContent,
|
|
230
|
-
success: true,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
return createErrorResult(error, content);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
function parseNamedImports(raw) {
|
|
238
|
-
return new Set(raw
|
|
239
|
-
.split(",")
|
|
240
|
-
.map((s) => s.trim())
|
|
241
|
-
.filter(Boolean));
|
|
242
|
-
}
|
|
243
|
-
/** Existing import (import1) is a superset of or equal to the required one (import2). */
|
|
244
|
-
function existingImportCoversRequired(existingRaw, requiredRaw) {
|
|
245
|
-
const existing = parseNamedImports(existingRaw);
|
|
246
|
-
const required = parseNamedImports(requiredRaw);
|
|
247
|
-
for (const name of required) {
|
|
248
|
-
if (!existing.has(name))
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Check if two import statements are equivalent (same module, overlapping named imports).
|
|
255
|
-
*/
|
|
256
|
-
function importsAreEquivalent(import1, import2) {
|
|
257
|
-
const moduleMatch1 = import1.match(FROM_MODULE_REGEX);
|
|
258
|
-
const moduleMatch2 = import2.match(FROM_MODULE_REGEX);
|
|
259
|
-
if (!moduleMatch1 || !moduleMatch2) {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
if (moduleMatch1[1] !== moduleMatch2[1]) {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
// Same module - compare named import bindings when both use them
|
|
266
|
-
const namedMatch1 = import1.match(NAMED_IMPORTS_REGEX);
|
|
267
|
-
const namedMatch2 = import2.match(NAMED_IMPORTS_REGEX);
|
|
268
|
-
if (namedMatch1 && namedMatch2) {
|
|
269
|
-
return existingImportCoversRequired(namedMatch1[1], namedMatch2[1]);
|
|
270
|
-
}
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
const FROM_MODULE_REGEX = /from\s+["']([^"']+)["']/;
|
|
274
|
-
const NAMED_IMPORTS_REGEX = /\{([^}]+)\}/;
|
|
275
|
-
const POSITION_ORDER = {
|
|
276
|
-
"before-imports": 0,
|
|
277
|
-
"after-imports": 1,
|
|
278
|
-
"after-app-init": 2,
|
|
279
|
-
"after-tags": 3,
|
|
280
|
-
"after-resource": 4,
|
|
281
|
-
"end-of-file": 5,
|
|
282
|
-
};
|
|
283
|
-
function sortCustomBlocksForInsertion(blocks) {
|
|
284
|
-
return [...blocks].sort((a, b) => {
|
|
285
|
-
const positionDiff = POSITION_ORDER[b.position] - POSITION_ORDER[a.position];
|
|
286
|
-
if (positionDiff !== 0)
|
|
287
|
-
return positionDiff;
|
|
288
|
-
return (b.originalLine ?? 0) - (a.originalLine ?? 0);
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
function insertOrOrphanBlock(content, block, insertPos, warnings) {
|
|
292
|
-
if (insertPos !== null) {
|
|
293
|
-
const codeToInsert = formatCustomCodeForInsertion(content, insertPos, block);
|
|
294
|
-
return insertAtPosition(content, insertPos, codeToInsert);
|
|
295
|
-
}
|
|
296
|
-
const orphanComment = block.afterManagedResource
|
|
297
|
-
? `// [ORPHANED: was after ${formatResourceId(block.afterManagedResource.type, block.afterManagedResource.name)}]\n`
|
|
298
|
-
: "// [ORPHANED]\n";
|
|
299
|
-
warnings.push(`Custom code from line ${block.originalLine} could not be positioned. Added at end of file.`);
|
|
300
|
-
return content + "\n" + orphanComment + block.sourceText;
|
|
301
|
-
}
|
|
302
|
-
export function injectCustomCodeBlocks(generatedCode, customBlocks, resourceMapping) {
|
|
303
|
-
if (!customBlocks || customBlocks.length === 0) {
|
|
304
|
-
return { content: generatedCode, success: true };
|
|
305
|
-
}
|
|
306
|
-
try {
|
|
307
|
-
const sourceFile = parseSourceFile(generatedCode);
|
|
308
|
-
const classifications = classifyStatements(sourceFile);
|
|
309
|
-
const warnings = [];
|
|
310
|
-
const processedBlocks = sortCustomBlocksForInsertion(customBlocks);
|
|
311
|
-
let result = generatedCode;
|
|
312
|
-
for (const block of processedBlocks) {
|
|
313
|
-
const insertPos = findCustomCodeInsertPosition(sourceFile, classifications, block, resourceMapping, warnings);
|
|
314
|
-
result = insertOrOrphanBlock(result, block, insertPos, warnings);
|
|
315
|
-
}
|
|
316
|
-
return {
|
|
317
|
-
content: result,
|
|
318
|
-
success: true,
|
|
319
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
catch (error) {
|
|
323
|
-
return createErrorResult(error, generatedCode);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
function findAfterResourcePosition(classifications, block, resourceMapping, warnings) {
|
|
327
|
-
if (!block.afterManagedResource)
|
|
328
|
-
return null;
|
|
329
|
-
const afterResource = block.afterManagedResource;
|
|
330
|
-
const targetName = (afterResource.name && resourceMapping?.get(afterResource.name)) ??
|
|
331
|
-
afterResource.name;
|
|
332
|
-
const resource = classifications.find((c) => c.type === afterResource.type && c.resourceName === targetName);
|
|
333
|
-
if (resource)
|
|
334
|
-
return resource.endPos;
|
|
335
|
-
// Fallback: if exactly one resource of this type, use it
|
|
336
|
-
const typeMatches = classifications.filter((c) => c.type === afterResource.type && c.isManaged);
|
|
337
|
-
if (typeMatches.length === 1)
|
|
338
|
-
return typeMatches[0].endPos;
|
|
339
|
-
if (warnings) {
|
|
340
|
-
warnings.push(`Resource ${formatResourceId(block.afterManagedResource.type, block.afterManagedResource.name)} not found. ` +
|
|
341
|
-
`Custom code may be orphaned.`);
|
|
342
|
-
}
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
function findCustomCodeInsertPosition(sourceFile, classifications, block, resourceMapping, warnings) {
|
|
346
|
-
switch (block.position) {
|
|
347
|
-
case "before-imports":
|
|
348
|
-
return 0;
|
|
349
|
-
case "after-imports":
|
|
350
|
-
return findLastClassification(classifications, "import")?.endPos ?? 0;
|
|
351
|
-
case "after-app-init":
|
|
352
|
-
return findClassification(classifications, "app-init")?.endPos ?? null;
|
|
353
|
-
case "after-tags":
|
|
354
|
-
return findClassification(classifications, "tags")?.endPos ?? null;
|
|
355
|
-
case "after-resource":
|
|
356
|
-
return findAfterResourcePosition(classifications, block, resourceMapping, warnings);
|
|
357
|
-
case "end-of-file":
|
|
358
|
-
return sourceFile.getEnd();
|
|
359
|
-
default:
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function formatCustomCodeForInsertion(content, position, block) {
|
|
364
|
-
const beforeText = getContextBeforePosition(content, position);
|
|
365
|
-
let code = block.sourceText;
|
|
366
|
-
const leadingNewlines = formatLeadingNewlines(beforeText, code);
|
|
367
|
-
code = leadingNewlines + code;
|
|
368
|
-
if (!code.endsWith("\n")) {
|
|
369
|
-
code += "\n";
|
|
370
|
-
}
|
|
371
|
-
return code;
|
|
372
|
-
}
|
|
373
|
-
export function validateModifiedFile(content) {
|
|
374
|
-
try {
|
|
375
|
-
const sourceFile = parseSourceFile(content);
|
|
376
|
-
// ts.createSourceFile doesn't fully validate, but it catches major syntax errors
|
|
377
|
-
const diagnostics = [];
|
|
378
|
-
// Walk the tree looking for problematic nodes
|
|
379
|
-
function visit(node) {
|
|
380
|
-
if (node.kind === ts.SyntaxKind.Unknown) {
|
|
381
|
-
diagnostics.push("Unknown node found in AST - possible parse error");
|
|
382
|
-
}
|
|
383
|
-
ts.forEachChild(node, visit);
|
|
384
|
-
}
|
|
385
|
-
visit(sourceFile);
|
|
386
|
-
if (diagnostics.length > 0) {
|
|
387
|
-
return {
|
|
388
|
-
valid: false,
|
|
389
|
-
errors: diagnostics,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
return { valid: true };
|
|
393
|
-
}
|
|
394
|
-
catch (error) {
|
|
395
|
-
return {
|
|
396
|
-
valid: false,
|
|
397
|
-
errors: [getErrorMessage(error)],
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
}
|
|
1
|
+
import*as f from"typescript";import{parseInfrastructure as D,classifyStatements as y,findManagedResourcePosition as C}from"./astInfrastructureParser.js";import{getErrorMessage as M}from"../util/errorUtils.js";function R(e,t,r){return e.slice(0,t)+r+e.slice(t)}function P(e,t){return e.find(r=>r.type===t)}function L(e,t){return e.filter(r=>r.type===t).pop()}function m(e){return f.createSourceFile("infrastructure.ts",e,f.ScriptTarget.Latest,!0,f.ScriptKind.TS)}function I(e,t){return e.endsWith(`
|
|
2
|
+
|
|
3
|
+
`)||t.startsWith(`
|
|
4
|
+
`)?"":e.endsWith(`
|
|
5
|
+
`)?`
|
|
6
|
+
`:`
|
|
7
|
+
|
|
8
|
+
`}function E(e,t){return e.slice(Math.max(0,t-2),t)}function p(e,t){return t?`${e}:${t}`:e}function l(e,t){return{content:t,success:!1,error:M(e)}}function S(e,t,r,s){const n=C(e,r,s);return n||l(new Error(`Resource not found: ${p(r,s)}`),t)}function T(e){return"success"in e&&!e.success}function J(e,t){const{resourceType:r,code:s,afterResource:n}=t;try{const o=m(e);let i;if(n){const u=C(o,n.type,n.name);if(!u)return l(new Error(`Insertion target not found: ${p(n.type,n.name)}`),e);i=u.endPos}else i=$(o,r);const c=B(e,i,s);return{content:R(e,i,c),success:!0}}catch(o){return l(o,e)}}function $(e,t){const r=y(e),s=["import","app-init","tags","database","storage","messaging","compute","network","cdn","pattern"],n=s.indexOf(t);let o=null;for(const i of r)if(i.isManaged){const c=s.indexOf(i.type);c!==-1&&c<=n&&(o=i)}return o?o.endPos:e.getEnd()}function B(e,t,r){const s=E(e,t),n=e.slice(t,Math.min(e.length,t+2)),o=I(s,r),i=n.startsWith(`
|
|
9
|
+
`)?"":`
|
|
10
|
+
`;return o+r+i}function Q(e,t){const{resourceType:r,resourceName:s,newCode:n}=t;try{const o=m(e),i=S(o,e,r,s);if(T(i))return i;const c=i,a=c.node.getFullStart(),u=c.endPos,d=e.slice(a,c.node.getStart()),g=W(d);return{content:e.slice(0,a)+g+n+e.slice(u),success:!0}}catch(o){return l(o,e)}}function W(e){const t=e.match(/^[\s]*[\n\r]+[\s]*/);return t?t[0]:""}function H(e,t,r,s){let n=e,o=0;for(const i of t){const c=`// [ORPHANED: was after ${p(r,s)}]
|
|
11
|
+
`,a=i.sourceText.trim(),u=n.indexOf(a,o);u!==-1&&(n=R(n,u,c),o=u+c.length+a.length)}return n}function V(e,t){const{resourceType:r,resourceName:s,orphanHandling:n}=t,o=[];try{const i=m(e),c=S(i,e,r,s);if(T(c))return c;const a=c,d=(D(e,{extractCustomCode:!0}).customCodeBlocks??[]).filter(x=>x.position==="after-resource"&&x.afterManagedResource?.type===r&&x.afterManagedResource?.name===s);d.length>0&&n==="preserve-with-warning"&&o.push(`Custom code after ${p(r,s)} will be orphaned. Added // [ORPHANED] marker comment.`);const g=a.node.getFullStart(),h=a.endPos,A=e[h]===`
|
|
12
|
+
`?h+1:h;let w=e.slice(0,g)+e.slice(A);return d.length>0&&n==="preserve-with-warning"&&(w=H(w,d,r,s)),{content:w,success:!0,warnings:o.length>0?o:void 0}}catch(i){return l(i,e)}}function Y(e,t){try{const r=m(e),s=new Set;for(const a of r.statements)f.isImportDeclaration(a)&&s.add(a.getText(r).trim());const n=[];for(const a of t){const u=a.trim();[...s].some(g=>q(g,u))||n.push(u)}if(n.length===0)return{content:e,success:!0};const o=r.statements.filter(f.isImportDeclaration).at(-1)?.getEnd()??0,i=`
|
|
13
|
+
`+n.join(`
|
|
14
|
+
`);return{content:R(e,o,i),success:!0}}catch(r){return l(r,e)}}function O(e){return new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function _(e,t){const r=O(e),s=O(t);for(const n of s)if(!r.has(n))return!1;return!0}function q(e,t){const r=e.match(F),s=t.match(F);if(!r||!s||r[1]!==s[1])return!1;const n=e.match(N),o=t.match(N);return n&&o?_(n[1],o[1]):!0}const F=/from\s+["']([^"']+)["']/,N=/\{([^}]+)\}/,v={"before-imports":0,"after-imports":1,"after-app-init":2,"after-tags":3,"after-resource":4,"end-of-file":5};function U(e){return[...e].sort((t,r)=>{const s=v[r.position]-v[t.position];return s!==0?s:(r.originalLine??0)-(t.originalLine??0)})}function j(e,t,r,s){if(r!==null){const o=K(e,r,t);return R(e,r,o)}const n=t.afterManagedResource?`// [ORPHANED: was after ${p(t.afterManagedResource.type,t.afterManagedResource.name)}]
|
|
15
|
+
`:`// [ORPHANED]
|
|
16
|
+
`;return s.push(`Custom code from line ${t.originalLine} could not be positioned. Added at end of file.`),e+`
|
|
17
|
+
`+n+t.sourceText}function Z(e,t,r){if(!t||t.length===0)return{content:e,success:!0};try{const s=m(e),n=y(s),o=[],i=U(t);let c=e;for(const a of i){const u=G(s,n,a,r,o);c=j(c,a,u,o)}return{content:c,success:!0,warnings:o.length>0?o:void 0}}catch(s){return l(s,e)}}function k(e,t,r,s){if(!t.afterManagedResource)return null;const n=t.afterManagedResource,o=(n.name&&r?.get(n.name))??n.name,i=e.find(a=>a.type===n.type&&a.resourceName===o);if(i)return i.endPos;const c=e.filter(a=>a.type===n.type&&a.isManaged);return c.length===1?c[0].endPos:(s&&s.push(`Resource ${p(t.afterManagedResource.type,t.afterManagedResource.name)} not found. Custom code may be orphaned.`),null)}function G(e,t,r,s,n){switch(r.position){case"before-imports":return 0;case"after-imports":return L(t,"import")?.endPos??0;case"after-app-init":return P(t,"app-init")?.endPos??null;case"after-tags":return P(t,"tags")?.endPos??null;case"after-resource":return k(t,r,s,n);case"end-of-file":return e.getEnd();default:return null}}function K(e,t,r){const s=E(e,t);let n=r.sourceText;return n=I(s,n)+n,n.endsWith(`
|
|
18
|
+
`)||(n+=`
|
|
19
|
+
`),n}function b(e){try{let s=function(n){n.kind===f.SyntaxKind.Unknown&&r.push("Unknown node found in AST - possible parse error"),f.forEachChild(n,s)};const t=m(e),r=[];return s(t),r.length>0?{valid:!1,errors:r}:{valid:!0}}catch(t){return{valid:!1,errors:[M(t)]}}}export{J as addResourceSurgically,Y as ensureImports,Z as injectCustomCodeBlocks,V as removeResourceSurgically,Q as updateResourceSurgically,b as validateModifiedFile};
|