@geekmidas/cli 1.10.41 → 1.12.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 (115) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +14 -0
  3. package/dist/{CachedStateProvider-37pG1HBx.cjs → CachedStateProvider-BdX7klRc.cjs} +1 -1
  4. package/dist/{CachedStateProvider-C8A-cCm5.mjs → CachedStateProvider-CzyFct42.mjs} +1 -1
  5. package/dist/{CachedStateProvider-C8A-cCm5.mjs.map → CachedStateProvider-CzyFct42.mjs.map} +1 -1
  6. package/dist/CachedStateProvider-GcGziXh1.mjs +3 -0
  7. package/dist/{CachedStateProvider-CxsXOKkg.cjs → CachedStateProvider-klLWZXWF.cjs} +1 -1
  8. package/dist/{CachedStateProvider-CxsXOKkg.cjs.map → CachedStateProvider-klLWZXWF.cjs.map} +1 -1
  9. package/dist/{HostingerProvider-ANWchdiK.mjs → HostingerProvider-B2ZYz7Yc.mjs} +1 -1
  10. package/dist/{HostingerProvider-ANWchdiK.mjs.map → HostingerProvider-B2ZYz7Yc.mjs.map} +1 -1
  11. package/dist/{HostingerProvider-5KYmwoK2.cjs → HostingerProvider-CrHfsgz9.cjs} +1 -1
  12. package/dist/{HostingerProvider-5KYmwoK2.cjs.map → HostingerProvider-CrHfsgz9.cjs.map} +1 -1
  13. package/dist/{LocalStateProvider-Dp0KkRcw.mjs → LocalStateProvider-CP49rjzt.mjs} +1 -1
  14. package/dist/{LocalStateProvider-Dp0KkRcw.mjs.map → LocalStateProvider-CP49rjzt.mjs.map} +1 -1
  15. package/dist/{LocalStateProvider-CLifRC0Y.cjs → LocalStateProvider-CalzNfNA.cjs} +1 -1
  16. package/dist/{LocalStateProvider-CLifRC0Y.cjs.map → LocalStateProvider-CalzNfNA.cjs.map} +1 -1
  17. package/dist/{Route53Provider-owQQ4pn6.cjs → Route53Provider-DwgXiLxt.cjs} +1 -1
  18. package/dist/{Route53Provider-owQQ4pn6.cjs.map → Route53Provider-DwgXiLxt.cjs.map} +1 -1
  19. package/dist/{Route53Provider-QoPgcXxn.mjs → Route53Provider-tlgBzrRQ.mjs} +1 -1
  20. package/dist/{Route53Provider-QoPgcXxn.mjs.map → Route53Provider-tlgBzrRQ.mjs.map} +1 -1
  21. package/dist/{SSMStateProvider-CksOTB8M.mjs → SSMStateProvider-BlrTiV5m.mjs} +1 -1
  22. package/dist/{SSMStateProvider-CksOTB8M.mjs.map → SSMStateProvider-BlrTiV5m.mjs.map} +1 -1
  23. package/dist/{SSMStateProvider-CT8tjl9o.cjs → SSMStateProvider-Bt6r8CTL.cjs} +1 -1
  24. package/dist/{SSMStateProvider-CT8tjl9o.cjs.map → SSMStateProvider-Bt6r8CTL.cjs.map} +1 -1
  25. package/dist/{backup-provisioner-C4noe75O.cjs → backup-provisioner-B3HBN-2k.cjs} +1 -1
  26. package/dist/{backup-provisioner-C4noe75O.cjs.map → backup-provisioner-B3HBN-2k.cjs.map} +1 -1
  27. package/dist/{backup-provisioner-BEXoHTuC.mjs → backup-provisioner-Xunq-p_8.mjs} +1 -1
  28. package/dist/{backup-provisioner-BEXoHTuC.mjs.map → backup-provisioner-Xunq-p_8.mjs.map} +1 -1
  29. package/dist/{bundler-c6QvGUBM.mjs → bundler-DkA4qzUD.mjs} +2 -2
  30. package/dist/{bundler-c6QvGUBM.mjs.map → bundler-DkA4qzUD.mjs.map} +1 -1
  31. package/dist/{bundler-BG6P249p.cjs → bundler-Dufq3uvI.cjs} +2 -2
  32. package/dist/{bundler-BG6P249p.cjs.map → bundler-Dufq3uvI.cjs.map} +1 -1
  33. package/dist/config.d.cts +2 -2
  34. package/dist/config.d.mts +2 -2
  35. package/dist/dokploy-api-BPYkG-ZZ.cjs +3 -0
  36. package/dist/dokploy-api-BiSzWM4v.mjs +3 -0
  37. package/dist/{dokploy-api-Dvyq-LsM.cjs → dokploy-api-CqOIKHme.cjs} +1 -1
  38. package/dist/{dokploy-api-Dvyq-LsM.cjs.map → dokploy-api-CqOIKHme.cjs.map} +1 -1
  39. package/dist/{dokploy-api-BIT0683o.mjs → dokploy-api-sfZFlWuE.mjs} +1 -1
  40. package/dist/{dokploy-api-BIT0683o.mjs.map → dokploy-api-sfZFlWuE.mjs.map} +1 -1
  41. package/dist/{encryption-CipsS0KW.cjs → encryption-BVS7Vmzs.cjs} +1 -1
  42. package/dist/encryption-Bd_m4q3g.mjs +3 -0
  43. package/dist/{encryption-DSkIlEZ6.mjs → encryption-CO5Q4Bhf.mjs} +1 -1
  44. package/dist/{encryption-DSkIlEZ6.mjs.map → encryption-CO5Q4Bhf.mjs.map} +1 -1
  45. package/dist/{encryption-DgKS-Dv9.cjs → encryption-MfXAiFTm.cjs} +1 -1
  46. package/dist/{encryption-DgKS-Dv9.cjs.map → encryption-MfXAiFTm.cjs.map} +1 -1
  47. package/dist/{index-BBvGMG_A.d.mts → index-Bw548Bta.d.mts} +2 -2
  48. package/dist/{index-Dz2a7xQU.d.cts.map → index-Bw548Bta.d.mts.map} +1 -1
  49. package/dist/{index-Dz2a7xQU.d.cts → index-DTpkeFb8.d.cts} +2 -2
  50. package/dist/{index-BBvGMG_A.d.mts.map → index-DTpkeFb8.d.cts.map} +1 -1
  51. package/dist/index.cjs +863 -1330
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.mjs +990 -1457
  54. package/dist/index.mjs.map +1 -1
  55. package/dist/{openapi-DHp49UJV.cjs → openapi-BzNOC7_i.cjs} +14 -2
  56. package/dist/openapi-BzNOC7_i.cjs.map +1 -0
  57. package/dist/{openapi-DF9GyhyZ.mjs → openapi-Cfwi0fqF.mjs} +14 -2
  58. package/dist/openapi-Cfwi0fqF.mjs.map +1 -0
  59. package/dist/{openapi-react-query-C4UdILaI.mjs → openapi-react-query-C4iIvpPf.mjs} +1 -1
  60. package/dist/{openapi-react-query-C4UdILaI.mjs.map → openapi-react-query-C4iIvpPf.mjs.map} +1 -1
  61. package/dist/{openapi-react-query-DYbBq-WJ.cjs → openapi-react-query-CYWik72n.cjs} +1 -1
  62. package/dist/{openapi-react-query-DYbBq-WJ.cjs.map → openapi-react-query-CYWik72n.cjs.map} +1 -1
  63. package/dist/openapi-react-query.cjs +1 -1
  64. package/dist/openapi-react-query.mjs +1 -1
  65. package/dist/openapi.cjs +1 -1
  66. package/dist/openapi.d.cts +1 -1
  67. package/dist/openapi.d.mts +1 -1
  68. package/dist/openapi.mjs +1 -1
  69. package/dist/{reconcile-CYGmqXvq.mjs → reconcile-B2FoJE0a.mjs} +1 -1
  70. package/dist/{reconcile-CYGmqXvq.mjs.map → reconcile-B2FoJE0a.mjs.map} +1 -1
  71. package/dist/{reconcile-D93YG7G5.cjs → reconcile-BzqKJ2Rt.cjs} +1 -1
  72. package/dist/{reconcile-D93YG7G5.cjs.map → reconcile-BzqKJ2Rt.cjs.map} +1 -1
  73. package/dist/setup-DDXUXzzl.mjs +765 -0
  74. package/dist/setup-DDXUXzzl.mjs.map +1 -0
  75. package/dist/setup-DLwhfyt-.cjs +8 -0
  76. package/dist/setup-Dh-B7YTX.mjs +8 -0
  77. package/dist/setup-WZ-xqAQO.cjs +849 -0
  78. package/dist/setup-WZ-xqAQO.cjs.map +1 -0
  79. package/dist/{sync-COnAugP-.mjs → sync-B5UHXwRM.mjs} +1 -1
  80. package/dist/sync-BLA94rgC.cjs +4 -0
  81. package/dist/{sync-D_NowTkZ.mjs → sync-CSooqRQ8.mjs} +1 -1
  82. package/dist/{sync-D_NowTkZ.mjs.map → sync-CSooqRQ8.mjs.map} +1 -1
  83. package/dist/{sync-DGXXSk2v.cjs → sync-o_CtEbvR.cjs} +1 -1
  84. package/dist/{sync-DGXXSk2v.cjs.map → sync-o_CtEbvR.cjs.map} +1 -1
  85. package/dist/{types-DdHfUbxk.d.cts → types-B3GQUg3k.d.mts} +3 -1
  86. package/dist/{types-DdHfUbxk.d.cts.map → types-B3GQUg3k.d.mts.map} +1 -1
  87. package/dist/{types-OszPdw9m.d.mts → types-BWQDiooe.d.cts} +3 -1
  88. package/dist/{types-OszPdw9m.d.mts.map → types-BWQDiooe.d.cts.map} +1 -1
  89. package/dist/workspace/index.d.cts +2 -2
  90. package/dist/workspace/index.d.mts +2 -2
  91. package/package.json +6 -5
  92. package/src/build/__tests__/index-new.spec.ts +3 -1
  93. package/src/build/__tests__/manifests.spec.ts +2 -2
  94. package/src/build/index.ts +61 -21
  95. package/src/build/manifests.ts +62 -5
  96. package/src/dev/index.ts +25 -11
  97. package/src/generators/EndpointGenerator.ts +14 -2
  98. package/src/generators/QueueGenerator.ts +245 -0
  99. package/src/generators/SubscriberGenerator.ts +5 -0
  100. package/src/generators/TopicGenerator.ts +45 -0
  101. package/src/generators/__tests__/QueueGenerator.spec.ts +141 -0
  102. package/src/generators/index.ts +2 -0
  103. package/src/index.ts +4 -0
  104. package/src/init/versions.ts +6 -6
  105. package/src/setup/__tests__/auto-setup.spec.ts +147 -0
  106. package/src/setup/index.ts +46 -6
  107. package/src/test/index.ts +17 -0
  108. package/src/types.ts +14 -51
  109. package/dist/CachedStateProvider-CC0DyO09.mjs +0 -3
  110. package/dist/dokploy-api-BOx71j6F.cjs +0 -3
  111. package/dist/dokploy-api-BZGk-lkD.mjs +0 -3
  112. package/dist/encryption-Db2zvGqM.mjs +0 -3
  113. package/dist/openapi-DF9GyhyZ.mjs.map +0 -1
  114. package/dist/openapi-DHp49UJV.cjs.map +0 -1
  115. package/dist/sync-D1Pa30oV.cjs +0 -4
