fjall 0.89.6 → 0.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/bin/.bundled +3 -0
  2. package/bin/.metafile.json +39155 -0
  3. package/bin/assets/generators/account/files/infrastructure.ts +17 -0
  4. package/bin/assets/generators/account/generator.d.ts +8 -0
  5. package/bin/assets/generators/account/generator.js +2 -0
  6. package/bin/assets/generators/account/index.d.ts +1 -0
  7. package/bin/assets/generators/account/index.js +1 -0
  8. package/bin/assets/generators/application/generator.d.ts +4 -0
  9. package/bin/assets/generators/application/generator.js +28 -0
  10. package/bin/assets/generators/application/index.d.ts +1 -0
  11. package/bin/assets/generators/application/index.js +1 -0
  12. package/bin/assets/generators/cdn/generator.d.ts +4 -0
  13. package/bin/assets/generators/cdn/generator.js +4 -0
  14. package/bin/assets/generators/cdn/index.d.ts +1 -0
  15. package/bin/assets/generators/cdn/index.js +1 -0
  16. package/bin/assets/generators/compute/computeIntegrationHelpers.d.ts +134 -0
  17. package/bin/assets/generators/compute/computeIntegrationHelpers.js +1 -0
  18. package/bin/assets/generators/compute/generator.d.ts +4 -0
  19. package/bin/assets/generators/compute/generator.js +1 -0
  20. package/bin/assets/generators/compute/index.d.ts +1 -0
  21. package/bin/assets/generators/compute/index.js +1 -0
  22. package/bin/assets/generators/compute/service/generator.d.ts +4 -0
  23. package/bin/assets/generators/compute/service/generator.js +1 -0
  24. package/bin/assets/generators/compute/service/index.d.ts +1 -0
  25. package/bin/assets/generators/compute/service/index.js +1 -0
  26. package/bin/assets/generators/database/databaseIntegrationTestUtils.d.ts +134 -0
  27. package/bin/assets/generators/database/databaseIntegrationTestUtils.js +1 -0
  28. package/bin/assets/generators/database/generator.d.ts +4 -0
  29. package/bin/assets/generators/database/generator.js +1 -0
  30. package/bin/assets/generators/database/index.d.ts +1 -0
  31. package/bin/assets/generators/database/index.js +1 -0
  32. package/bin/assets/generators/database/proxy/generator.d.ts +4 -0
  33. package/bin/assets/generators/database/proxy/generator.js +1 -0
  34. package/bin/assets/generators/database/proxy/index.d.ts +1 -0
  35. package/bin/assets/generators/database/proxy/index.js +1 -0
  36. package/bin/assets/generators/domain/files/infrastructure.ts.ejs +22 -0
  37. package/bin/assets/generators/domain/files/zone.bind.ejs +14 -0
  38. package/bin/assets/generators/domain/generator.d.ts +10 -0
  39. package/bin/assets/generators/domain/generator.js +2 -0
  40. package/bin/assets/generators/domain/index.d.ts +1 -0
  41. package/bin/assets/generators/domain/index.js +1 -0
  42. package/bin/assets/generators/messaging/index.d.ts +1 -0
  43. package/bin/assets/generators/messaging/index.js +0 -0
  44. package/bin/assets/generators/network/generator.d.ts +4 -0
  45. package/bin/assets/generators/network/generator.js +1 -0
  46. package/bin/assets/generators/network/index.d.ts +1 -0
  47. package/bin/assets/generators/network/index.js +1 -0
  48. package/bin/assets/generators/organisation/files/account/infrastructure.ts +25 -0
  49. package/bin/assets/generators/organisation/files/organisation/infrastructure.ts +46 -0
  50. package/bin/assets/generators/organisation/files/platform/infrastructure.ts +16 -0
  51. package/bin/assets/generators/organisation/generator.d.ts +4 -0
  52. package/bin/assets/generators/organisation/generator.js +2 -0
  53. package/bin/assets/generators/organisation/index.d.ts +1 -0
  54. package/bin/assets/generators/organisation/index.js +1 -0
  55. package/bin/assets/generators/shared/files/cdk.context.json +3 -0
  56. package/bin/assets/generators/shared/files/cdk.json +55 -0
  57. package/bin/assets/generators/shared/files/package.json +15 -0
  58. package/bin/assets/generators/shared/files/tsconfig.json +23 -0
  59. package/bin/assets/generators/storage/index.d.ts +1 -0
  60. package/bin/assets/generators/storage/index.js +1 -0
  61. package/bin/assets/generators/storage/s3/generator.d.ts +4 -0
  62. package/bin/assets/generators/storage/s3/generator.js +1 -0
  63. package/bin/assets/generators/tunnel/generator.d.ts +4 -0
  64. package/bin/assets/generators/tunnel/generator.js +1 -0
  65. package/bin/assets/generators/tunnel/index.d.ts +1 -0
  66. package/bin/assets/generators/tunnel/index.js +1 -0
  67. package/bin/assets/generators/utils/ast/astCodeModification.d.ts +23 -0
  68. package/bin/assets/generators/utils/ast/astCodeModification.js +1 -0
  69. package/bin/assets/generators/utils/ast/astDomainRewriter.d.ts +65 -0
  70. package/bin/assets/generators/utils/ast/astDomainRewriter.js +1 -0
  71. package/bin/assets/generators/utils/ast/astSurgicalModification.d.ts +2 -0
  72. package/bin/assets/generators/utils/ast/astSurgicalModification.js +1 -0
  73. package/bin/assets/generators/utils/ast/index.d.ts +3 -0
  74. package/bin/assets/generators/utils/ast/index.js +1 -0
  75. package/bin/assets/generators/utils/copySharedFiles.d.ts +10 -0
  76. package/bin/assets/generators/utils/copySharedFiles.js +1 -0
  77. package/bin/assets/generators/utils/index.d.ts +8 -0
  78. package/bin/assets/generators/utils/index.js +1 -0
  79. package/bin/assets/generators/utils/integrationTestUtils.d.ts +129 -0
  80. package/bin/assets/generators/utils/integrationTestUtils.js +2 -0
  81. package/bin/assets/generators/utils/mockTree.d.ts +30 -0
  82. package/bin/assets/generators/utils/mockTree.js +1 -0
  83. package/bin/assets/generators/utils/planning/generatorHelpers.d.ts +35 -0
  84. package/bin/assets/generators/utils/planning/generatorHelpers.js +2 -0
  85. package/bin/assets/generators/utils/planning/index.d.ts +1 -0
  86. package/bin/assets/generators/utils/planning/index.js +1 -0
  87. package/bin/assets/generators/utils/prompts.d.ts +4 -0
  88. package/bin/assets/generators/utils/prompts.js +6 -0
  89. package/bin/assets/generators/utils/renderEjs.d.ts +24 -0
  90. package/bin/assets/generators/utils/renderEjs.js +1 -0
  91. package/bin/assets/generators/utils/resources/connectionHelpers.d.ts +19 -0
  92. package/bin/assets/generators/utils/resources/connectionHelpers.js +1 -0
  93. package/bin/assets/generators/utils/resources/index.d.ts +4 -0
  94. package/bin/assets/generators/utils/resources/index.js +1 -0
  95. package/bin/assets/generators/utils/resources/promptValidation.d.ts +42 -0
  96. package/bin/assets/generators/utils/resources/promptValidation.js +1 -0
  97. package/bin/assets/generators/utils/resources/resourceDetection.d.ts +12 -0
  98. package/bin/assets/generators/utils/resources/resourceDetection.js +1 -0
  99. package/bin/assets/generators/utils/resources/resourceNaming.d.ts +6 -0
  100. package/bin/assets/generators/utils/resources/resourceNaming.js +1 -0
  101. package/bin/assets/generators/utils/tree.d.ts +25 -0
  102. package/bin/assets/generators/utils/tree.js +1 -0
  103. package/bin/assets/proto/buildkit.proto +126 -0
  104. package/bin/assets/proto/proto/buildkit.proto +126 -0
  105. package/bin/fjall.bundle.js +1138 -0
  106. package/bin/fjall.js +5 -1
  107. package/package.json +41 -4
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { App, OrganisationFactory, getConfig } from "@fjall/components-infrastructure";
4
+
5
+ const config = getConfig();
6
+
7
+ if (config.environment === "platform") {
8
+ const app = App.getInstance();
9
+ const platform = OrganisationFactory.build("Platform", { type: "platform" })(app);
10
+ <% if (security === "compliance" || security === "hardened") { -%>
11
+
12
+ platform.enableSecurityServicesAdmin({
13
+ services: ["guardduty", "securityhub", "config"]
14
+ });
15
+ <% } -%>
16
+ }
@@ -0,0 +1,4 @@
1
+ import { type Tree } from "@nx/devkit";
2
+ import { type OrganisationGeneratorOptions } from "@fjall/generator";
3
+ export declare function organisationGenerator(tree: Tree, options: OrganisationGeneratorOptions): Promise<void>;
4
+ export default organisationGenerator;
@@ -0,0 +1,2 @@
1
+ import{join as m}from"path";import{renderEjsTemplates as c}from"../utils/renderEjs.js";import{copySharedCdkFiles as p}from"../utils/copySharedFiles.js";import{getOrganisationComponentPath as g}from"../../src/util/pathHelpers.js";import{OrganisationGeneratorSchema as f,getZodErrorMessage as d}from"@fjall/generator";const l=import.meta.dirname;async function u(n,i){const e=f.safeParse(i);if(!e.success)throw new Error(`Invalid organisation generator options:
2
+ ${d(e.error)}`);const a=e.data,r={name:a.name,nameLowerCase:a.name.toLowerCase(),accountName:a.accountName||`${a.name.toLowerCase()}-main`,email:a.email,primaryRegion:a.primaryRegion||"us-east-1",secondaryRegions:a.secondaryRegions||[],security:a.security},s=[{name:"organisation",packageName:"infra-organisation"},{name:"platform",packageName:"infra-organisation"},{name:"account",packageName:"infra-app"}];for(const o of s){const t=g(o.name);c(n,m(l,"files",o.name),t,r),p(n,t,{...r,packageName:o.packageName})}}var C=u;export{C as default,u as organisationGenerator};
@@ -0,0 +1 @@
1
+ export { organisationGenerator } from "./generator.js";
@@ -0,0 +1 @@
1
+ import{organisationGenerator as a}from"./generator.js";export{a as organisationGenerator};
@@ -0,0 +1,3 @@
1
+ {
2
+ "acknowledged-issue-numbers": [32775, 34892, 34635]
3
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "app": "npx ts-node --prefer-ts-exts --transpile-only infrastructure.ts",
3
+ "assetMetadata": false,
4
+ "pathMetadata": false,
5
+ "versionReporting": false,
6
+ "watch": {
7
+ "include": ["**"],
8
+ "exclude": [
9
+ "README.md",
10
+ "cdk*.json",
11
+ "**/*.d.ts",
12
+ "**/*.js",
13
+ "tsconfig.json",
14
+ "package*.json",
15
+ "yarn.lock",
16
+ "node_modules",
17
+ "test"
18
+ ]
19
+ },
20
+ "context": {
21
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
22
+ "@aws-cdk/core:checkSecretUsage": true,
23
+ "@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
24
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
25
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
26
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27
+ "@aws-cdk/aws-iam:minimizePolicies": true,
28
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
29
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
30
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
31
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
32
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
33
+ "@aws-cdk/core:enablePartitionLiterals": true,
34
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
35
+ "@aws-cdk/aws-iam:standardizedServicePrincipals": true,
36
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
37
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
38
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
39
+ "@aws-cdk/aws-route53-patters:useCertificate": true,
40
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
41
+ "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
42
+ "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
43
+ "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
44
+ "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
45
+ "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
46
+ "@aws-cdk/aws-redshift:columnId": true,
47
+ "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
48
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
49
+ "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
50
+ "@aws-cdk/aws-kms:aliasNameRef": true,
51
+ "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
52
+ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
53
+ "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true
54
+ }
55
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "<%= packageName %>",
3
+ "version": "0.94.0",
4
+ "scripts": {},
5
+ "devDependencies": {
6
+ "@types/node": "20.4.9",
7
+ "ts-node": "^10.9.1",
8
+ "typescript": "~5.1.6"
9
+ },
10
+ "dependencies": {
11
+ "@fjall/components-infrastructure": "^0.94.0",
12
+ "aws-cdk-lib": "^2.239.0",
13
+ "constructs": "^10.0.0"
14
+ }
15
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["es2020", "dom"],
6
+ "declaration": true,
7
+ "strict": true,
8
+ "noImplicitAny": true,
9
+ "strictNullChecks": true,
10
+ "noImplicitThis": true,
11
+ "alwaysStrict": true,
12
+ "noUnusedLocals": false,
13
+ "noUnusedParameters": false,
14
+ "noImplicitReturns": true,
15
+ "noFallthroughCasesInSwitch": false,
16
+ "inlineSourceMap": true,
17
+ "inlineSources": true,
18
+ "experimentalDecorators": true,
19
+ "strictPropertyInitialization": false,
20
+ "typeRoots": ["./node_modules/@types"]
21
+ },
22
+ "exclude": ["node_modules", "cdk.out"]
23
+ }
@@ -0,0 +1 @@
1
+ export { s3Generator } from "./s3/generator.js";
@@ -0,0 +1 @@
1
+ import{s3Generator as o}from"./s3/generator.js";export{o as s3Generator};
@@ -0,0 +1,4 @@
1
+ import { type Tree } from "@nx/devkit";
2
+ import { type S3GeneratorOptions } from "@fjall/generator";
3
+ export declare function s3Generator(tree: Tree, options: S3GeneratorOptions): Promise<void>;
4
+ export default s3Generator;
@@ -0,0 +1 @@
1
+ import{assertResourceNameUnique as b}from"../../utils/resources/resourceDetection.js";import{pickDefined as k,withInfrastructurePlan as f}from"../../utils/planning/generatorHelpers.js";import{S3GeneratorSchema as S,toPascalCase as h,STORAGE_PRESETS as P}from"@fjall/generator";import{handleResourceConnections as g}from"../../utils/resources/connectionHelpers.js";async function N(n,c){await f(n,S,c,"S3",async({tree:a,validated:e,plan:t})=>{const o=h(e.bucketName);b(a,e.appName,o);const u=e.storagePreset??"standard",m=P[u],r=t.s3.length,i=r===0?"":`${r+1}`,p={name:o,bucketName:e.bucketName,variableName:`bucket${i}`,...m,...k(e,"publicReadAccess","websiteHosting","backupVaultTier","versioned","encryption","kmsKeyArn","cors")},s={...t,s3:[...t.s3,p]};return await g(s,e,o,"storage",{singularPrompt:"Found an existing compute resource. Connect this bucket to it?",pluralPrompt:"Found {count} compute resources. Connect this bucket to them?"}),s})}var R=N;export{R as default,N as s3Generator};
@@ -0,0 +1,4 @@
1
+ import { type Tree } from "@nx/devkit";
2
+ import { type TunnelGeneratorOptions } from "@fjall/generator";
3
+ export declare function tunnelGenerator(tree: Tree, options: TunnelGeneratorOptions): Promise<void>;
4
+ export default tunnelGenerator;
@@ -0,0 +1 @@
1
+ import{withInfrastructurePlan as o}from"../utils/planning/generatorHelpers.js";import{TunnelGeneratorSchema as s}from"@fjall/generator";async function i(t,a){await o(t,s,a,"tunnel",({validated:n,plan:e})=>{if(e.tunnel&&typeof e.tunnel=="object")throw new Error("Tunnel is already enabled for this application");if(!(e.database.length>0))throw new Error("No databases found \u2014 tunnel requires at least one relational database");const r=n.instanceType?{instanceType:n.instanceType}:{};return{...e,tunnel:r}})}var f=i;export{f as default,i as tunnelGenerator};
@@ -0,0 +1 @@
1
+ export { tunnelGenerator } from "./generator.js";
@@ -0,0 +1 @@
1
+ import{tunnelGenerator as n}from"./generator.js";export{n as tunnelGenerator};
@@ -0,0 +1,23 @@
1
+ import * as ts from "typescript";
2
+ /**
3
+ * Replace a single top-level statement with zero or more new statements
4
+ * in-place. Returns the updated source file. If `oldStatement` is not a
5
+ * top-level statement of `sourceFile`, the source file is returned unchanged.
6
+ */
7
+ export declare function replaceStatement(sourceFile: ts.SourceFile, oldStatement: ts.Statement, newStatements: readonly ts.Statement[]): ts.SourceFile;
8
+ export interface ReplacementImportSpec {
9
+ readonly module: string;
10
+ readonly namespaceAlias?: string;
11
+ readonly namedImports?: readonly string[];
12
+ }
13
+ /**
14
+ * Remove the listed named imports from any `import { ... } from fromModule`
15
+ * statement. If the statement becomes empty (zero named imports, no default,
16
+ * no namespace alias), it is removed entirely.
17
+ *
18
+ * The `replacementImports` are appended at the top of the file (after any
19
+ * preserved `@fjall/*` or third-party imports), but only inserted if not
20
+ * already present — namespace aliases and named imports are reused if they
21
+ * already exist in an import statement for the same module.
22
+ */
23
+ export declare function rewriteNamedImport(sourceFile: ts.SourceFile, fromModule: string, namesToRemove: readonly string[], replacementImports: readonly ReplacementImportSpec[]): ts.SourceFile;
@@ -0,0 +1 @@
1
+ import*as u from"typescript";function D(e,n,i){const t=u.factory,r=e.statements.indexOf(n);if(r<0)return e;const a=[...e.statements];return a.splice(r,1,...i),t.updateSourceFile(e,a,e.isDeclarationFile,e.referencedFiles,e.typeReferenceDirectives,e.hasNoDefaultLib,e.libReferenceDirectives)}function S(e,n,i,t){const r=u.factory,a=new Set(i);let d=[];for(const m of e.statements){if(!u.isImportDeclaration(m)){d.push(m);continue}if(o(m)!==n){d.push(m);continue}const f=c(r,m,a);f!==void 0&&d.push(f)}return d=I(r,d,t),r.updateSourceFile(e,d,e.isDeclarationFile,e.referencedFiles,e.typeReferenceDirectives,e.hasNoDefaultLib,e.libReferenceDirectives)}function o(e){if(u.isStringLiteral(e.moduleSpecifier))return e.moduleSpecifier.text}function c(e,n,i){const t=n.importClause;if(t===void 0)return n;const r=t.namedBindings;let a=r;if(r!==void 0&&u.isNamedImports(r)){const s=r.elements.filter(f=>{const p=f.propertyName?.text??f.name.text;return!i.has(p)});s.length===0?a=void 0:s.length!==r.elements.length&&(a=e.createNamedImports(s))}if(!(!(t.name!==void 0)&&!(a!==void 0)))return e.updateImportDeclaration(n,n.modifiers,e.updateImportClause(t,t.isTypeOnly,t.name,a),n.moduleSpecifier,n.attributes)}function I(e,n,i){const t=h(n),r=[];for(const d of i){const m=t.get(d.module);if(m===void 0){r.push(l(e,d));continue}const s=g(e,m,d);if(s!==void 0){const f=n.indexOf(m);f>=0&&n.splice(f,1,s)}else N(m,d)||r.push(l(e,d))}let a=0;for(let d=0;d<n.length;d++){const m=n[d];if(m===void 0||!u.isImportDeclaration(m))break;a=d+1}return r.length>0&&n.splice(a,0,...r),n}function h(e){const n=new Map;for(const i of e){if(!u.isImportDeclaration(i))continue;const t=o(i);t!==void 0&&!n.has(t)&&n.set(t,i)}return n}function l(e,n){let i;if(n.namespaceAlias!==void 0)i=e.createImportClause(!1,void 0,e.createNamespaceImport(e.createIdentifier(n.namespaceAlias)));else if(n.namedImports!==void 0&&n.namedImports.length>0)i=e.createImportClause(!1,void 0,e.createNamedImports(n.namedImports.map(t=>e.createImportSpecifier(!1,void 0,e.createIdentifier(t)))));else return e.createImportDeclaration(void 0,void 0,e.createStringLiteral(n.module),void 0);return e.createImportDeclaration(void 0,i,e.createStringLiteral(n.module),void 0)}function g(e,n,i){const t=n.importClause;if(t===void 0||i.namespaceAlias!==void 0||i.namedImports===void 0||i.namedImports.length===0)return;const r=t.namedBindings;if(r===void 0||!u.isNamedImports(r))return;const a=new Set(r.elements.map(s=>s.name.text)),d=i.namedImports.filter(s=>!a.has(s));if(d.length===0)return;const m=[...r.elements,...d.map(s=>e.createImportSpecifier(!1,void 0,e.createIdentifier(s)))];return e.updateImportDeclaration(n,n.modifiers,e.updateImportClause(t,t.isTypeOnly,t.name,e.createNamedImports(m)),n.moduleSpecifier,n.attributes)}function N(e,n){const i=e.importClause;if(i===void 0)return!1;if(n.namespaceAlias!==void 0){const t=i.namedBindings;return t!==void 0&&u.isNamespaceImport(t)&&t.name.text===n.namespaceAlias}if(n.namedImports!==void 0&&n.namedImports.length>0){const t=i.namedBindings;if(t===void 0||!u.isNamedImports(t))return!1;const r=new Set(t.elements.map(a=>a.name.text));return n.namedImports.every(a=>r.has(a))}return!1}export{D as replaceStatement,S as rewriteNamedImport};
@@ -0,0 +1,65 @@
1
+ import * as ts from "typescript";
2
+ /**
3
+ * Rewrite `<domainVar>.<prop>` property accesses in the source file after the
4
+ * `new Domain(...)` declaration has been replaced by the eject template.
5
+ *
6
+ * The `Domain` pattern exposes a closed set of public readonly fields
7
+ * (`hostedZone`, `certificates`, `nameServers`, `manualRecords`, `exportNames`,
8
+ * `registrar`, `zoneName`, inherited `node`). Each maps to a vanilla CDK
9
+ * equivalent created by the template. The rewriter preserves any user-attached
10
+ * custom L2 constructs (Layer 2 of the portability model) by mechanical
11
+ * substitution.
12
+ *
13
+ * Accesses that cannot be resolved (e.g. `<domainVar>.hostedZone` in
14
+ * external-records mode, or accesses to fields outside the closed set) are
15
+ * left untouched and reported as warnings — the user must fix them by hand.
16
+ */
17
+ export interface DomainRewriteContext {
18
+ /** The variable name bound to `new Domain(...)`. */
19
+ readonly domainVar: string;
20
+ /** Which registrar template was emitted. */
21
+ readonly registrar: "route53" | "external-delegated" | "external-records";
22
+ /** True when the emitted template produced a `hostedZone` identifier. */
23
+ readonly hasHostedZone: boolean;
24
+ /** True when the emitted template produced any `cert{i}` identifiers. */
25
+ readonly hasCertificates: boolean;
26
+ /** True when the emitted template produced an apex delegation role. */
27
+ readonly hasDelegationRole: boolean;
28
+ /**
29
+ * Cert keys in declaration order. `<domainVar>.certificates.get("<key>")`
30
+ * rewrites to the matching `cert{i}` identifier; unknown keys are warned on.
31
+ * Keys are the literal `cert.domainName` strings.
32
+ */
33
+ readonly certKeys: readonly string[];
34
+ /** Effective zone name (delegated: `sub.example.com`; else: `zoneName`). */
35
+ readonly effectiveZoneName: string;
36
+ /** The top-level `registrar` string — literal to inline. */
37
+ readonly registrarValue: string;
38
+ /** Export-name shape (mirrors `getDomainExportNames`). */
39
+ readonly exportNames: {
40
+ readonly hostedZoneId: string;
41
+ readonly certificateArn: string;
42
+ readonly delegationRoleArn: string;
43
+ };
44
+ }
45
+ export interface DomainRewriteResult {
46
+ readonly sourceFile: ts.SourceFile;
47
+ /**
48
+ * `true` when any `<domainVar>.certificates` or `<domainVar>.certificates.get(...)`
49
+ * access was encountered. The caller prepends a `const certificates = new
50
+ * Map(...)` binding right after the emitted template when this is set.
51
+ */
52
+ readonly needsCertificateMap: boolean;
53
+ /** Human-readable warnings for unresolvable or deliberately-skipped accesses. */
54
+ readonly warnings: readonly string[];
55
+ }
56
+ /**
57
+ * Walk the source file and rewrite every `<domainVar>.<prop>` (and nested
58
+ * call expressions for `certificates.get(...)`) to the vanilla-CDK equivalent.
59
+ */
60
+ export declare function rewriteDomainPropertyAccess(sourceFile: ts.SourceFile, context: DomainRewriteContext): DomainRewriteResult;
61
+ /**
62
+ * Build `const certificates = new Map<string, acm.Certificate>([["<key0>", cert0], ...]);`
63
+ * for prepending to the emitted template when the rewriter needed it.
64
+ */
65
+ export declare function buildCertificateMapStatement(certKeys: readonly string[]): ts.VariableStatement;
@@ -0,0 +1 @@
1
+ import*as s from"typescript";function I(a,e){const c=[];let o=!1;const i=s.factory;function f(r){const t=r.getStart(a,!1);return a.getLineAndCharacterOfPosition(t).line+1}function n(r,t){c.push(`line ${f(r)}: ${t}`)}function u(r){return s.isIdentifier(r)&&r.text===e.domainVar}function h(r){const t=r.expression;if(!s.isPropertyAccessExpression(t)||t.name.text!=="get")return;const d=t.expression;if(!s.isPropertyAccessExpression(d)||d.name.text!=="certificates"||!u(d.expression))return;const m=r.arguments[0];if(m===void 0||!s.isStringLiteral(m)){n(r,`'${e.domainVar}.certificates.get(...)' with non-string-literal key cannot be statically rewritten. Replace with a direct reference to the emitted certificate variable.`);return}if(e.registrar==="external-records"){n(r,`'${e.domainVar}.certificates.get(...)' on an external-records domain: no certificate map is emitted. Reference the emitted 'cert{i}' variable directly.`);return}const p=m.text,g=e.certKeys.indexOf(p);if(g<0){n(r,`'${e.domainVar}.certificates.get("${p}")': no certificate with domainName "${p}" was declared on the domain. Accesses remain unrewritten.`);return}return i.createIdentifier(`cert${g}`)}function y(r){if(!u(r.expression))return;const t=r.name.text;switch(t){case"hostedZone":{if(!e.hasHostedZone){n(r,`'${e.domainVar}.hostedZone' is undefined in '${e.registrar}' mode (no hosted zone is created). Remove the access or switch registrars.`);return}return i.createIdentifier("hostedZone")}case"certificates":{if(e.registrar==="external-records"){n(r,`'${e.domainVar}.certificates' on external-records is not exposed as a Map. Reference 'cert{i}' variables directly.`);return}if(!e.hasCertificates){n(r,`'${e.domainVar}.certificates' referenced but no certificates were declared on the domain. Access remains unrewritten.`);return}return o=!0,i.createIdentifier("certificates")}case"nameServers":{if(!e.hasHostedZone){n(r,`'${e.domainVar}.nameServers' is undefined in '${e.registrar}' mode (no hosted zone is created).`);return}return i.createPropertyAccessExpression(i.createIdentifier("hostedZone"),i.createIdentifier("hostedZoneNameServers"))}case"manualRecords":{if(e.registrar!=="external-records"){n(r,`'${e.domainVar}.manualRecords' is only populated on external-records; on '${e.registrar}' it is always empty. Access remains unrewritten.`);return}n(r,`'${e.domainVar}.manualRecords' has no direct vanilla-CDK equivalent \u2014 the emitted CfnOutputs carry the equivalent data per-entry. Access remains unrewritten.`);return}case"registrar":return i.createStringLiteral(e.registrarValue);case"zoneName":return i.createStringLiteral(e.effectiveZoneName);case"node":return i.createPropertyAccessExpression(i.createIdentifier("domainScope"),i.createIdentifier("node"));case"exportNames":return w(e);default:{n(r,`'${e.domainVar}.${t}' is not a public readonly property of the Domain construct and cannot be ejected. Access remains unrewritten.`);return}}}function A(r){const t=r.expression;if(!s.isPropertyAccessExpression(t)||t.name.text!=="exportNames"||!u(t.expression))return;const d=r.name.text;switch(d){case"hostedZoneId":{if(!e.hasHostedZone){n(r,`'${e.domainVar}.exportNames.hostedZoneId' is not emitted in '${e.registrar}' mode.`);return}return i.createStringLiteral(e.exportNames.hostedZoneId)}case"certificateArn":{if(!e.hasCertificates){n(r,`'${e.domainVar}.exportNames.certificateArn' is not emitted (no certificates declared).`);return}return i.createStringLiteral(e.exportNames.certificateArn)}case"delegationRoleArn":{if(!e.hasDelegationRole){n(r,`'${e.domainVar}.exportNames.delegationRoleArn' is not emitted (no apex delegation role created).`);return}return i.createStringLiteral(e.exportNames.delegationRoleArn)}default:{n(r,`'${e.domainVar}.exportNames.${d}' is not a known export name.`);return}}}function l(r){if(s.isPropertyAccessExpression(r)){const t=A(r);if(t!==void 0)return t}if(s.isCallExpression(r)){const t=h(r);if(t!==void 0)return t}if(s.isPropertyAccessExpression(r)){const t=y(r);if(t!==void 0)return t}return s.visitEachChild(r,l,void 0)}const $=a.statements.map(r=>l(r));return{sourceFile:i.updateSourceFile(a,$,a.isDeclarationFile,a.referencedFiles,a.typeReferenceDirectives,a.hasNoDefaultLib,a.libReferenceDirectives),needsCertificateMap:o,warnings:c}}function w(a){const e=s.factory,c=[];return c.push(e.createPropertyAssignment(e.createIdentifier("hostedZoneId"),e.createStringLiteral(a.exportNames.hostedZoneId))),c.push(e.createPropertyAssignment(e.createIdentifier("certificateArn"),e.createStringLiteral(a.exportNames.certificateArn))),c.push(e.createPropertyAssignment(e.createIdentifier("delegationRoleArn"),e.createStringLiteral(a.exportNames.delegationRoleArn))),e.createObjectLiteralExpression(c,!0)}function R(a){const e=s.factory,c=a.map((f,n)=>e.createArrayLiteralExpression([e.createStringLiteral(f),e.createIdentifier(`cert${n}`)],!1)),o=[e.createTypeReferenceNode("string",void 0),e.createTypeReferenceNode("acm.Certificate",void 0)],i=e.createNewExpression(e.createIdentifier("Map"),o,[e.createArrayLiteralExpression(c,!0)]);return e.createVariableStatement(void 0,e.createVariableDeclarationList([e.createVariableDeclaration(e.createIdentifier("certificates"),void 0,void 0,i)],s.NodeFlags.Const))}export{R as buildCertificateMapStatement,I as rewriteDomainPropertyAccess};
@@ -0,0 +1,2 @@
1
+ import type { SurgicalModificationResult } from "@fjall/generator";
2
+ export declare function createBackup(filePath: string): SurgicalModificationResult;
@@ -0,0 +1 @@
1
+ import*as s from"node:fs";import{getErrorMessage as u}from"@fjall/generator";const a=1e4;function f(r){return r instanceof Error&&"code"in r}function p(r){try{const t=s.readFileSync(r,"utf-8"),n=`${r}.bak`;let e=n;for(let c=1;c<=a;c+=1)try{return s.writeFileSync(e,t,{flag:"wx"}),{content:e,success:!0}}catch(o){if(!f(o)||o.code!=="EEXIST")throw o;e=`${n}.${c}`}return{content:"",success:!1,error:`Failed to create backup of ${r}: exhausted ${String(a)} suffix attempts`}}catch(t){return{content:"",success:!1,error:`Failed to create backup of ${r}: ${u(t)}`}}}export{p as createBackup};
@@ -0,0 +1,3 @@
1
+ export { createBackup } from "./astSurgicalModification.js";
2
+ export { replaceStatement, rewriteNamedImport, type ReplacementImportSpec } from "./astCodeModification.js";
3
+ export { buildCertificateMapStatement, rewriteDomainPropertyAccess, type DomainRewriteContext, type DomainRewriteResult } from "./astDomainRewriter.js";
@@ -0,0 +1 @@
1
+ import{createBackup as t}from"./astSurgicalModification.js";import{replaceStatement as o,rewriteNamedImport as m}from"./astCodeModification.js";import{buildCertificateMapStatement as c,rewriteDomainPropertyAccess as i}from"./astDomainRewriter.js";export{c as buildCertificateMapStatement,t as createBackup,o as replaceStatement,i as rewriteDomainPropertyAccess,m as rewriteNamedImport};
@@ -0,0 +1,10 @@
1
+ import { type Tree } from "@nx/devkit";
2
+ /**
3
+ * Copies shared CDK configuration files to the target directory
4
+ * @param tree - Nx workspace tree
5
+ * @param targetPath - Target directory path. Can be:
6
+ * - App name only (e.g., "myapp") - will be prefixed with "fjall/"
7
+ * - Full path (e.g., "fjall/account") - will be used as-is
8
+ * @param templateVariables - Variables for EJS template rendering
9
+ */
10
+ export declare function copySharedCdkFiles(tree: Tree, targetPath: string, templateVariables: Record<string, unknown>): void;
@@ -0,0 +1 @@
1
+ import{join as n}from"path";import{renderEjsTemplates as s}from"./renderEjs.js";import{getAppPath as p}from"../../src/util/pathHelpers.js";const t=import.meta.dirname;function f(o,e,i){const r=n(t,"..","shared","files"),m=e.includes("/")?e:p(e);s(o,r,m,i)}export{f as copySharedCdkFiles};
@@ -0,0 +1,8 @@
1
+ export * from "@fjall/generator";
2
+ export * from "./ast/index.js";
3
+ export * from "./planning/index.js";
4
+ export * from "./resources/index.js";
5
+ export { promptForConnection, selectResources } from "./prompts.js";
6
+ export { renderEjsTemplates, renderEjsString, copyTemplateFile } from "./renderEjs.js";
7
+ export { copySharedCdkFiles } from "./copySharedFiles.js";
8
+ export { FileSystemTree } from "./tree.js";
@@ -0,0 +1 @@
1
+ export*from"@fjall/generator";export*from"./ast/index.js";export*from"./planning/index.js";export*from"./resources/index.js";import{promptForConnection as f,selectResources as s}from"./prompts.js";import{renderEjsTemplates as l,renderEjsString as n,copyTemplateFile as c}from"./renderEjs.js";import{copySharedCdkFiles as d}from"./copySharedFiles.js";import{FileSystemTree as a}from"./tree.js";export{a as FileSystemTree,d as copySharedCdkFiles,c as copyTemplateFile,f as promptForConnection,n as renderEjsString,l as renderEjsTemplates,s as selectResources};
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Integration Test Utilities
3
+ *
4
+ * Shared utilities for integration tests that run CDK synth operations.
5
+ * Optimises test performance by using shared node_modules instead of
6
+ * running npm install for each test.
7
+ *
8
+ * @see aiDocs/architecture/TESTING_STRATEGY.md
9
+ */
10
+ export declare const CLI_ROOT: string;
11
+ export declare const TEST_APPS_DIR: string;
12
+ export declare const COMPONENTS_DIR: string;
13
+ export declare const TEST_FIXTURES_DIR: string;
14
+ export declare const SHARED_NODE_MODULES: string;
15
+ export declare const MOCK_ACCOUNT = "123456789012";
16
+ export declare const MOCK_REGION = "ap-southeast-2";
17
+ export interface CfnResource {
18
+ Type: string;
19
+ Properties: Record<string, unknown>;
20
+ [key: string]: unknown;
21
+ }
22
+ export interface CfnTemplate {
23
+ Resources: Record<string, CfnResource>;
24
+ [key: string]: unknown;
25
+ }
26
+ /**
27
+ * ECS Container definition structure from CloudFormation.
28
+ */
29
+ export interface EcsContainerDefinition {
30
+ Name?: string;
31
+ Image?: string;
32
+ Essential?: boolean;
33
+ PortMappings?: Array<{
34
+ ContainerPort?: number;
35
+ Protocol?: string;
36
+ }>;
37
+ Environment?: Array<{
38
+ Name: string;
39
+ Value: string;
40
+ }>;
41
+ Secrets?: Array<{
42
+ Name: string;
43
+ ValueFrom: unknown;
44
+ }>;
45
+ Command?: string[];
46
+ EntryPoint?: string[];
47
+ [key: string]: unknown;
48
+ }
49
+ /**
50
+ * Common CloudFormation resource properties accessed in tests.
51
+ * Provides type safety for frequently accessed properties while
52
+ * allowing access to any other property via index signature.
53
+ */
54
+ export interface CfnResourceProperties {
55
+ GroupDescription?: string;
56
+ SecurityGroupIngress?: Array<{
57
+ CidrIp?: string;
58
+ FromPort?: number;
59
+ ToPort?: number;
60
+ IpProtocol?: string;
61
+ SourceSecurityGroupId?: unknown;
62
+ }>;
63
+ Conditions?: Array<{
64
+ Field?: string;
65
+ PathPatternConfig?: {
66
+ Values?: string[];
67
+ };
68
+ HostHeaderConfig?: {
69
+ Values?: string[];
70
+ };
71
+ Values?: string[];
72
+ }>;
73
+ ContainerDefinitions?: EcsContainerDefinition[];
74
+ RequiresCompatibilities?: string[];
75
+ GenerateSecretString?: {
76
+ SecretStringTemplate?: string;
77
+ GenerateStringKey?: string;
78
+ ExcludeCharacters?: string;
79
+ };
80
+ [key: string]: unknown;
81
+ }
82
+ /**
83
+ * A CloudFormation resource entry with typed common properties.
84
+ */
85
+ export interface ResourceEntry {
86
+ logicalId: string;
87
+ properties: CfnResourceProperties;
88
+ }
89
+ export interface CdkSynthResult {
90
+ success: boolean;
91
+ output: string;
92
+ }
93
+ /**
94
+ * Run CDK synth on a test app.
95
+ * Uses shared node_modules when available, falls back to npm install.
96
+ */
97
+ export declare function runCdkSynth(appName: string, options?: {
98
+ context?: Record<string, unknown>;
99
+ }): CdkSynthResult;
100
+ /**
101
+ * Clean up a test app directory.
102
+ * Handles symlinked node_modules properly.
103
+ */
104
+ export declare function cleanupTestApp(appName: string): void;
105
+ /**
106
+ * Read a CloudFormation template from cdk.out.
107
+ */
108
+ export declare function readCfnTemplate(appName: string, stackSuffix: string): CfnTemplate;
109
+ /**
110
+ * Get infrastructure.ts content for a test app.
111
+ */
112
+ export declare function getInfrastructureContent(appName: string): string;
113
+ /**
114
+ * Find resources by type in a CloudFormation template.
115
+ */
116
+ export declare function findResourcesByType(template: CfnTemplate, resourceType: string): ResourceEntry[];
117
+ /**
118
+ * Find a security group by description substring.
119
+ * Commonly used to find database or ALB security groups.
120
+ */
121
+ export declare function findSecurityGroupByDescription(securityGroups: ResourceEntry[], descriptionSubstring: string): ResourceEntry | undefined;
122
+ /**
123
+ * Extract values from ALB listener rule conditions by field type.
124
+ */
125
+ export declare function extractConditionValues(listenerRules: ResourceEntry[], fieldType: "path-pattern" | "host-header"): string[];
126
+ /**
127
+ * Ensure components-infrastructure is built.
128
+ */
129
+ export declare function ensureComponentsBuilt(): void;
@@ -0,0 +1,2 @@
1
+ import r from"fs";import o from"path";import{execFileSync as p}from"child_process";import{normaliseError as S}from"../../src/util/errorUtils.js";const a=o.resolve(import.meta.dirname,"../.."),c=o.join(a,"fjall"),d=o.resolve(a,"../components/infrastructure"),y=o.join(a,"test-fixtures"),l=o.join(y,"node_modules"),f="123456789012",s="ap-southeast-2";function O(n,e){const t=o.join(c,n);return x(t),_(t,e?.context),m(t)?b(t):{success:!1,output:"Failed to set up node_modules"}}function m(n){const e=o.join(n,"node_modules");if(r.existsSync(l))try{return r.existsSync(e)&&(r.lstatSync(e).isSymbolicLink()?r.unlinkSync(e):r.rmSync(e,{recursive:!0,force:!0})),r.symlinkSync(l,e,"junction"),!0}catch{}if(!r.existsSync(e))try{p("npm",["install"],{cwd:n,encoding:"utf-8",timeout:12e4})}catch{return!1}return!0}function b(n){try{const e=o.join(n,"node_modules",".bin","cdk");return{success:!0,output:p(e,["synth"],{cwd:n,encoding:"utf-8",timeout:12e4,stdio:["pipe","pipe","pipe"],env:{...process.env,AWS_REGION:s,AWS_DEFAULT_REGION:s,CDK_DEFAULT_ACCOUNT:f,CDK_DEFAULT_REGION:s}})}}catch(e){const t=S(e),i="stdout"in t&&typeof t.stdout=="string"?t.stdout:void 0,u="stderr"in t&&typeof t.stderr=="string"?t.stderr:void 0;return{success:!1,output:i||u||t.message||"Unknown error"}}}function x(n){const e=o.join(n,"package.json"),t=JSON.parse(r.readFileSync(e,"utf-8"));t.dependencies["@fjall/components-infrastructure"]=`file:${d}`,r.writeFileSync(e,JSON.stringify(t,null,2))}function _(n,e){const t={app:"./node_modules/.bin/ts-node infrastructure.ts",context:{"aws:cdk:bundling-stacks":[],[`availability-zones:account=${f}:region=${s}`]:[`${s}a`,`${s}b`,`${s}c`],[`vpc-provider:account=${f}:filter.isDefault=true:region=${s}:returnAsymmetricSubnets=true`]:{vpcId:"vpc-12345678",vpcCidrBlock:"10.0.0.0/16",availabilityZones:[`${s}a`,`${s}b`],publicSubnetIds:["subnet-pub1","subnet-pub2"],publicSubnetRouteTableIds:["rtb-pub1","rtb-pub2"],privateSubnetIds:["subnet-priv1","subnet-priv2"],privateSubnetRouteTableIds:["rtb-priv1","rtb-priv2"]},...e}};r.writeFileSync(o.join(n,"cdk.json"),JSON.stringify(t,null,2))}function E(n){const e=o.join(c,n);if(!r.existsSync(e))return;const t=o.join(e,"node_modules");r.existsSync(t)&&r.lstatSync(t).isSymbolicLink()&&r.unlinkSync(t),r.rmSync(e,{recursive:!0,force:!0})}function C(n){return n.replace(/[-_](.)/g,(e,t)=>t.toUpperCase()).replace(/^./,e=>e.toUpperCase())}function T(n,e){const t=o.join(c,n,"cdk.out",`${C(n)}${e}.template.json`);if(!r.existsSync(t))throw new Error(`Template not found: ${t}`);return JSON.parse(r.readFileSync(t,"utf-8"))}function h(n){const e=o.join(c,n,"infrastructure.ts");if(!r.existsSync(e))throw new Error(`Infrastructure file not found: ${e}`);return r.readFileSync(e,"utf-8")}function I(n,e){return Object.entries(n.Resources).filter(([,t])=>t.Type===e).map(([t,i])=>({logicalId:t,properties:i.Properties}))}function D(n,e){return n.find(t=>t.properties.GroupDescription?.includes(e))}function N(n,e){const t=e==="path-pattern"?"PathPatternConfig":"HostHeaderConfig";return n.flatMap(i=>i.properties.Conditions?.filter(u=>u.Field===e).flatMap(u=>u[t]?.Values??u.Values??[])).filter(i=>i!==void 0)}function R(){r.existsSync(o.join(d,"dist"))||(process.stdout.write(`Building components-infrastructure...
2
+ `),p("npm",["run","build"],{cwd:d,stdio:"inherit"}))}export{a as CLI_ROOT,d as COMPONENTS_DIR,f as MOCK_ACCOUNT,s as MOCK_REGION,l as SHARED_NODE_MODULES,c as TEST_APPS_DIR,y as TEST_FIXTURES_DIR,E as cleanupTestApp,R as ensureComponentsBuilt,N as extractConditionValues,I as findResourcesByType,D as findSecurityGroupByDescription,h as getInfrastructureContent,T as readCfnTemplate,O as runCdkSynth};
@@ -0,0 +1,30 @@
1
+ import type { Tree } from "@nx/devkit";
2
+ /**
3
+ * Lightweight mock Tree implementation for generator unit tests.
4
+ * Use this instead of duplicating MockTree in each test file.
5
+ *
6
+ * For integration tests that need real file system behaviour, use FileSystemTree instead.
7
+ */
8
+ export declare class MockTree {
9
+ private files;
10
+ private changes;
11
+ constructor(initialFiles?: Record<string, string>);
12
+ read(path: string): Buffer | null;
13
+ write(path: string, content: string): void;
14
+ exists(path: string): boolean;
15
+ getChanges(): Array<{
16
+ path: string;
17
+ content: string;
18
+ }>;
19
+ getFileContent(path: string): string | undefined;
20
+ }
21
+ /**
22
+ * Type helper: cast MockTree as Tree for generator functions that expect the NX Tree interface.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const tree = new MockTree({ "apps/myapp/infrastructure/main.ts": code });
27
+ * const result = myGenerator(tree as unknown as Tree, options);
28
+ * ```
29
+ */
30
+ export type { Tree };
@@ -0,0 +1 @@
1
+ class r{files=new Map;changes=[];constructor(e={}){for(const[t,s]of Object.entries(e))this.files.set(t,s)}read(e){const t=this.files.get(e);return t?Buffer.from(t):null}write(e,t){this.files.set(e,t),this.changes.push({path:e,content:t})}exists(e){return this.files.has(e)}getChanges(){return this.changes}getFileContent(e){return this.files.get(e)}}export{r as MockTree};
@@ -0,0 +1,35 @@
1
+ import { type Tree } from "@nx/devkit";
2
+ import { type Result } from "../../../src/types/Result.js";
3
+ import { type ParsedInfrastructure, type ApplicationResourcePlan } from "@fjall/generator";
4
+ import { type z } from "zod";
5
+ export declare function createInfrastructureBackup(tree: Tree, infrastructurePath: string, originalContent: string): Result<void, Error>;
6
+ export declare function pickDefined<T extends Record<string, unknown>>(source: T, ...keys: Array<keyof T>): Partial<Pick<T, (typeof keys)[number]>>;
7
+ export interface ParseAndBuildResult {
8
+ parsed: ParsedInfrastructure;
9
+ plan: ApplicationResourcePlan;
10
+ }
11
+ export declare function parseAndBuildPlan(content: string, appName: string): Result<ParseAndBuildResult, Error>;
12
+ export declare function parseAndBuildPlanOrThrow(content: string, appName: string): ParseAndBuildResult;
13
+ export declare function validateGeneratorOptions<T>(schema: z.ZodType<T>, options: unknown, generatorName: string): T;
14
+ export declare function readInfrastructureOrThrow(tree: Tree, appName: string): {
15
+ path: string;
16
+ content: string;
17
+ };
18
+ export interface WithInfrastructurePlanContext<T> {
19
+ tree: Tree;
20
+ validated: T;
21
+ plan: ApplicationResourcePlan;
22
+ infrastructurePath: string;
23
+ content: string;
24
+ }
25
+ /**
26
+ * Orchestrates the common generator pattern: validate options, read existing
27
+ * infrastructure, parse into a plan, run custom logic, then write back.
28
+ *
29
+ * The callback receives the parsed plan and validated options, and must return
30
+ * the updated plan to be written.
31
+ */
32
+ export declare function withInfrastructurePlan<T extends {
33
+ appName: string;
34
+ }>(tree: Tree, schema: z.ZodType<T>, options: unknown, generatorName: string, buildPlan: (ctx: WithInfrastructurePlanContext<T>) => ApplicationResourcePlan | Promise<ApplicationResourcePlan>): Promise<void>;
35
+ export declare function generateAndWriteInfrastructure(tree: Tree, infrastructurePath: string, originalContent: string, plan: ApplicationResourcePlan): Result<void, Error>;
@@ -0,0 +1,2 @@
1
+ import*as p from"fs";import*as w from"path";import{logger as g}from"../../../src/util/logger/index.js";import{normaliseError as d}from"../../../src/util/errorUtils.js";import{success as u,failure as a}from"../../../src/types/Result.js";import{parseInfrastructure as h,convertToResourcePlan as k,getZodErrorMessage as x,generateInfrastructureFromPlan as E}from"@fjall/generator";import{getInfrastructurePath as I}from"../../../src/util/pathHelpers.js";function P(t,o,r){const e=t.root?w.join(t.root,o):o;try{return p.existsSync(e)&&p.writeFileSync(`${e}.bak`,r),u(void 0)}catch(s){const n=d(s);return g.warn("Generator",`Failed to create backup of ${o}`,{error:n.message}),a(n)}}function S(t,...o){const r={};for(const e of o)t[e]!==void 0&&(r[e]=t[e]);return r}function $(t,o){try{const r=h(t,{extractCustomCode:!0}),e=k(r,o,{skipValidation:!0});return r.customCodeBlocks?.length&&(e.customCodeBlocks=r.customCodeBlocks),u({parsed:r,plan:e})}catch(r){return a(d(r))}}function y(t,o){const r=$(t,o);if(!r.success)throw new Error(`Failed to parse infrastructure: ${r.error.message}`);return r.data}function B(t,o,r){const e=t.safeParse(o);if(!e.success)throw new Error(`Invalid ${r} generator options:
2
+ ${x(e.error)}`);return e.data}function b(t,o){const r=I(o),e=t.read(r)?.toString();if(!e)throw new Error(`App '${o}' not found. Expected infrastructure.ts at: ${r}`);return{path:r,content:e}}async function T(t,o,r,e,s){const n=B(o,r,e),{path:i,content:c}=b(t,n.appName),{plan:l}=y(c,n.appName),m=await s({tree:t,validated:n,plan:l,infrastructurePath:i,content:c}),f=C(t,i,c,m);if(!f.success)throw new Error(f.error.message)}function C(t,o,r,e){const s=E(e);if(!s.success)return a(s.error);const n=P(t,o,r);return n.success?(t.write(o,s.data),u(void 0)):a(new Error(`Failed to create backup: ${n.error.message}`))}export{P as createInfrastructureBackup,C as generateAndWriteInfrastructure,$ as parseAndBuildPlan,y as parseAndBuildPlanOrThrow,S as pickDefined,b as readInfrastructureOrThrow,B as validateGeneratorOptions,T as withInfrastructurePlan};
@@ -0,0 +1 @@
1
+ export { createInfrastructureBackup, pickDefined, type ParseAndBuildResult, parseAndBuildPlan, parseAndBuildPlanOrThrow, validateGeneratorOptions, readInfrastructureOrThrow, generateAndWriteInfrastructure, withInfrastructurePlan, type WithInfrastructurePlanContext } from "./generatorHelpers.js";
@@ -0,0 +1 @@
1
+ import{createInfrastructureBackup as a,pickDefined as t,parseAndBuildPlan as n,parseAndBuildPlanOrThrow as u,validateGeneratorOptions as d,readInfrastructureOrThrow as i,generateAndWriteInfrastructure as c,withInfrastructurePlan as s}from"./generatorHelpers.js";export{a as createInfrastructureBackup,c as generateAndWriteInfrastructure,n as parseAndBuildPlan,u as parseAndBuildPlanOrThrow,t as pickDefined,i as readInfrastructureOrThrow,d as validateGeneratorOptions,s as withInfrastructurePlan};
@@ -0,0 +1,4 @@
1
+ export declare function promptForConnection(resourceCount: number, resourceType: string, singularMessage?: string, pluralMessage?: string): Promise<boolean>;
2
+ export declare function selectResources<T extends {
3
+ name: string;
4
+ }>(resources: T[], message: string, getDescription?: (r: T) => string): Promise<string[]>;
@@ -0,0 +1,6 @@
1
+ import i from"prompts";async function p(e,s,o,t){const c=`Connect to existing ${s}?`,n=`Found ${e} ${s}s. Configure connections?`,a=e===1?o??c:t?.replace("{count}",String(e))??n,l=await i([{type:"confirm",name:"shouldConnect",message:a,initial:!0}]);return l?l.shouldConnect:!1}async function u(e,s,o){if(e.length===1)return process.stdout.write(`
2
+ Auto-selected: ${e[0].name}
3
+ `),[e[0].name];let t=[];for(;t.length===0;)t=(await i([{type:"multiselect",name:"selected",message:s,choices:e.map(n=>({title:n.name,value:n.name,description:o?.(n),selected:!1}))}]))?.selected||[],t.length===0&&process.stdout.write(`
4
+ Please select at least one resource
5
+
6
+ `);return t}export{p as promptForConnection,u as selectResources};
@@ -0,0 +1,24 @@
1
+ import { type Tree } from "@nx/devkit";
2
+ /**
3
+ * Renders EJS templates and generates files using Tree
4
+ * @param tree - Tree for file operations
5
+ * @param templatePath - Path to the template files
6
+ * @param targetPath - Target path for generated files
7
+ * @param substitutions - Object containing template variables
8
+ */
9
+ export declare function renderEjsTemplates(tree: Tree, templatePath: string, targetPath: string, substitutions: Record<string, unknown>): void;
10
+ /**
11
+ * Renders a single EJS template string
12
+ * @param template - EJS template string
13
+ * @param data - Template data
14
+ * @returns Rendered template string
15
+ */
16
+ export declare function renderEjsString(template: string, data: Record<string, unknown>): string;
17
+ /**
18
+ * Copies a template file with EJS substitutions
19
+ * @param tree - Tree for file operations
20
+ * @param from - Source template path
21
+ * @param to - Destination path
22
+ * @param substitutions - Template variables
23
+ */
24
+ export declare function copyTemplateFile(tree: Tree, from: string, to: string, substitutions: Record<string, unknown>): void;
@@ -0,0 +1 @@
1
+ import S from"ejs";import r from"fs";import a from"path";function w(t,e,i,s){function n(o,u=""){const y=r.readdirSync(o);for(const d of y){const c=a.join(o,d),m=a.join(u,d),p=r.statSync(c);if(p.isDirectory())n(c,m);else if(p.isFile()){const f=r.readFileSync(c,"utf-8"),j=a.join(i,m),F=f.includes("<%")?l(f,s):f;t.write(j,F)}}}n(e)}function l(t,e){return S.render(t,e)}function D(t,e,i,s){const n=r.readFileSync(e,"utf-8"),o=l(n,s);t.write(i,o)}export{D as copyTemplateFile,l as renderEjsString,w as renderEjsTemplates};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Interactive resource connection handler (CLI-only — uses prompts).
3
+ * Pure connection logic (applyComputeConnections, applyServiceConnections)
4
+ * lives in @fjall/generator for shared use by CLI and webapp.
5
+ */
6
+ import { type ApplicationResourcePlan, type ConnectionType } from "@fjall/generator";
7
+ interface ConnectionPrompts {
8
+ readonly singularPrompt: string;
9
+ readonly pluralPrompt: string;
10
+ }
11
+ interface ConnectionHandlerOptions {
12
+ readonly connectionConfig?: {
13
+ readonly connectToCompute?: string[];
14
+ readonly connectToServices?: string[];
15
+ };
16
+ readonly nameProvidedByFlag?: boolean;
17
+ }
18
+ export declare function handleResourceConnections(currentPlan: ApplicationResourcePlan, options: ConnectionHandlerOptions, resourceName: string, type: ConnectionType, prompts: ConnectionPrompts): Promise<void>;
19
+ export {};