@stacksjs/ts-cloud-core 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +321 -0
- package/package.json +31 -0
- package/src/advanced-features.test.ts +465 -0
- package/src/aws/cloudformation.ts +421 -0
- package/src/aws/cloudfront.ts +158 -0
- package/src/aws/credentials.test.ts +132 -0
- package/src/aws/credentials.ts +545 -0
- package/src/aws/index.ts +87 -0
- package/src/aws/s3.test.ts +188 -0
- package/src/aws/s3.ts +1088 -0
- package/src/aws/signature.test.ts +670 -0
- package/src/aws/signature.ts +1155 -0
- package/src/backup/disaster-recovery.test.ts +726 -0
- package/src/backup/disaster-recovery.ts +500 -0
- package/src/backup/index.ts +34 -0
- package/src/backup/manager.test.ts +498 -0
- package/src/backup/manager.ts +432 -0
- package/src/cicd/circleci.ts +430 -0
- package/src/cicd/github-actions.ts +424 -0
- package/src/cicd/gitlab-ci.ts +255 -0
- package/src/cicd/index.ts +8 -0
- package/src/cli/history.ts +396 -0
- package/src/cli/index.ts +10 -0
- package/src/cli/progress.ts +458 -0
- package/src/cli/repl.ts +454 -0
- package/src/cli/suggestions.ts +327 -0
- package/src/cli/table.test.ts +319 -0
- package/src/cli/table.ts +332 -0
- package/src/cloudformation/builder.test.ts +327 -0
- package/src/cloudformation/builder.ts +378 -0
- package/src/cloudformation/builders/api-gateway.ts +449 -0
- package/src/cloudformation/builders/cache.ts +334 -0
- package/src/cloudformation/builders/cdn.ts +278 -0
- package/src/cloudformation/builders/compute.ts +485 -0
- package/src/cloudformation/builders/database.ts +392 -0
- package/src/cloudformation/builders/functions.ts +343 -0
- package/src/cloudformation/builders/messaging.ts +140 -0
- package/src/cloudformation/builders/monitoring.ts +300 -0
- package/src/cloudformation/builders/network.ts +264 -0
- package/src/cloudformation/builders/queue.ts +147 -0
- package/src/cloudformation/builders/security.ts +399 -0
- package/src/cloudformation/builders/storage.ts +285 -0
- package/src/cloudformation/index.ts +30 -0
- package/src/cloudformation/types.ts +173 -0
- package/src/compliance/aws-config.ts +543 -0
- package/src/compliance/cloudtrail.ts +376 -0
- package/src/compliance/compliance.test.ts +423 -0
- package/src/compliance/guardduty.ts +446 -0
- package/src/compliance/index.ts +66 -0
- package/src/compliance/security-hub.ts +456 -0
- package/src/containers/build-optimization.ts +416 -0
- package/src/containers/containers.test.ts +508 -0
- package/src/containers/image-scanning.ts +360 -0
- package/src/containers/index.ts +9 -0
- package/src/containers/registry.ts +293 -0
- package/src/containers/service-mesh.ts +520 -0
- package/src/database/database.test.ts +762 -0
- package/src/database/index.ts +9 -0
- package/src/database/migrations.ts +444 -0
- package/src/database/performance.ts +528 -0
- package/src/database/replicas.ts +534 -0
- package/src/database/users.ts +494 -0
- package/src/dependency-graph.ts +143 -0
- package/src/deployment/ab-testing.ts +582 -0
- package/src/deployment/blue-green.ts +452 -0
- package/src/deployment/canary.ts +500 -0
- package/src/deployment/deployment.test.ts +526 -0
- package/src/deployment/index.ts +61 -0
- package/src/deployment/progressive.ts +62 -0
- package/src/dns/dns.test.ts +641 -0
- package/src/dns/dnssec.ts +315 -0
- package/src/dns/index.ts +8 -0
- package/src/dns/resolver.ts +496 -0
- package/src/dns/routing.ts +593 -0
- package/src/email/advanced/analytics.ts +445 -0
- package/src/email/advanced/index.ts +11 -0
- package/src/email/advanced/rules.ts +465 -0
- package/src/email/advanced/scheduling.ts +352 -0
- package/src/email/advanced/search.ts +412 -0
- package/src/email/advanced/shared-mailboxes.ts +404 -0
- package/src/email/advanced/templates.ts +455 -0
- package/src/email/advanced/threading.ts +281 -0
- package/src/email/analytics.ts +467 -0
- package/src/email/bounce-handling.ts +425 -0
- package/src/email/email.test.ts +431 -0
- package/src/email/handlers/__tests__/inbound.test.ts +38 -0
- package/src/email/handlers/__tests__/outbound.test.ts +37 -0
- package/src/email/handlers/converter.ts +227 -0
- package/src/email/handlers/feedback.ts +228 -0
- package/src/email/handlers/inbound.ts +169 -0
- package/src/email/handlers/outbound.ts +178 -0
- package/src/email/index.ts +15 -0
- package/src/email/reputation.ts +303 -0
- package/src/email/templates.ts +352 -0
- package/src/errors/index.test.ts +434 -0
- package/src/errors/index.ts +416 -0
- package/src/health-checks/index.ts +40 -0
- package/src/index.ts +360 -0
- package/src/intrinsic-functions.ts +118 -0
- package/src/lambda/concurrency.ts +330 -0
- package/src/lambda/destinations.ts +345 -0
- package/src/lambda/dlq.ts +425 -0
- package/src/lambda/index.ts +11 -0
- package/src/lambda/lambda.test.ts +840 -0
- package/src/lambda/layers.ts +263 -0
- package/src/lambda/versions.ts +376 -0
- package/src/lambda/vpc.ts +399 -0
- package/src/local/config.ts +114 -0
- package/src/local/index.ts +6 -0
- package/src/local/mock-aws.ts +351 -0
- package/src/modules/ai.ts +340 -0
- package/src/modules/api.ts +478 -0
- package/src/modules/auth.ts +805 -0
- package/src/modules/cache.ts +417 -0
- package/src/modules/cdn.ts +1062 -0
- package/src/modules/communication.ts +1094 -0
- package/src/modules/compute.ts +3348 -0
- package/src/modules/database.ts +554 -0
- package/src/modules/deployment.ts +1079 -0
- package/src/modules/dns.ts +337 -0
- package/src/modules/email.ts +1538 -0
- package/src/modules/filesystem.ts +515 -0
- package/src/modules/index.ts +32 -0
- package/src/modules/messaging.ts +486 -0
- package/src/modules/monitoring.ts +2086 -0
- package/src/modules/network.ts +664 -0
- package/src/modules/parameter-store.ts +325 -0
- package/src/modules/permissions.ts +1081 -0
- package/src/modules/phone.ts +494 -0
- package/src/modules/queue.ts +1260 -0
- package/src/modules/redirects.ts +464 -0
- package/src/modules/registry.ts +699 -0
- package/src/modules/search.ts +401 -0
- package/src/modules/secrets.ts +416 -0
- package/src/modules/security.ts +731 -0
- package/src/modules/sms.ts +389 -0
- package/src/modules/storage.ts +1120 -0
- package/src/modules/workflow.ts +680 -0
- package/src/multi-account/config.ts +521 -0
- package/src/multi-account/index.ts +7 -0
- package/src/multi-account/manager.ts +427 -0
- package/src/multi-region/cross-region.ts +410 -0
- package/src/multi-region/index.ts +8 -0
- package/src/multi-region/manager.ts +483 -0
- package/src/multi-region/regions.ts +435 -0
- package/src/network-security/index.ts +48 -0
- package/src/observability/index.ts +9 -0
- package/src/observability/logs.ts +522 -0
- package/src/observability/metrics.ts +460 -0
- package/src/observability/observability.test.ts +782 -0
- package/src/observability/synthetics.ts +568 -0
- package/src/observability/xray.ts +358 -0
- package/src/phone/advanced/analytics.ts +349 -0
- package/src/phone/advanced/callbacks.ts +428 -0
- package/src/phone/advanced/index.ts +8 -0
- package/src/phone/advanced/ivr-builder.ts +504 -0
- package/src/phone/advanced/recording.ts +310 -0
- package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
- package/src/phone/handlers/incoming-call.ts +117 -0
- package/src/phone/handlers/missed-call.ts +116 -0
- package/src/phone/handlers/voicemail.ts +179 -0
- package/src/phone/index.ts +9 -0
- package/src/presets/api-backend.ts +134 -0
- package/src/presets/data-pipeline.ts +204 -0
- package/src/presets/extend.test.ts +295 -0
- package/src/presets/extend.ts +297 -0
- package/src/presets/fullstack-app.ts +144 -0
- package/src/presets/index.ts +27 -0
- package/src/presets/jamstack.ts +135 -0
- package/src/presets/microservices.ts +167 -0
- package/src/presets/ml-api.ts +208 -0
- package/src/presets/nodejs-server.ts +104 -0
- package/src/presets/nodejs-serverless.ts +114 -0
- package/src/presets/realtime-app.ts +184 -0
- package/src/presets/static-site.ts +64 -0
- package/src/presets/traditional-web-app.ts +339 -0
- package/src/presets/wordpress.ts +138 -0
- package/src/preview/github.test.ts +249 -0
- package/src/preview/github.ts +297 -0
- package/src/preview/index.ts +37 -0
- package/src/preview/manager.test.ts +440 -0
- package/src/preview/manager.ts +326 -0
- package/src/preview/notifications.test.ts +582 -0
- package/src/preview/notifications.ts +341 -0
- package/src/queue/batch-processing.ts +402 -0
- package/src/queue/dlq-monitoring.ts +402 -0
- package/src/queue/fifo.ts +342 -0
- package/src/queue/index.ts +9 -0
- package/src/queue/management.ts +428 -0
- package/src/queue/queue.test.ts +429 -0
- package/src/resource-mgmt/index.ts +39 -0
- package/src/resource-naming.ts +62 -0
- package/src/s3/index.ts +523 -0
- package/src/schema/cloud-config.schema.json +554 -0
- package/src/schema/index.ts +68 -0
- package/src/security/certificate-manager.ts +492 -0
- package/src/security/index.ts +9 -0
- package/src/security/scanning.ts +545 -0
- package/src/security/secrets-manager.ts +476 -0
- package/src/security/secrets-rotation.ts +456 -0
- package/src/security/security.test.ts +738 -0
- package/src/sms/advanced/ab-testing.ts +389 -0
- package/src/sms/advanced/analytics.ts +336 -0
- package/src/sms/advanced/campaigns.ts +523 -0
- package/src/sms/advanced/chatbot.ts +224 -0
- package/src/sms/advanced/index.ts +10 -0
- package/src/sms/advanced/link-tracking.ts +248 -0
- package/src/sms/advanced/mms.ts +308 -0
- package/src/sms/handlers/__tests__/send.test.ts +40 -0
- package/src/sms/handlers/delivery-status.ts +133 -0
- package/src/sms/handlers/receive.ts +162 -0
- package/src/sms/handlers/send.ts +174 -0
- package/src/sms/index.ts +9 -0
- package/src/stack-diff.ts +389 -0
- package/src/static-site/index.ts +85 -0
- package/src/template-builder.ts +110 -0
- package/src/template-validator.ts +574 -0
- package/src/utils/cache.ts +291 -0
- package/src/utils/diff.ts +269 -0
- package/src/utils/hash.ts +227 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/parallel.ts +294 -0
- package/src/validators/credentials.test.ts +274 -0
- package/src/validators/credentials.ts +233 -0
- package/src/validators/quotas.test.ts +434 -0
- package/src/validators/quotas.ts +217 -0
- package/test/ai.test.ts +327 -0
- package/test/api.test.ts +511 -0
- package/test/auth.test.ts +632 -0
- package/test/cache.test.ts +406 -0
- package/test/cdn.test.ts +247 -0
- package/test/compute.test.ts +861 -0
- package/test/database.test.ts +523 -0
- package/test/deployment.test.ts +499 -0
- package/test/dns.test.ts +270 -0
- package/test/email.test.ts +439 -0
- package/test/filesystem.test.ts +382 -0
- package/test/integration.test.ts +350 -0
- package/test/messaging.test.ts +514 -0
- package/test/monitoring.test.ts +634 -0
- package/test/network.test.ts +425 -0
- package/test/permissions.test.ts +488 -0
- package/test/queue.test.ts +484 -0
- package/test/registry.test.ts +306 -0
- package/test/security.test.ts +462 -0
- package/test/storage.test.ts +463 -0
- package/test/template-validator.test.ts +559 -0
- package/test/workflow.test.ts +592 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
import { generatePreviewWorkflow, generateCleanupWorkflow, generateCostReportWorkflow } from './github'
|
|
3
|
+
|
|
4
|
+
describe('GitHub Workflow Generation', () => {
|
|
5
|
+
describe('generatePreviewWorkflow', () => {
|
|
6
|
+
it('should generate basic preview workflow', () => {
|
|
7
|
+
const workflow = generatePreviewWorkflow()
|
|
8
|
+
|
|
9
|
+
expect(workflow).toContain('name: Preview Environment')
|
|
10
|
+
expect(workflow).toContain('pull_request:')
|
|
11
|
+
expect(workflow).toContain('types: [opened, synchronize, reopened, closed]')
|
|
12
|
+
expect(workflow).toContain('bun run cloud env:preview')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should include custom workflow name', () => {
|
|
16
|
+
const workflow = generatePreviewWorkflow({
|
|
17
|
+
workflowName: 'Custom Preview',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expect(workflow).toContain('name: Custom Preview')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should configure trigger type', () => {
|
|
24
|
+
const workflow = generatePreviewWorkflow({
|
|
25
|
+
trigger: 'push',
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
expect(workflow).toContain('push:')
|
|
29
|
+
expect(workflow).not.toContain('pull_request:')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should configure branch filters', () => {
|
|
33
|
+
const workflow = generatePreviewWorkflow({
|
|
34
|
+
branches: ['main', 'develop', 'staging'],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(workflow).toContain('- main')
|
|
38
|
+
expect(workflow).toContain('- develop')
|
|
39
|
+
expect(workflow).toContain('- staging')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should configure AWS region', () => {
|
|
43
|
+
const workflow = generatePreviewWorkflow({
|
|
44
|
+
awsRegion: 'eu-west-1',
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
expect(workflow).toContain('AWS_REGION: eu-west-1')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should use IAM role when provided', () => {
|
|
51
|
+
const workflow = generatePreviewWorkflow({
|
|
52
|
+
awsRole: 'arn:aws:iam::123456789012:role/github-actions',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
expect(workflow).toContain('role-to-assume: arn:aws:iam::123456789012:role/github-actions')
|
|
56
|
+
expect(workflow).not.toContain('AWS_ACCESS_KEY_ID')
|
|
57
|
+
expect(workflow).not.toContain('AWS_SECRET_ACCESS_KEY')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should use access keys when IAM role not provided', () => {
|
|
61
|
+
const workflow = generatePreviewWorkflow()
|
|
62
|
+
|
|
63
|
+
expect(workflow).toContain('AWS_ACCESS_KEY_ID')
|
|
64
|
+
expect(workflow).toContain('AWS_SECRET_ACCESS_KEY')
|
|
65
|
+
expect(workflow).not.toContain('role-to-assume')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should configure TTL', () => {
|
|
69
|
+
const workflow = generatePreviewWorkflow({
|
|
70
|
+
ttl: 48,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
expect(workflow).toContain('TTL_HOURS: 48')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should include deploy step', () => {
|
|
77
|
+
const workflow = generatePreviewWorkflow()
|
|
78
|
+
|
|
79
|
+
expect(workflow).toContain('Deploy preview environment')
|
|
80
|
+
expect(workflow).toContain('--branch=${{ github.head_ref }}')
|
|
81
|
+
expect(workflow).toContain('--pr=${{ github.event.pull_request.number }}')
|
|
82
|
+
expect(workflow).toContain('--commit=${{ github.event.pull_request.head.sha }}')
|
|
83
|
+
expect(workflow).toContain('--ttl=${{ env.TTL_HOURS }}')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should include PR comment step', () => {
|
|
87
|
+
const workflow = generatePreviewWorkflow()
|
|
88
|
+
|
|
89
|
+
expect(workflow).toContain('Comment PR with preview URL')
|
|
90
|
+
expect(workflow).toContain('actions/github-script@v7')
|
|
91
|
+
expect(workflow).toContain('github.rest.issues.createComment')
|
|
92
|
+
expect(workflow).toContain('Preview environment deployed')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should include destroy step', () => {
|
|
96
|
+
const workflow = generatePreviewWorkflow()
|
|
97
|
+
|
|
98
|
+
expect(workflow).toContain('Destroy preview environment')
|
|
99
|
+
expect(workflow).toContain('github.event.action == \'closed\'')
|
|
100
|
+
expect(workflow).toContain('--destroy')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should include cleanup job', () => {
|
|
104
|
+
const workflow = generatePreviewWorkflow()
|
|
105
|
+
|
|
106
|
+
expect(workflow).toContain('cleanup:')
|
|
107
|
+
expect(workflow).toContain('Cleanup stale environments')
|
|
108
|
+
expect(workflow).toContain('bun run cloud env:cleanup')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should use latest Bun version', () => {
|
|
112
|
+
const workflow = generatePreviewWorkflow()
|
|
113
|
+
|
|
114
|
+
expect(workflow).toContain('uses: oven-sh/setup-bun@v1')
|
|
115
|
+
expect(workflow).toContain('bun-version: latest')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should include proper permissions', () => {
|
|
119
|
+
const workflow = generatePreviewWorkflow()
|
|
120
|
+
|
|
121
|
+
expect(workflow).toContain('permissions:')
|
|
122
|
+
expect(workflow).toContain('id-token: write')
|
|
123
|
+
expect(workflow).toContain('contents: read')
|
|
124
|
+
expect(workflow).toContain('pull-requests: write')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('generateCleanupWorkflow', () => {
|
|
129
|
+
it('should generate basic cleanup workflow', () => {
|
|
130
|
+
const workflow = generateCleanupWorkflow()
|
|
131
|
+
|
|
132
|
+
expect(workflow).toContain('name: Cleanup Stale Preview Environments')
|
|
133
|
+
expect(workflow).toContain('schedule:')
|
|
134
|
+
expect(workflow).toContain('workflow_dispatch:')
|
|
135
|
+
expect(workflow).toContain('bun run cloud env:cleanup')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should use default schedule', () => {
|
|
139
|
+
const workflow = generateCleanupWorkflow()
|
|
140
|
+
|
|
141
|
+
expect(workflow).toContain('cron: \'0 0 * * *\'') // Daily at midnight
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should configure custom schedule', () => {
|
|
145
|
+
const workflow = generateCleanupWorkflow({
|
|
146
|
+
schedule: '0 2 * * *', // 2am daily
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
expect(workflow).toContain('cron: \'0 2 * * *\'')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('should configure max age', () => {
|
|
153
|
+
const workflow = generateCleanupWorkflow({
|
|
154
|
+
maxAge: 72,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
expect(workflow).toContain('--max-age=72')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should configure keep count', () => {
|
|
161
|
+
const workflow = generateCleanupWorkflow({
|
|
162
|
+
keepCount: 5,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
expect(workflow).toContain('--keep-count=5')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should include default maxAge and keepCount', () => {
|
|
169
|
+
const workflow = generateCleanupWorkflow()
|
|
170
|
+
|
|
171
|
+
expect(workflow).toContain('--max-age=48')
|
|
172
|
+
expect(workflow).toContain('--keep-count=10')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should include report step', () => {
|
|
176
|
+
const workflow = generateCleanupWorkflow()
|
|
177
|
+
|
|
178
|
+
expect(workflow).toContain('Report cleanup results')
|
|
179
|
+
expect(workflow).toContain('bun run cloud env:list --status=destroyed')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should use ubuntu-latest runner', () => {
|
|
183
|
+
const workflow = generateCleanupWorkflow()
|
|
184
|
+
|
|
185
|
+
expect(workflow).toContain('runs-on: ubuntu-latest')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe('generateCostReportWorkflow', () => {
|
|
190
|
+
it('should generate basic cost report workflow', () => {
|
|
191
|
+
const workflow = generateCostReportWorkflow()
|
|
192
|
+
|
|
193
|
+
expect(workflow).toContain('name: Preview Environment Cost Report')
|
|
194
|
+
expect(workflow).toContain('schedule:')
|
|
195
|
+
expect(workflow).toContain('workflow_dispatch:')
|
|
196
|
+
expect(workflow).toContain('bun run cloud env:cost --json')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('should use default schedule', () => {
|
|
200
|
+
const workflow = generateCostReportWorkflow()
|
|
201
|
+
|
|
202
|
+
expect(workflow).toContain('cron: \'0 8 * * 1\'') // Monday at 8am
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should configure custom schedule', () => {
|
|
206
|
+
const workflow = generateCostReportWorkflow({
|
|
207
|
+
schedule: '0 9 * * 5', // Friday at 9am
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
expect(workflow).toContain('cron: \'0 9 * * 5\'')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('should include cost generation step', () => {
|
|
214
|
+
const workflow = generateCostReportWorkflow()
|
|
215
|
+
|
|
216
|
+
expect(workflow).toContain('Generate cost report')
|
|
217
|
+
expect(workflow).toContain('COST_JSON=$(bun run cloud env:cost --json)')
|
|
218
|
+
expect(workflow).toContain('echo "cost_json=$COST_JSON" >> $GITHUB_OUTPUT')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should send to Slack when webhook provided', () => {
|
|
222
|
+
const workflow = generateCostReportWorkflow({
|
|
223
|
+
webhookUrl: 'https://hooks.slack.com/services/xxx/yyy/zzz',
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
expect(workflow).toContain('Send cost report to Slack')
|
|
227
|
+
expect(workflow).toContain('SLACK_WEBHOOK_URL: https://hooks.slack.com/services/xxx/yyy/zzz')
|
|
228
|
+
expect(workflow).toContain('curl -X POST')
|
|
229
|
+
expect(workflow).toContain('Preview Environment Cost Report')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should echo cost when webhook not provided', () => {
|
|
233
|
+
const workflow = generateCostReportWorkflow()
|
|
234
|
+
|
|
235
|
+
expect(workflow).toContain('Send cost report')
|
|
236
|
+
expect(workflow).toContain('echo "${{ steps.cost.outputs.cost_json }}"')
|
|
237
|
+
expect(workflow).not.toContain('SLACK_WEBHOOK_URL')
|
|
238
|
+
expect(workflow).not.toContain('curl')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should include proper permissions', () => {
|
|
242
|
+
const workflow = generateCostReportWorkflow()
|
|
243
|
+
|
|
244
|
+
expect(workflow).toContain('permissions:')
|
|
245
|
+
expect(workflow).toContain('id-token: write')
|
|
246
|
+
expect(workflow).toContain('contents: read')
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
})
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub integration for preview environments
|
|
3
|
+
* Generates GitHub Actions workflows for automated preview deployments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface GitHubWorkflowOptions {
|
|
7
|
+
workflowName?: string
|
|
8
|
+
trigger?: 'pull_request' | 'push' | 'workflow_dispatch'
|
|
9
|
+
branches?: string[]
|
|
10
|
+
awsRegion?: string
|
|
11
|
+
awsRole?: string
|
|
12
|
+
configFile?: string
|
|
13
|
+
ttl?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate GitHub Actions workflow for preview environments
|
|
18
|
+
*/
|
|
19
|
+
export function generatePreviewWorkflow(options: GitHubWorkflowOptions = {}): string {
|
|
20
|
+
const {
|
|
21
|
+
workflowName = 'Preview Environment',
|
|
22
|
+
trigger = 'pull_request',
|
|
23
|
+
branches = ['main', 'develop'],
|
|
24
|
+
awsRegion = 'us-east-1',
|
|
25
|
+
awsRole,
|
|
26
|
+
configFile = 'cloud.config.ts',
|
|
27
|
+
ttl = 24,
|
|
28
|
+
} = options
|
|
29
|
+
|
|
30
|
+
return `name: ${workflowName}
|
|
31
|
+
|
|
32
|
+
on:
|
|
33
|
+
${trigger}:
|
|
34
|
+
types: [opened, synchronize, reopened, closed]
|
|
35
|
+
branches:
|
|
36
|
+
${branches.map(b => ` - ${b}`).join('\n')}
|
|
37
|
+
|
|
38
|
+
env:
|
|
39
|
+
AWS_REGION: ${awsRegion}
|
|
40
|
+
TTL_HOURS: ${ttl}
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
preview:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write
|
|
47
|
+
contents: read
|
|
48
|
+
pull-requests: write
|
|
49
|
+
|
|
50
|
+
steps:
|
|
51
|
+
- name: Checkout code
|
|
52
|
+
uses: actions/checkout@v4
|
|
53
|
+
|
|
54
|
+
- name: Setup Bun
|
|
55
|
+
uses: oven-sh/setup-bun@v1
|
|
56
|
+
with:
|
|
57
|
+
bun-version: latest
|
|
58
|
+
|
|
59
|
+
- name: Install dependencies
|
|
60
|
+
run: bun install
|
|
61
|
+
|
|
62
|
+
${awsRole
|
|
63
|
+
? `- name: Configure AWS credentials
|
|
64
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
65
|
+
with:
|
|
66
|
+
role-to-assume: ${awsRole}
|
|
67
|
+
aws-region: \${{ env.AWS_REGION }}`
|
|
68
|
+
: `- name: Configure AWS credentials
|
|
69
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
70
|
+
with:
|
|
71
|
+
aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
72
|
+
aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
73
|
+
aws-region: \${{ env.AWS_REGION }}`}
|
|
74
|
+
|
|
75
|
+
- name: Deploy preview environment
|
|
76
|
+
if: github.event.action != 'closed'
|
|
77
|
+
id: deploy
|
|
78
|
+
run: |
|
|
79
|
+
# Create preview environment
|
|
80
|
+
bun run cloud env:preview \\
|
|
81
|
+
--branch=\${{ github.head_ref }} \\
|
|
82
|
+
--pr=\${{ github.event.pull_request.number }} \\
|
|
83
|
+
--commit=\${{ github.event.pull_request.head.sha }} \\
|
|
84
|
+
--ttl=\${{ env.TTL_HOURS }}
|
|
85
|
+
|
|
86
|
+
# Get preview URL
|
|
87
|
+
PREVIEW_URL=\$(bun run cloud env:preview --get-url \${{ github.head_ref }})
|
|
88
|
+
echo "preview_url=\$PREVIEW_URL" >> $GITHUB_OUTPUT
|
|
89
|
+
|
|
90
|
+
- name: Comment PR with preview URL
|
|
91
|
+
if: github.event.action != 'closed' && steps.deploy.outputs.preview_url
|
|
92
|
+
uses: actions/github-script@v7
|
|
93
|
+
with:
|
|
94
|
+
script: |
|
|
95
|
+
const previewUrl = '\${{ steps.deploy.outputs.preview_url }}';
|
|
96
|
+
const body = \`:rocket: Preview environment deployed!\\n\\n\` +
|
|
97
|
+
\`**URL:** \${previewUrl}\\n\\n\` +
|
|
98
|
+
\`**Branch:** \${{ github.head_ref }}\\n\` +
|
|
99
|
+
\`**Commit:** \${{ github.event.pull_request.head.sha }}\\n\` +
|
|
100
|
+
\`**Expires:** \${{ env.TTL_HOURS }} hours from now\\n\\n\` +
|
|
101
|
+
\`_This preview will be automatically destroyed after \${{ env.TTL_HOURS }} hours or when the PR is closed._\`;
|
|
102
|
+
|
|
103
|
+
github.rest.issues.createComment({
|
|
104
|
+
issue_number: context.issue.number,
|
|
105
|
+
owner: context.repo.owner,
|
|
106
|
+
repo: context.repo.repo,
|
|
107
|
+
body: body
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
- name: Destroy preview environment
|
|
111
|
+
if: github.event.action == 'closed'
|
|
112
|
+
run: |
|
|
113
|
+
bun run cloud env:preview --destroy \\
|
|
114
|
+
--branch=\${{ github.head_ref }} \\
|
|
115
|
+
--pr=\${{ github.event.pull_request.number }}
|
|
116
|
+
|
|
117
|
+
- name: Comment PR on destruction
|
|
118
|
+
if: github.event.action == 'closed'
|
|
119
|
+
uses: actions/github-script@v7
|
|
120
|
+
with:
|
|
121
|
+
script: |
|
|
122
|
+
const body = ':wastebasket: Preview environment destroyed.';
|
|
123
|
+
github.rest.issues.createComment({
|
|
124
|
+
issue_number: context.issue.number,
|
|
125
|
+
owner: context.repo.owner,
|
|
126
|
+
repo: context.repo.repo,
|
|
127
|
+
body: body
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
# Cleanup stale preview environments
|
|
131
|
+
cleanup:
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
if: github.event.action == 'closed'
|
|
134
|
+
permissions:
|
|
135
|
+
id-token: write
|
|
136
|
+
contents: read
|
|
137
|
+
|
|
138
|
+
steps:
|
|
139
|
+
- name: Checkout code
|
|
140
|
+
uses: actions/checkout@v4
|
|
141
|
+
|
|
142
|
+
- name: Setup Bun
|
|
143
|
+
uses: oven-sh/setup-bun@v1
|
|
144
|
+
with:
|
|
145
|
+
bun-version: latest
|
|
146
|
+
|
|
147
|
+
- name: Install dependencies
|
|
148
|
+
run: bun install
|
|
149
|
+
|
|
150
|
+
${awsRole
|
|
151
|
+
? `- name: Configure AWS credentials
|
|
152
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
153
|
+
with:
|
|
154
|
+
role-to-assume: ${awsRole}
|
|
155
|
+
aws-region: \${{ env.AWS_REGION }}`
|
|
156
|
+
: `- name: Configure AWS credentials
|
|
157
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
158
|
+
with:
|
|
159
|
+
aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
160
|
+
aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
161
|
+
aws-region: \${{ env.AWS_REGION }}`}
|
|
162
|
+
|
|
163
|
+
- name: Cleanup stale environments
|
|
164
|
+
run: |
|
|
165
|
+
# Clean up environments older than TTL
|
|
166
|
+
bun run cloud env:cleanup --max-age=\${{ env.TTL_HOURS }}
|
|
167
|
+
`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate scheduled cleanup workflow
|
|
172
|
+
*/
|
|
173
|
+
export function generateCleanupWorkflow(options: {
|
|
174
|
+
schedule?: string
|
|
175
|
+
maxAge?: number
|
|
176
|
+
keepCount?: number
|
|
177
|
+
} = {}): string {
|
|
178
|
+
const {
|
|
179
|
+
schedule = '0 0 * * *', // Daily at midnight
|
|
180
|
+
maxAge = 48, // 48 hours
|
|
181
|
+
keepCount = 10,
|
|
182
|
+
} = options
|
|
183
|
+
|
|
184
|
+
return `name: Cleanup Stale Preview Environments
|
|
185
|
+
|
|
186
|
+
on:
|
|
187
|
+
schedule:
|
|
188
|
+
- cron: '${schedule}'
|
|
189
|
+
workflow_dispatch:
|
|
190
|
+
|
|
191
|
+
env:
|
|
192
|
+
AWS_REGION: us-east-1
|
|
193
|
+
|
|
194
|
+
jobs:
|
|
195
|
+
cleanup:
|
|
196
|
+
runs-on: ubuntu-latest
|
|
197
|
+
permissions:
|
|
198
|
+
id-token: write
|
|
199
|
+
contents: read
|
|
200
|
+
|
|
201
|
+
steps:
|
|
202
|
+
- name: Checkout code
|
|
203
|
+
uses: actions/checkout@v4
|
|
204
|
+
|
|
205
|
+
- name: Setup Bun
|
|
206
|
+
uses: oven-sh/setup-bun@v1
|
|
207
|
+
with:
|
|
208
|
+
bun-version: latest
|
|
209
|
+
|
|
210
|
+
- name: Install dependencies
|
|
211
|
+
run: bun install
|
|
212
|
+
|
|
213
|
+
- name: Configure AWS credentials
|
|
214
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
215
|
+
with:
|
|
216
|
+
aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
217
|
+
aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
218
|
+
aws-region: \${{ env.AWS_REGION }}
|
|
219
|
+
|
|
220
|
+
- name: Cleanup stale environments
|
|
221
|
+
run: |
|
|
222
|
+
bun run cloud env:cleanup \\
|
|
223
|
+
--max-age=${maxAge} \\
|
|
224
|
+
--keep-count=${keepCount}
|
|
225
|
+
|
|
226
|
+
- name: Report cleanup results
|
|
227
|
+
run: |
|
|
228
|
+
bun run cloud env:list --status=destroyed
|
|
229
|
+
`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Generate cost report workflow
|
|
234
|
+
*/
|
|
235
|
+
export function generateCostReportWorkflow(options: {
|
|
236
|
+
schedule?: string
|
|
237
|
+
webhookUrl?: string
|
|
238
|
+
} = {}): string {
|
|
239
|
+
const {
|
|
240
|
+
schedule = '0 8 * * 1', // Weekly on Monday at 8am
|
|
241
|
+
webhookUrl,
|
|
242
|
+
} = options
|
|
243
|
+
|
|
244
|
+
return `name: Preview Environment Cost Report
|
|
245
|
+
|
|
246
|
+
on:
|
|
247
|
+
schedule:
|
|
248
|
+
- cron: '${schedule}'
|
|
249
|
+
workflow_dispatch:
|
|
250
|
+
|
|
251
|
+
env:
|
|
252
|
+
AWS_REGION: us-east-1
|
|
253
|
+
|
|
254
|
+
jobs:
|
|
255
|
+
cost-report:
|
|
256
|
+
runs-on: ubuntu-latest
|
|
257
|
+
permissions:
|
|
258
|
+
id-token: write
|
|
259
|
+
contents: read
|
|
260
|
+
|
|
261
|
+
steps:
|
|
262
|
+
- name: Checkout code
|
|
263
|
+
uses: actions/checkout@v4
|
|
264
|
+
|
|
265
|
+
- name: Setup Bun
|
|
266
|
+
uses: oven-sh/setup-bun@v1
|
|
267
|
+
with:
|
|
268
|
+
bun-version: latest
|
|
269
|
+
|
|
270
|
+
- name: Install dependencies
|
|
271
|
+
run: bun install
|
|
272
|
+
|
|
273
|
+
- name: Configure AWS credentials
|
|
274
|
+
uses: aws-actions/configure-aws-credentials@v4
|
|
275
|
+
with:
|
|
276
|
+
aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
277
|
+
aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
278
|
+
aws-region: \${{ env.AWS_REGION }}
|
|
279
|
+
|
|
280
|
+
- name: Generate cost report
|
|
281
|
+
id: cost
|
|
282
|
+
run: |
|
|
283
|
+
COST_JSON=\$(bun run cloud env:cost --json)
|
|
284
|
+
echo "cost_json=\$COST_JSON" >> $GITHUB_OUTPUT
|
|
285
|
+
|
|
286
|
+
- name: Send cost report${webhookUrl ? ' to Slack' : ''}
|
|
287
|
+
${webhookUrl
|
|
288
|
+
? `env:
|
|
289
|
+
SLACK_WEBHOOK_URL: ${webhookUrl}
|
|
290
|
+
run: |
|
|
291
|
+
curl -X POST -H 'Content-type: application/json' \\
|
|
292
|
+
--data '{"text":"Preview Environment Cost Report\\n\${{ steps.cost.outputs.cost_json }}"}' \\
|
|
293
|
+
\$SLACK_WEBHOOK_URL`
|
|
294
|
+
: `run: |
|
|
295
|
+
echo "\${{ steps.cost.outputs.cost_json }}"`}
|
|
296
|
+
`
|
|
297
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Environments
|
|
3
|
+
* Ephemeral environments for PR/branch previews
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
PreviewEnvironmentManager,
|
|
8
|
+
previewManager,
|
|
9
|
+
} from './manager'
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
PreviewEnvironment,
|
|
13
|
+
PreviewEnvironmentOptions,
|
|
14
|
+
PreviewCleanupOptions,
|
|
15
|
+
} from './manager'
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
generatePreviewWorkflow,
|
|
19
|
+
generateCleanupWorkflow,
|
|
20
|
+
generateCostReportWorkflow,
|
|
21
|
+
} from './github'
|
|
22
|
+
|
|
23
|
+
export type { GitHubWorkflowOptions } from './github'
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
PreviewNotificationService,
|
|
27
|
+
previewNotifications,
|
|
28
|
+
} from './notifications'
|
|
29
|
+
|
|
30
|
+
export type {
|
|
31
|
+
NotificationChannel,
|
|
32
|
+
SlackConfig,
|
|
33
|
+
DiscordConfig,
|
|
34
|
+
EmailConfig,
|
|
35
|
+
WebhookConfig,
|
|
36
|
+
NotificationEvent,
|
|
37
|
+
} from './notifications'
|