@@ -1 +1 @@
1
- {"version":3,"file":"Route53Provider-QoPgcXxn.mjs","names":["options: Route53ProviderOptions","domain: string","type: string","validTypes: DnsRecordType[]","recordName: string","records: DnsRecord[]","nextRecordName: string | undefined","nextRecordType: RRType | undefined","records: UpsertDnsRecord[]","results: UpsertResult[]","records: DeleteDnsRecord[]","results: DeleteResult[]"],"sources":["../src/deploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\t// Route53 is a global service, default to us-east-1 if no region specified\n\t\tconst region = options.region ?? process.env.AWS_REGION ?? 'us-east-1';\n\n\t\tthis.client = new Route53Client({\n\t\t\tregion,\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to find the ones to delete\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tif (!existing) {\n\t\t\t\t\t// Record doesn't exist - already deleted\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'DELETE' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: existing.ttl,\n\t\t\t\t\t\tResourceRecords: existing.values.map((v) => ({ Value: v })),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: true,\n\t\t\t\t\tnotFound: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\t\tComment: 'Delete by gkm undeploy',\n\t\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tawait this.client.send(command);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mark all records in this batch as failed\n\t\t\t\t\tfor (const result of results.slice(-changes.length)) {\n\t\t\t\t\t\tif (result.deleted) {\n\t\t\t\t\t\t\tresult.deleted = false;\n\t\t\t\t\t\t\tresult.error = String(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Route53Provider-tlgBzrRQ.mjs","names":["options: Route53ProviderOptions","domain: string","type: string","validTypes: DnsRecordType[]","recordName: string","records: DnsRecord[]","nextRecordName: string | undefined","nextRecordType: RRType | undefined","records: UpsertDnsRecord[]","results: UpsertResult[]","records: DeleteDnsRecord[]","results: DeleteResult[]"],"sources":["../src/deploy/dns/Route53Provider.ts"],"sourcesContent":["/**\n * Route53 DNS Provider\n *\n * Implements DnsProvider interface using AWS Route53.\n */\n\nimport {\n\tChangeResourceRecordSetsCommand,\n\tListHostedZonesByNameCommand,\n\tListResourceRecordSetsCommand,\n\tRoute53Client,\n\ttype RRType,\n} from '@aws-sdk/client-route-53';\nimport { fromIni } from '@aws-sdk/credential-providers';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordType,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\n\nexport interface Route53ProviderOptions {\n\t/** AWS region (optional - uses AWS_REGION env var if not provided) */\n\tregion?: string;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** Hosted zone ID (optional - auto-detected from domain if not provided) */\n\thostedZoneId?: string;\n\t/** Custom endpoint for testing with localstack */\n\tendpoint?: string;\n}\n\n/**\n * Route53 DNS provider implementation.\n *\n * Uses AWS default credential chain for authentication.\n * Region can be specified or will use AWS_REGION/AWS_DEFAULT_REGION env vars.\n * Profile can be specified to use a named profile from ~/.aws/credentials.\n */\nexport class Route53Provider implements DnsProvider {\n\treadonly name = 'route53';\n\tprivate client: Route53Client;\n\tprivate hostedZoneId?: string;\n\tprivate hostedZoneCache: Map<string, string> = new Map();\n\n\tconstructor(options: Route53ProviderOptions = {}) {\n\t\t// Route53 is a global service, default to us-east-1 if no region specified\n\t\tconst region = options.region ?? process.env.AWS_REGION ?? 'us-east-1';\n\n\t\tthis.client = new Route53Client({\n\t\t\tregion,\n\t\t\t...(options.endpoint && { endpoint: options.endpoint }),\n\t\t\t...(options.profile && {\n\t\t\t\tcredentials: fromIni({ profile: options.profile }),\n\t\t\t}),\n\t\t});\n\t\tthis.hostedZoneId = options.hostedZoneId;\n\t}\n\n\t/**\n\t * Get the hosted zone ID for a domain.\n\t * Uses cache to avoid repeated API calls.\n\t */\n\tprivate async getHostedZoneId(domain: string): Promise<string> {\n\t\t// Use configured zone ID if provided\n\t\tif (this.hostedZoneId) {\n\t\t\treturn this.hostedZoneId;\n\t\t}\n\n\t\t// Check cache\n\t\tif (this.hostedZoneCache.has(domain)) {\n\t\t\treturn this.hostedZoneCache.get(domain)!;\n\t\t}\n\n\t\t// Auto-detect from domain\n\t\tconst command = new ListHostedZonesByNameCommand({\n\t\t\tDNSName: domain,\n\t\t\tMaxItems: 1,\n\t\t});\n\n\t\tconst response = await this.client.send(command);\n\t\tconst zones = response.HostedZones ?? [];\n\n\t\t// Find exact match (domain with trailing dot)\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst zone = zones.find((z) => z.Name === normalizedDomain);\n\n\t\tif (!zone?.Id) {\n\t\t\tthrow new Error(\n\t\t\t\t`No hosted zone found for domain: ${domain}. Create one in Route53 or provide hostedZoneId in config.`,\n\t\t\t);\n\t\t}\n\n\t\t// Zone ID comes as \"/hostedzone/Z1234567890\" - extract just the ID\n\t\tconst zoneId = zone.Id.replace('/hostedzone/', '');\n\t\tthis.hostedZoneCache.set(domain, zoneId);\n\t\treturn zoneId;\n\t}\n\n\t/**\n\t * Convert Route53 record type to our DnsRecordType.\n\t * Excludes NS and SOA which are auto-managed by Route53 for the zone.\n\t */\n\tprivate toRecordType(type: string): DnsRecordType | null {\n\t\t// Exclude NS and SOA which are auto-managed zone records\n\t\tconst managedTypes = ['NS', 'SOA'];\n\t\tif (managedTypes.includes(type)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst validTypes: DnsRecordType[] = [\n\t\t\t'A',\n\t\t\t'AAAA',\n\t\t\t'CNAME',\n\t\t\t'MX',\n\t\t\t'TXT',\n\t\t\t'SRV',\n\t\t\t'CAA',\n\t\t];\n\t\treturn validTypes.includes(type as DnsRecordType)\n\t\t\t? (type as DnsRecordType)\n\t\t\t: null;\n\t}\n\n\t/**\n\t * Extract subdomain from full record name relative to domain.\n\t */\n\tprivate extractSubdomain(recordName: string, domain: string): string {\n\t\tconst normalizedDomain = domain.endsWith('.') ? domain : `${domain}.`;\n\t\tconst normalizedName = recordName.endsWith('.')\n\t\t\t? recordName\n\t\t\t: `${recordName}.`;\n\n\t\tif (normalizedName === normalizedDomain) {\n\t\t\treturn '@';\n\t\t}\n\n\t\t// Remove the domain suffix\n\t\tconst subdomain = normalizedName.replace(`.${normalizedDomain}`, '');\n\t\treturn subdomain.replace(/\\.$/, ''); // Remove trailing dot if any\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst records: DnsRecord[] = [];\n\n\t\tlet nextRecordName: string | undefined;\n\t\tlet nextRecordType: RRType | undefined;\n\n\t\t// Paginate through all records\n\t\tdo {\n\t\t\tconst command = new ListResourceRecordSetsCommand({\n\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\tStartRecordName: nextRecordName,\n\t\t\t\tStartRecordType: nextRecordType,\n\t\t\t\tMaxItems: 100,\n\t\t\t});\n\n\t\t\tconst response = await this.client.send(command);\n\n\t\t\tfor (const recordSet of response.ResourceRecordSets ?? []) {\n\t\t\t\tconst type = this.toRecordType(recordSet.Type ?? '');\n\t\t\t\tif (!type || !recordSet.Name) continue;\n\n\t\t\t\tconst values = (recordSet.ResourceRecords ?? [])\n\t\t\t\t\t.map((r) => r.Value)\n\t\t\t\t\t.filter((v): v is string => !!v);\n\n\t\t\t\trecords.push({\n\t\t\t\t\tname: this.extractSubdomain(recordSet.Name, domain),\n\t\t\t\t\ttype,\n\t\t\t\t\tttl: recordSet.TTL ?? 300,\n\t\t\t\t\tvalues,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (response.IsTruncated) {\n\t\t\t\tnextRecordName = response.NextRecordName;\n\t\t\t\tnextRecordType = response.NextRecordType;\n\t\t\t} else {\n\t\t\t\tnextRecordName = undefined;\n\t\t\t}\n\t\t} while (nextRecordName);\n\n\t\treturn records;\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to determine if creating or updating\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tconst existingValue = existing?.values?.[0];\n\n\t\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tcreated: false,\n\t\t\t\t\t\tunchanged: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'UPSERT' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: record.ttl,\n\t\t\t\t\t\tResourceRecords: [{ Value: record.value }],\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: !existing,\n\t\t\t\t\tunchanged: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\tComment: 'Upsert by gkm deploy',\n\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tawait this.client.send(command);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst zoneId = await this.getHostedZoneId(domain);\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to find the ones to delete\n\t\tconst existingRecords = await this.getRecords(domain);\n\n\t\t// Process records in batches (Route53 allows max 1000 changes per request)\n\t\tconst batchSize = 100;\n\t\tfor (let i = 0; i < records.length; i += batchSize) {\n\t\t\tconst batch = records.slice(i, i + batchSize);\n\t\t\tconst changes = [];\n\n\t\t\tfor (const record of batch) {\n\t\t\t\tconst existing = existingRecords.find(\n\t\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t\t);\n\n\t\t\t\tif (!existing) {\n\t\t\t\t\t// Record doesn't exist - already deleted\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: true,\n\t\t\t\t\t});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Build full record name\n\t\t\t\tconst recordName =\n\t\t\t\t\trecord.name === '@' ? domain : `${record.name}.${domain}`;\n\n\t\t\t\tchanges.push({\n\t\t\t\t\tAction: 'DELETE' as const,\n\t\t\t\t\tResourceRecordSet: {\n\t\t\t\t\t\tName: recordName,\n\t\t\t\t\t\tType: record.type,\n\t\t\t\t\t\tTTL: existing.ttl,\n\t\t\t\t\t\tResourceRecords: existing.values.map((v) => ({ Value: v })),\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: true,\n\t\t\t\t\tnotFound: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Execute batch if there are changes\n\t\t\tif (changes.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\tconst command = new ChangeResourceRecordSetsCommand({\n\t\t\t\t\t\tHostedZoneId: zoneId,\n\t\t\t\t\t\tChangeBatch: {\n\t\t\t\t\t\t\tComment: 'Delete by gkm undeploy',\n\t\t\t\t\t\t\tChanges: changes,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\tawait this.client.send(command);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mark all records in this batch as failed\n\t\t\t\t\tfor (const result of results.slice(-changes.length)) {\n\t\t\t\t\t\tif (result.deleted) {\n\t\t\t\t\t\t\tresult.deleted = false;\n\t\t\t\t\t\t\tresult.error = String(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -63,4 +63,4 @@ var SSMStateProvider = class SSMStateProvider {
63
63
 
64
64
  //#endregion
65
65
  export { SSMStateProvider };
66
- //# sourceMappingURL=SSMStateProvider-CksOTB8M.mjs.map
66
+ //# sourceMappingURL=SSMStateProvider-BlrTiV5m.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SSMStateProvider-CksOTB8M.mjs","names":["workspaceName: string","client: SSMClient","options: SSMStateProviderOptions","clientConfig: SSMClientConfig","stage: string","state: DokployStageState"],"sources":["../src/deploy/SSMStateProvider.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store State Provider\n *\n * Stores deployment state as SecureString parameters in AWS SSM.\n * Uses AWS-managed KMS key for encryption (free tier).\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/state\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion, StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\nexport interface SSMStateProviderOptions {\n\t/** Workspace name (used in parameter path) */\n\tworkspaceName: string;\n\t/** AWS region */\n\tregion?: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** AWS credentials (optional - uses default credential chain if not provided) */\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** Custom endpoint (for LocalStack or other S3-compatible services) */\n\tendpoint?: string;\n}\n\n/**\n * AWS SSM Parameter Store state provider.\n *\n * Stores state as encrypted SecureString parameters.\n * Parameter path: /gkm/{workspaceName}/{stage}/state\n */\nexport class SSMStateProvider implements StateProvider {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {}\n\n\t/**\n\t * Create an SSMStateProvider with a new SSMClient.\n\t */\n\tstatic create(options: SSMStateProviderOptions): SSMStateProvider {\n\t\tconst clientConfig: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\n\t\t// Use profile credentials if specified, otherwise use provided credentials or default chain\n\t\tif (options.profile) {\n\t\t\t// Dynamic import to avoid requiring @aws-sdk/credential-providers when not using profiles\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tclientConfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tclientConfig.credentials = options.credentials;\n\t\t}\n\n\t\tconst client = new SSMClient(clientConfig);\n\t\treturn new SSMStateProvider(options.workspaceName, client);\n\t}\n\n\t/**\n\t * Get the SSM parameter name for a stage.\n\t */\n\tprivate getParameterName(stage: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/state`;\n\t}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: parameterName,\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Parameter?.Value) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn JSON.parse(response.Parameter.Value) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// Parameter doesn't exist - return null (new deployment)\n\t\t\tif (error instanceof ParameterNotFound) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Re-throw other errors (permission denied, network, etc.)\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tValue: JSON.stringify(state),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `GKM deployment state for ${this.workspaceName}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;AAsCA,IAAa,mBAAb,MAAa,iBAA0C;CACtD,YACUA,eACQC,QAChB;EAFQ;EACQ;CACd;;;;CAKJ,OAAO,OAAOC,SAAoD;EACjE,MAAMC,eAAgC;GACrC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;EAClB;AAGD,MAAI,QAAQ,SAAS;GAEpB,MAAM,EAAE,SAAS,GAAG,UAAQ,gCAAgC;AAC5D,gBAAa,cAAc,QAAQ,EAAE,SAAS,QAAQ,QAAS,EAAC;EAChE,WAAU,QAAQ,YAClB,cAAa,cAAc,QAAQ;EAGpC,MAAM,SAAS,IAAI,UAAU;AAC7B,SAAO,IAAI,iBAAiB,QAAQ,eAAe;CACnD;;;;CAKD,AAAQ,iBAAiBC,OAAuB;AAC/C,UAAQ,OAAO,KAAK,cAAc,GAAG,MAAM;CAC3C;CAED,MAAM,KAAKA,OAAkD;EAC5D,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAElD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAI,oBAAoB;IACvB,MAAM;IACN,gBAAgB;GAChB,GACD;AAED,QAAK,SAAS,WAAW,MACxB,QAAO;AAGR,UAAO,KAAK,MAAM,SAAS,UAAU,MAAM;EAC3C,SAAQ,OAAO;AAEf,OAAI,iBAAiB,kBACpB,QAAO;AAIR,SAAM;EACN;CACD;CAED,MAAM,MAAMA,OAAeC,OAAyC;EACnE,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAGlD,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,KAAK,OAAO,KACjB,IAAI,oBAAoB;GACvB,MAAM;GACN,OAAO,KAAK,UAAU,MAAM;GAC5B,MAAM;GACN,WAAW;GACX,cAAc,2BAA2B,KAAK,cAAc,GAAG,MAAM;EACrE,GACD;CACD;AACD"}
1
+ {"version":3,"file":"SSMStateProvider-BlrTiV5m.mjs","names":["workspaceName: string","client: SSMClient","options: SSMStateProviderOptions","clientConfig: SSMClientConfig","stage: string","state: DokployStageState"],"sources":["../src/deploy/SSMStateProvider.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store State Provider\n *\n * Stores deployment state as SecureString parameters in AWS SSM.\n * Uses AWS-managed KMS key for encryption (free tier).\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/state\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion, StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\nexport interface SSMStateProviderOptions {\n\t/** Workspace name (used in parameter path) */\n\tworkspaceName: string;\n\t/** AWS region */\n\tregion?: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** AWS credentials (optional - uses default credential chain if not provided) */\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** Custom endpoint (for LocalStack or other S3-compatible services) */\n\tendpoint?: string;\n}\n\n/**\n * AWS SSM Parameter Store state provider.\n *\n * Stores state as encrypted SecureString parameters.\n * Parameter path: /gkm/{workspaceName}/{stage}/state\n */\nexport class SSMStateProvider implements StateProvider {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {}\n\n\t/**\n\t * Create an SSMStateProvider with a new SSMClient.\n\t */\n\tstatic create(options: SSMStateProviderOptions): SSMStateProvider {\n\t\tconst clientConfig: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\n\t\t// Use profile credentials if specified, otherwise use provided credentials or default chain\n\t\tif (options.profile) {\n\t\t\t// Dynamic import to avoid requiring @aws-sdk/credential-providers when not using profiles\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tclientConfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tclientConfig.credentials = options.credentials;\n\t\t}\n\n\t\tconst client = new SSMClient(clientConfig);\n\t\treturn new SSMStateProvider(options.workspaceName, client);\n\t}\n\n\t/**\n\t * Get the SSM parameter name for a stage.\n\t */\n\tprivate getParameterName(stage: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/state`;\n\t}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: parameterName,\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Parameter?.Value) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn JSON.parse(response.Parameter.Value) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// Parameter doesn't exist - return null (new deployment)\n\t\t\tif (error instanceof ParameterNotFound) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Re-throw other errors (permission denied, network, etc.)\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tValue: JSON.stringify(state),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `GKM deployment state for ${this.workspaceName}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;AAsCA,IAAa,mBAAb,MAAa,iBAA0C;CACtD,YACUA,eACQC,QAChB;EAFQ;EACQ;CACd;;;;CAKJ,OAAO,OAAOC,SAAoD;EACjE,MAAMC,eAAgC;GACrC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;EAClB;AAGD,MAAI,QAAQ,SAAS;GAEpB,MAAM,EAAE,SAAS,GAAG,UAAQ,gCAAgC;AAC5D,gBAAa,cAAc,QAAQ,EAAE,SAAS,QAAQ,QAAS,EAAC;EAChE,WAAU,QAAQ,YAClB,cAAa,cAAc,QAAQ;EAGpC,MAAM,SAAS,IAAI,UAAU;AAC7B,SAAO,IAAI,iBAAiB,QAAQ,eAAe;CACnD;;;;CAKD,AAAQ,iBAAiBC,OAAuB;AAC/C,UAAQ,OAAO,KAAK,cAAc,GAAG,MAAM;CAC3C;CAED,MAAM,KAAKA,OAAkD;EAC5D,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAElD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAI,oBAAoB;IACvB,MAAM;IACN,gBAAgB;GAChB,GACD;AAED,QAAK,SAAS,WAAW,MACxB,QAAO;AAGR,UAAO,KAAK,MAAM,SAAS,UAAU,MAAM;EAC3C,SAAQ,OAAO;AAEf,OAAI,iBAAiB,kBACpB,QAAO;AAIR,SAAM;EACN;CACD;CAED,MAAM,MAAMA,OAAeC,OAAyC;EACnE,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAGlD,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,KAAK,OAAO,KACjB,IAAI,oBAAoB;GACvB,MAAM;GACN,OAAO,KAAK,UAAU,MAAM;GAC5B,MAAM;GACN,WAAW;GACX,cAAc,2BAA2B,KAAK,cAAc,GAAG,MAAM;EACrE,GACD;CACD;AACD"}
@@ -63,4 +63,4 @@ var SSMStateProvider = class SSMStateProvider {
63
63
 
64
64
  //#endregion
65
65
  exports.SSMStateProvider = SSMStateProvider;
66
- //# sourceMappingURL=SSMStateProvider-CT8tjl9o.cjs.map
66
+ //# sourceMappingURL=SSMStateProvider-Bt6r8CTL.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"SSMStateProvider-CT8tjl9o.cjs","names":["workspaceName: string","client: SSMClient","options: SSMStateProviderOptions","clientConfig: SSMClientConfig","SSMClient","stage: string","GetParameterCommand","ParameterNotFound","state: DokployStageState","PutParameterCommand"],"sources":["../src/deploy/SSMStateProvider.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store State Provider\n *\n * Stores deployment state as SecureString parameters in AWS SSM.\n * Uses AWS-managed KMS key for encryption (free tier).\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/state\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion, StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\nexport interface SSMStateProviderOptions {\n\t/** Workspace name (used in parameter path) */\n\tworkspaceName: string;\n\t/** AWS region */\n\tregion?: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** AWS credentials (optional - uses default credential chain if not provided) */\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** Custom endpoint (for LocalStack or other S3-compatible services) */\n\tendpoint?: string;\n}\n\n/**\n * AWS SSM Parameter Store state provider.\n *\n * Stores state as encrypted SecureString parameters.\n * Parameter path: /gkm/{workspaceName}/{stage}/state\n */\nexport class SSMStateProvider implements StateProvider {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {}\n\n\t/**\n\t * Create an SSMStateProvider with a new SSMClient.\n\t */\n\tstatic create(options: SSMStateProviderOptions): SSMStateProvider {\n\t\tconst clientConfig: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\n\t\t// Use profile credentials if specified, otherwise use provided credentials or default chain\n\t\tif (options.profile) {\n\t\t\t// Dynamic import to avoid requiring @aws-sdk/credential-providers when not using profiles\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tclientConfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tclientConfig.credentials = options.credentials;\n\t\t}\n\n\t\tconst client = new SSMClient(clientConfig);\n\t\treturn new SSMStateProvider(options.workspaceName, client);\n\t}\n\n\t/**\n\t * Get the SSM parameter name for a stage.\n\t */\n\tprivate getParameterName(stage: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/state`;\n\t}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: parameterName,\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Parameter?.Value) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn JSON.parse(response.Parameter.Value) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// Parameter doesn't exist - return null (new deployment)\n\t\t\tif (error instanceof ParameterNotFound) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Re-throw other errors (permission denied, network, etc.)\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tValue: JSON.stringify(state),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `GKM deployment state for ${this.workspaceName}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;AAsCA,IAAa,mBAAb,MAAa,iBAA0C;CACtD,YACUA,eACQC,QAChB;EAFQ;EACQ;CACd;;;;CAKJ,OAAO,OAAOC,SAAoD;EACjE,MAAMC,eAAgC;GACrC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;EAClB;AAGD,MAAI,QAAQ,SAAS;GAEpB,MAAM,EAAE,SAAS,GAAG,QAAQ,gCAAgC;AAC5D,gBAAa,cAAc,QAAQ,EAAE,SAAS,QAAQ,QAAS,EAAC;EAChE,WAAU,QAAQ,YAClB,cAAa,cAAc,QAAQ;EAGpC,MAAM,SAAS,IAAIC,+BAAU;AAC7B,SAAO,IAAI,iBAAiB,QAAQ,eAAe;CACnD;;;;CAKD,AAAQ,iBAAiBC,OAAuB;AAC/C,UAAQ,OAAO,KAAK,cAAc,GAAG,MAAM;CAC3C;CAED,MAAM,KAAKA,OAAkD;EAC5D,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAElD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAIC,yCAAoB;IACvB,MAAM;IACN,gBAAgB;GAChB,GACD;AAED,QAAK,SAAS,WAAW,MACxB,QAAO;AAGR,UAAO,KAAK,MAAM,SAAS,UAAU,MAAM;EAC3C,SAAQ,OAAO;AAEf,OAAI,iBAAiBC,uCACpB,QAAO;AAIR,SAAM;EACN;CACD;CAED,MAAM,MAAMF,OAAeG,OAAyC;EACnE,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAGlD,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,KAAK,OAAO,KACjB,IAAIC,yCAAoB;GACvB,MAAM;GACN,OAAO,KAAK,UAAU,MAAM;GAC5B,MAAM;GACN,WAAW;GACX,cAAc,2BAA2B,KAAK,cAAc,GAAG,MAAM;EACrE,GACD;CACD;AACD"}
1
+ {"version":3,"file":"SSMStateProvider-Bt6r8CTL.cjs","names":["workspaceName: string","client: SSMClient","options: SSMStateProviderOptions","clientConfig: SSMClientConfig","SSMClient","stage: string","GetParameterCommand","ParameterNotFound","state: DokployStageState","PutParameterCommand"],"sources":["../src/deploy/SSMStateProvider.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store State Provider\n *\n * Stores deployment state as SecureString parameters in AWS SSM.\n * Uses AWS-managed KMS key for encryption (free tier).\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/state\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion, StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\nexport interface SSMStateProviderOptions {\n\t/** Workspace name (used in parameter path) */\n\tworkspaceName: string;\n\t/** AWS region */\n\tregion?: AwsRegion;\n\t/** AWS profile name (optional - uses default credential chain if not provided) */\n\tprofile?: string;\n\t/** AWS credentials (optional - uses default credential chain if not provided) */\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** Custom endpoint (for LocalStack or other S3-compatible services) */\n\tendpoint?: string;\n}\n\n/**\n * AWS SSM Parameter Store state provider.\n *\n * Stores state as encrypted SecureString parameters.\n * Parameter path: /gkm/{workspaceName}/{stage}/state\n */\nexport class SSMStateProvider implements StateProvider {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {}\n\n\t/**\n\t * Create an SSMStateProvider with a new SSMClient.\n\t */\n\tstatic create(options: SSMStateProviderOptions): SSMStateProvider {\n\t\tconst clientConfig: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\n\t\t// Use profile credentials if specified, otherwise use provided credentials or default chain\n\t\tif (options.profile) {\n\t\t\t// Dynamic import to avoid requiring @aws-sdk/credential-providers when not using profiles\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tclientConfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tclientConfig.credentials = options.credentials;\n\t\t}\n\n\t\tconst client = new SSMClient(clientConfig);\n\t\treturn new SSMStateProvider(options.workspaceName, client);\n\t}\n\n\t/**\n\t * Get the SSM parameter name for a stage.\n\t */\n\tprivate getParameterName(stage: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/state`;\n\t}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: parameterName,\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\tif (!response.Parameter?.Value) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn JSON.parse(response.Parameter.Value) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// Parameter doesn't exist - return null (new deployment)\n\t\t\tif (error instanceof ParameterNotFound) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Re-throw other errors (permission denied, network, etc.)\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst parameterName = this.getParameterName(stage);\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tValue: JSON.stringify(state),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `GKM deployment state for ${this.workspaceName}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;AAsCA,IAAa,mBAAb,MAAa,iBAA0C;CACtD,YACUA,eACQC,QAChB;EAFQ;EACQ;CACd;;;;CAKJ,OAAO,OAAOC,SAAoD;EACjE,MAAMC,eAAgC;GACrC,QAAQ,QAAQ;GAChB,UAAU,QAAQ;EAClB;AAGD,MAAI,QAAQ,SAAS;GAEpB,MAAM,EAAE,SAAS,GAAG,QAAQ,gCAAgC;AAC5D,gBAAa,cAAc,QAAQ,EAAE,SAAS,QAAQ,QAAS,EAAC;EAChE,WAAU,QAAQ,YAClB,cAAa,cAAc,QAAQ;EAGpC,MAAM,SAAS,IAAIC,+BAAU;AAC7B,SAAO,IAAI,iBAAiB,QAAQ,eAAe;CACnD;;;;CAKD,AAAQ,iBAAiBC,OAAuB;AAC/C,UAAQ,OAAO,KAAK,cAAc,GAAG,MAAM;CAC3C;CAED,MAAM,KAAKA,OAAkD;EAC5D,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAElD,MAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAIC,yCAAoB;IACvB,MAAM;IACN,gBAAgB;GAChB,GACD;AAED,QAAK,SAAS,WAAW,MACxB,QAAO;AAGR,UAAO,KAAK,MAAM,SAAS,UAAU,MAAM;EAC3C,SAAQ,OAAO;AAEf,OAAI,iBAAiBC,uCACpB,QAAO;AAIR,SAAM;EACN;CACD;CAED,MAAM,MAAMF,OAAeG,OAAyC;EACnE,MAAM,gBAAgB,KAAK,iBAAiB,MAAM;AAGlD,QAAM,iBAAiB,qBAAI,QAAO,aAAa;AAE/C,QAAM,KAAK,OAAO,KACjB,IAAIC,yCAAoB;GACvB,MAAM;GACN,OAAO,KAAK,UAAU,MAAM;GAC5B,MAAM;GACN,WAAW;GACX,cAAc,2BAA2B,KAAK,cAAc,GAAG,MAAM;EACrE,GACD;CACD;AACD"}
@@ -161,4 +161,4 @@ async function provisionBackupDestination(options) {
161
161
 
162
162
  //#endregion
163
163
  exports.provisionBackupDestination = provisionBackupDestination;
164
- //# sourceMappingURL=backup-provisioner-C4noe75O.cjs.map
164
+ //# sourceMappingURL=backup-provisioner-B3HBN-2k.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"backup-provisioner-C4noe75O.cjs","names":["name: string","region: string","profile?: string","endpoint?: string","config: S3ClientConfig & IAMClientConfig","S3Client","IAMClient","s3: S3Client","bucketName: string","HeadBucketCommand","iam: IAMClient","userName: string","GetUserCommand","options: ProvisionBackupOptions","createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t}","CreateBucketCommand","PutBucketVersioningCommand","CreateUserCommand","PutUserPolicyCommand","accessKeyId: string","secretAccessKey: string","CreateAccessKeyCommand"],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"backup-provisioner-B3HBN-2k.cjs","names":["name: string","region: string","profile?: string","endpoint?: string","config: S3ClientConfig & IAMClientConfig","S3Client","IAMClient","s3: S3Client","bucketName: string","HeadBucketCommand","iam: IAMClient","userName: string","GetUserCommand","options: ProvisionBackupOptions","createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t}","CreateBucketCommand","PutBucketVersioningCommand","CreateUserCommand","PutUserPolicyCommand","accessKeyId: string","secretAccessKey: string","CreateAccessKeyCommand"],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
@@ -160,4 +160,4 @@ async function provisionBackupDestination(options) {
160
160
 
161
161
  //#endregion
162
162
  export { provisionBackupDestination };
163
- //# sourceMappingURL=backup-provisioner-BEXoHTuC.mjs.map
163
+ //# sourceMappingURL=backup-provisioner-Xunq-p_8.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"backup-provisioner-BEXoHTuC.mjs","names":["name: string","region: string","profile?: string","endpoint?: string","config: S3ClientConfig & IAMClientConfig","s3: S3Client","bucketName: string","iam: IAMClient","userName: string","options: ProvisionBackupOptions","createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t}","accessKeyId: string","secretAccessKey: string"],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"backup-provisioner-Xunq-p_8.mjs","names":["name: string","region: string","profile?: string","endpoint?: string","config: S3ClientConfig & IAMClientConfig","s3: S3Client","bucketName: string","iam: IAMClient","userName: string","options: ProvisionBackupOptions","createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t}","accessKeyId: string","secretAccessKey: string"],"sources":["../src/deploy/backup-provisioner.ts"],"sourcesContent":["/**\n * Backup Destination Provisioner\n *\n * Creates AWS resources (S3 bucket, IAM user, access keys) and configures\n * Dokploy backup destinations for database backups.\n */\n\nimport {\n\tCreateAccessKeyCommand,\n\tCreateUserCommand,\n\tGetUserCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tPutUserPolicyCommand,\n} from '@aws-sdk/client-iam';\nimport {\n\ttype BucketLocationConstraint,\n\tCreateBucketCommand,\n\tHeadBucketCommand,\n\tPutBucketVersioningCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { BackupsConfig } from '../workspace/types.js';\nimport type { DokployApi } from './dokploy-api.js';\nimport type { BackupState } from './state.js';\n\nexport interface ProvisionBackupOptions {\n\t/** Dokploy API client */\n\tapi: DokployApi;\n\t/** Dokploy project ID */\n\tprojectId: string;\n\t/** Workspace name (used for resource naming) */\n\tprojectName: string;\n\t/** Deploy stage (e.g., 'production', 'staging') */\n\tstage: string;\n\t/** Backup configuration */\n\tconfig: BackupsConfig;\n\t/** Existing backup state (if any) */\n\texistingState?: BackupState;\n\t/** Logger for progress output */\n\tlogger: { log: (msg: string) => void };\n\t/** AWS endpoint override (for testing with LocalStack) */\n\tawsEndpoint?: string;\n}\n\n/**\n * Generate a random suffix for unique resource names\n */\nfunction randomSuffix(): string {\n\treturn Math.random().toString(36).substring(2, 8);\n}\n\n/**\n * Sanitize a name for AWS resources (lowercase alphanumeric and hyphens)\n */\nfunction sanitizeName(name: string): string {\n\treturn name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n}\n\n/**\n * Create AWS clients with optional profile credentials\n */\nasync function createAwsClients(\n\tregion: string,\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<{ s3: S3Client; iam: IAMClient }> {\n\tconst config: S3ClientConfig & IAMClientConfig = { region };\n\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\n\t// Support custom endpoint for testing (e.g., LocalStack)\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\t(config as S3ClientConfig).forcePathStyle = true;\n\t\t// Use test credentials when endpoint is specified\n\t\tconfig.credentials = {\n\t\t\taccessKeyId: 'test',\n\t\t\tsecretAccessKey: 'test',\n\t\t};\n\t}\n\n\treturn {\n\t\ts3: new S3Client(config),\n\t\tiam: new IAMClient(config),\n\t};\n}\n\n/**\n * Check if an S3 bucket exists\n */\nasync function bucketExists(\n\ts3: S3Client,\n\tbucketName: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucketName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name === 'NotFound') {\n\t\t\treturn false;\n\t\t}\n\t\t// 403 means bucket exists but we don't have access\n\t\tif (\n\t\t\t(error as { $metadata?: { httpStatusCode?: number } }).$metadata\n\t\t\t\t?.httpStatusCode === 403\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if an IAM user exists\n */\nasync function userExists(iam: IAMClient, userName: string): Promise<boolean> {\n\ttry {\n\t\tawait iam.send(new GetUserCommand({ UserName: userName }));\n\t\treturn true;\n\t} catch (error) {\n\t\tconst errorName = (error as { name?: string }).name;\n\t\t// AWS returns 'NoSuchEntity', LocalStack returns 'NoSuchEntityException'\n\t\tif (errorName === 'NoSuchEntity' || errorName === 'NoSuchEntityException') {\n\t\t\treturn false;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Provision backup destination for a deployment.\n *\n * Creates AWS resources (S3 bucket, IAM user) and Dokploy destination if needed.\n * Reuses existing resources from state when possible.\n */\nexport async function provisionBackupDestination(\n\toptions: ProvisionBackupOptions,\n): Promise<BackupState> {\n\tconst {\n\t\tapi,\n\t\tprojectName,\n\t\tstage,\n\t\tconfig,\n\t\texistingState,\n\t\tlogger,\n\t\tawsEndpoint,\n\t} = options;\n\n\t// If we have existing state, verify the Dokploy destination still exists\n\tif (existingState?.destinationId) {\n\t\ttry {\n\t\t\tawait api.getDestination(existingState.destinationId);\n\t\t\tlogger.log(' Using existing backup destination');\n\t\t\treturn existingState;\n\t\t} catch {\n\t\t\tlogger.log(' Existing destination not found, recreating...');\n\t\t}\n\t}\n\n\t// Create AWS clients\n\tconst aws = await createAwsClients(\n\t\tconfig.region,\n\t\tconfig.profile,\n\t\tawsEndpoint,\n\t);\n\tconst sanitizedProject = sanitizeName(projectName);\n\n\t// 1. Create or verify S3 bucket\n\tconst bucketName =\n\t\texistingState?.bucketName ??\n\t\t`${sanitizedProject}-${stage}-backups-${randomSuffix()}`;\n\n\tconst bucketAlreadyExists = await bucketExists(aws.s3, bucketName);\n\tif (!bucketAlreadyExists) {\n\t\tlogger.log(` Creating S3 bucket: ${bucketName}`);\n\n\t\t// CreateBucket needs LocationConstraint for non-us-east-1 regions\n\t\tconst createBucketParams: {\n\t\t\tBucket: string;\n\t\t\tCreateBucketConfiguration?: {\n\t\t\t\tLocationConstraint: BucketLocationConstraint;\n\t\t\t};\n\t\t} = {\n\t\t\tBucket: bucketName,\n\t\t};\n\t\tif (config.region !== 'us-east-1') {\n\t\t\tcreateBucketParams.CreateBucketConfiguration = {\n\t\t\t\tLocationConstraint: config.region as BucketLocationConstraint,\n\t\t\t};\n\t\t}\n\n\t\tawait aws.s3.send(new CreateBucketCommand(createBucketParams));\n\n\t\t// Enable versioning for backup integrity\n\t\tawait aws.s3.send(\n\t\t\tnew PutBucketVersioningCommand({\n\t\t\t\tBucket: bucketName,\n\t\t\t\tVersioningConfiguration: { Status: 'Enabled' },\n\t\t\t}),\n\t\t);\n\t} else {\n\t\tlogger.log(` Using existing S3 bucket: ${bucketName}`);\n\t}\n\n\t// 2. Create or verify IAM user\n\tconst iamUserName =\n\t\texistingState?.iamUserName ?? `dokploy-backup-${sanitizedProject}-${stage}`;\n\n\tconst iamUserAlreadyExists = await userExists(aws.iam, iamUserName);\n\tif (!iamUserAlreadyExists) {\n\t\tlogger.log(` Creating IAM user: ${iamUserName}`);\n\t\tawait aws.iam.send(new CreateUserCommand({ UserName: iamUserName }));\n\t} else {\n\t\tlogger.log(` Using existing IAM user: ${iamUserName}`);\n\t}\n\n\t// 3. Attach bucket policy to IAM user\n\tconst policyDocument = {\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: [\n\t\t\t\t\t's3:GetObject',\n\t\t\t\t\t's3:PutObject',\n\t\t\t\t\t's3:DeleteObject',\n\t\t\t\t\t's3:ListBucket',\n\t\t\t\t\t's3:GetBucketLocation',\n\t\t\t\t],\n\t\t\t\tResource: [\n\t\t\t\t\t`arn:aws:s3:::${bucketName}`,\n\t\t\t\t\t`arn:aws:s3:::${bucketName}/*`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t};\n\n\tlogger.log(' Updating IAM policy');\n\tawait aws.iam.send(\n\t\tnew PutUserPolicyCommand({\n\t\t\tUserName: iamUserName,\n\t\t\tPolicyName: 'DokployBackupAccess',\n\t\t\tPolicyDocument: JSON.stringify(policyDocument),\n\t\t}),\n\t);\n\n\t// 4. Create access key (or reuse existing if state has it and destination needs recreation)\n\tlet accessKeyId: string;\n\tlet secretAccessKey: string;\n\n\tif (existingState?.iamAccessKeyId && existingState?.iamSecretAccessKey) {\n\t\t// Reuse existing credentials\n\t\tlogger.log(' Using existing IAM access key');\n\t\taccessKeyId = existingState.iamAccessKeyId;\n\t\tsecretAccessKey = existingState.iamSecretAccessKey;\n\t} else {\n\t\t// Create new access key\n\t\tlogger.log(' Creating IAM access key');\n\t\tconst accessKeyResult = await aws.iam.send(\n\t\t\tnew CreateAccessKeyCommand({ UserName: iamUserName }),\n\t\t);\n\n\t\tif (!accessKeyResult.AccessKey) {\n\t\t\tthrow new Error('Failed to create IAM access key');\n\t\t}\n\n\t\taccessKeyId = accessKeyResult.AccessKey.AccessKeyId!;\n\t\tsecretAccessKey = accessKeyResult.AccessKey.SecretAccessKey!;\n\t}\n\n\t// 5. Create Dokploy destination\n\tconst destinationName = `${sanitizedProject}-${stage}-s3`;\n\tlogger.log(` Creating Dokploy destination: ${destinationName}`);\n\n\tconst { destination, created } = await api.findOrCreateDestination(\n\t\tdestinationName,\n\t\t{\n\t\t\taccessKey: accessKeyId,\n\t\t\tsecretAccessKey: secretAccessKey,\n\t\t\tbucket: bucketName,\n\t\t\tregion: config.region,\n\t\t},\n\t);\n\n\tif (created) {\n\t\tlogger.log(' ✓ Dokploy destination created');\n\t} else {\n\t\tlogger.log(' ✓ Using existing Dokploy destination');\n\t}\n\n\t// 6. Test connection\n\ttry {\n\t\tawait api.testDestinationConnection(destination.destinationId);\n\t\tlogger.log(' ✓ Destination connection verified');\n\t} catch (error) {\n\t\tlogger.log(\n\t\t\t` ⚠ Warning: Could not verify destination connection: ${error}`,\n\t\t);\n\t}\n\n\treturn {\n\t\tbucketName,\n\t\tbucketArn: `arn:aws:s3:::${bucketName}`,\n\t\tiamUserName,\n\t\tiamAccessKeyId: accessKeyId,\n\t\tiamSecretAccessKey: secretAccessKey,\n\t\tdestinationId: destination.destinationId,\n\t\tregion: config.region,\n\t\tcreatedAt: existingState?.createdAt ?? new Date().toISOString(),\n\t};\n}\n"],"mappings":";;;;;;;AAiDA,SAAS,eAAuB;AAC/B,QAAO,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,UAAU,GAAG,EAAE;AACjD;;;;AAKD,SAAS,aAAaA,MAAsB;AAC3C,QAAO,KAAK,aAAa,CAAC,QAAQ,eAAe,IAAI;AACrD;;;;AAKD,eAAe,iBACdC,QACAC,SACAC,UAC4C;CAC5C,MAAMC,SAA2C,EAAE,OAAQ;AAE3D,KAAI,SAAS;EACZ,MAAM,EAAE,SAAS,GAAG,MAAM,OAAO;AACjC,SAAO,cAAc,QAAQ,EAAE,QAAS,EAAC;CACzC;AAGD,KAAI,UAAU;AACb,SAAO,WAAW;AAClB,EAAC,OAA0B,iBAAiB;AAE5C,SAAO,cAAc;GACpB,aAAa;GACb,iBAAiB;EACjB;CACD;AAED,QAAO;EACN,IAAI,IAAI,SAAS;EACjB,KAAK,IAAI,UAAU;CACnB;AACD;;;;AAKD,eAAe,aACdC,IACAC,YACmB;AACnB,KAAI;AACH,QAAM,GAAG,KAAK,IAAI,kBAAkB,EAAE,QAAQ,WAAY,GAAE;AAC5D,SAAO;CACP,SAAQ,OAAO;AACf,MAAK,MAA4B,SAAS,WACzC,QAAO;AAGR,MACE,MAAsD,WACpD,mBAAmB,IAEtB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,eAAe,WAAWC,KAAgBC,UAAoC;AAC7E,KAAI;AACH,QAAM,IAAI,KAAK,IAAI,eAAe,EAAE,UAAU,SAAU,GAAE;AAC1D,SAAO;CACP,SAAQ,OAAO;EACf,MAAM,YAAa,MAA4B;AAE/C,MAAI,cAAc,kBAAkB,cAAc,wBACjD,QAAO;AAER,QAAM;CACN;AACD;;;;;;;AAQD,eAAsB,2BACrBC,SACuB;CACvB,MAAM,EACL,KACA,aACA,OACA,QACA,eACA,QACA,aACA,GAAG;AAGJ,KAAI,eAAe,cAClB,KAAI;AACH,QAAM,IAAI,eAAe,cAAc,cAAc;AACrD,SAAO,IAAI,uCAAuC;AAClD,SAAO;CACP,QAAO;AACP,SAAO,IAAI,mDAAmD;CAC9D;CAIF,MAAM,MAAM,MAAM,iBACjB,OAAO,QACP,OAAO,SACP,YACA;CACD,MAAM,mBAAmB,aAAa,YAAY;CAGlD,MAAM,aACL,eAAe,eACd,EAAE,iBAAiB,GAAG,MAAM,WAAW,cAAc,CAAC;CAExD,MAAM,sBAAsB,MAAM,aAAa,IAAI,IAAI,WAAW;AAClE,MAAK,qBAAqB;AACzB,SAAO,KAAK,yBAAyB,WAAW,EAAE;EAGlD,MAAMC,qBAKF,EACH,QAAQ,WACR;AACD,MAAI,OAAO,WAAW,YACrB,oBAAmB,4BAA4B,EAC9C,oBAAoB,OAAO,OAC3B;AAGF,QAAM,IAAI,GAAG,KAAK,IAAI,oBAAoB,oBAAoB;AAG9D,QAAM,IAAI,GAAG,KACZ,IAAI,2BAA2B;GAC9B,QAAQ;GACR,yBAAyB,EAAE,QAAQ,UAAW;EAC9C,GACD;CACD,MACA,QAAO,KAAK,+BAA+B,WAAW,EAAE;CAIzD,MAAM,cACL,eAAe,gBAAgB,iBAAiB,iBAAiB,GAAG,MAAM;CAE3E,MAAM,uBAAuB,MAAM,WAAW,IAAI,KAAK,YAAY;AACnE,MAAK,sBAAsB;AAC1B,SAAO,KAAK,wBAAwB,YAAY,EAAE;AAClD,QAAM,IAAI,IAAI,KAAK,IAAI,kBAAkB,EAAE,UAAU,YAAa,GAAE;CACpE,MACA,QAAO,KAAK,8BAA8B,YAAY,EAAE;CAIzD,MAAM,iBAAiB;EACtB,SAAS;EACT,WAAW,CACV;GACC,QAAQ;GACR,QAAQ;IACP;IACA;IACA;IACA;IACA;GACA;GACD,UAAU,EACR,eAAe,WAAW,IAC1B,eAAe,WAAW,GAC3B;EACD,CACD;CACD;AAED,QAAO,IAAI,yBAAyB;AACpC,OAAM,IAAI,IAAI,KACb,IAAI,qBAAqB;EACxB,UAAU;EACV,YAAY;EACZ,gBAAgB,KAAK,UAAU,eAAe;CAC9C,GACD;CAGD,IAAIC;CACJ,IAAIC;AAEJ,KAAI,eAAe,kBAAkB,eAAe,oBAAoB;AAEvE,SAAO,IAAI,mCAAmC;AAC9C,gBAAc,cAAc;AAC5B,oBAAkB,cAAc;CAChC,OAAM;AAEN,SAAO,IAAI,6BAA6B;EACxC,MAAM,kBAAkB,MAAM,IAAI,IAAI,KACrC,IAAI,uBAAuB,EAAE,UAAU,YAAa,GACpD;AAED,OAAK,gBAAgB,UACpB,OAAM,IAAI,MAAM;AAGjB,gBAAc,gBAAgB,UAAU;AACxC,oBAAkB,gBAAgB,UAAU;CAC5C;CAGD,MAAM,mBAAmB,EAAE,iBAAiB,GAAG,MAAM;AACrD,QAAO,KAAK,mCAAmC,gBAAgB,EAAE;CAEjE,MAAM,EAAE,aAAa,SAAS,GAAG,MAAM,IAAI,wBAC1C,iBACA;EACC,WAAW;EACM;EACjB,QAAQ;EACR,QAAQ,OAAO;CACf,EACD;AAED,KAAI,QACH,QAAO,IAAI,mCAAmC;KAE9C,QAAO,IAAI,0CAA0C;AAItD,KAAI;AACH,QAAM,IAAI,0BAA0B,YAAY,cAAc;AAC9D,SAAO,IAAI,uCAAuC;CAClD,SAAQ,OAAO;AACf,SAAO,KACL,yDAAyD,MAAM,EAChE;CACD;AAED,QAAO;EACN;EACA,YAAY,eAAe,WAAW;EACtC;EACA,gBAAgB;EAChB,oBAAoB;EACpB,eAAe,YAAY;EAC3B,QAAQ,OAAO;EACf,WAAW,eAAe,aAAa,qBAAI,QAAO,aAAa;CAC/D;AACD"}
@@ -59,7 +59,7 @@ async function bundleServer(options) {
59
59
  let masterKey;
60
60
  if (stage) {
61
61
  const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await import("./storage-Cs4WBsc4.mjs");
62
- const { encryptSecrets, generateDefineOptions } = await import("./encryption-Db2zvGqM.mjs");
62
+ const { encryptSecrets, generateDefineOptions } = await import("./encryption-Bd_m4q3g.mjs");
63
63
  let secrets = await readStageSecrets(stage);
64
64
  if (!secrets) {
65
65
  console.log(` Initializing secrets for stage "${stage}"...`);
@@ -132,4 +132,4 @@ async function bundleServer(options) {
132
132
 
133
133
  //#endregion
134
134
  export { bundleServer };
135
- //# sourceMappingURL=bundler-c6QvGUBM.mjs.map
135
+ //# sourceMappingURL=bundler-DkA4qzUD.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundler-c6QvGUBM.mjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundler-DkA4qzUD.mjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
@@ -60,7 +60,7 @@ async function bundleServer(options) {
60
60
  let masterKey;
61
61
  if (stage) {
62
62
  const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
63
- const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./encryption-CipsS0KW.cjs"));
63
+ const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./encryption-BVS7Vmzs.cjs"));
64
64
  let secrets = await readStageSecrets(stage);
65
65
  if (!secrets) {
66
66
  console.log(` Initializing secrets for stage "${stage}"...`);
@@ -133,4 +133,4 @@ async function bundleServer(options) {
133
133
 
134
134
  //#endregion
135
135
  exports.bundleServer = bundleServer;
136
- //# sourceMappingURL=bundler-BG6P249p.cjs.map
136
+ //# sourceMappingURL=bundler-Dufq3uvI.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundler-BG6P249p.cjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bundler-Dufq3uvI.cjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
package/dist/config.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { GkmConfig } from "./types-DdHfUbxk.cjs";
2
- import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-Dz2a7xQU.cjs";
1
+ import { GkmConfig } from "./types-BWQDiooe.cjs";
2
+ import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-DTpkeFb8.cjs";
3
3
 
4
4
  //#region src/config.d.ts
5
5
 
package/dist/config.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { GkmConfig } from "./types-OszPdw9m.mjs";
2
- import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-BBvGMG_A.mjs";
1
+ import { GkmConfig } from "./types-B3GQUg3k.mjs";
2
+ import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-Bw548Bta.mjs";
3
3
 
4
4
  //#region src/config.d.ts
5
5
 
@@ -0,0 +1,3 @@
1
+ const require_dokploy_api = require('./dokploy-api-CqOIKHme.cjs');
2
+
3
+ exports.DokployApi = require_dokploy_api.DokployApi;
@@ -0,0 +1,3 @@
1
+ import { DokployApi, DokployApiError } from "./dokploy-api-sfZFlWuE.mjs";
2
+
3
+ export { DokployApi };
@@ -606,4 +606,4 @@ Object.defineProperty(exports, 'DokployApiError', {
606
606
  return DokployApiError;
607
607
  }
608
608
  });
609
- //# sourceMappingURL=dokploy-api-Dvyq-LsM.cjs.map
609
+ //# sourceMappingURL=dokploy-api-CqOIKHme.cjs.map