chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,57 @@
import { CancelUpdateStackCommand, CloudFormationClient } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationCancelUpdateStackContract } from '@/lib/api/contracts/tools/aws/cloudformation-cancel-update-stack'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationCancelUpdateStack')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationCancelUpdateStackContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
logger.info(`Cancelling update for CloudFormation stack "${validatedData.stackName}"`)
try {
const command = new CancelUpdateStackCommand({
StackName: validatedData.stackName,
})
await client.send(command)
return NextResponse.json({
success: true,
output: {
message: `Update for stack "${validatedData.stackName}" is being cancelled and rolled back`,
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to cancel CloudFormation stack update')
logger.error('CancelUpdateStack failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,68 @@
import { CloudFormationClient, CreateChangeSetCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationCreateChangeSetContract } from '@/lib/api/contracts/tools/aws/cloudformation-create-change-set'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { parseCapabilities, toStackParameters } from '../utils'
const logger = createLogger('CloudFormationCreateChangeSet')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationCreateChangeSetContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
logger.info(
`Creating change set "${validatedData.changeSetName}" for stack "${validatedData.stackName}"`
)
try {
const command = new CreateChangeSetCommand({
StackName: validatedData.stackName,
ChangeSetName: validatedData.changeSetName,
TemplateBody: validatedData.templateBody,
UsePreviousTemplate: validatedData.usePreviousTemplate,
Parameters: toStackParameters(validatedData.parameters),
Capabilities: parseCapabilities(validatedData.capabilities),
ChangeSetType: validatedData.changeSetType,
Description: validatedData.description,
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
changeSetId: response.Id ?? '',
stackId: response.StackId ?? '',
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to create CloudFormation change set')
logger.error('CreateChangeSet failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,64 @@
import { CloudFormationClient, CreateStackCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationCreateStackContract } from '@/lib/api/contracts/tools/aws/cloudformation-create-stack'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { parseCapabilities, toStackParameters, toStackTags } from '../utils'
const logger = createLogger('CloudFormationCreateStack')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationCreateStackContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
logger.info(`Creating CloudFormation stack "${validatedData.stackName}"`)
try {
const command = new CreateStackCommand({
StackName: validatedData.stackName,
TemplateBody: validatedData.templateBody,
Parameters: toStackParameters(validatedData.parameters),
Capabilities: parseCapabilities(validatedData.capabilities),
Tags: toStackTags(validatedData.tags),
OnFailure: validatedData.onFailure,
TimeoutInMinutes: validatedData.timeoutInMinutes,
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
stackId: response.StackId ?? '',
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to create CloudFormation stack')
logger.error('CreateStack failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,67 @@
import { CloudFormationClient, DeleteStackCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationDeleteStackContract } from '@/lib/api/contracts/tools/aws/cloudformation-delete-stack'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationDeleteStack')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationDeleteStackContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
logger.info(`Deleting CloudFormation stack "${validatedData.stackName}"`)
try {
const retainResources = validatedData.retainResources
?.split(',')
.map((r) => r.trim())
.filter(Boolean)
const command = new DeleteStackCommand({
StackName: validatedData.stackName,
...(retainResources && retainResources.length > 0 && { RetainResources: retainResources }),
})
await client.send(command)
logger.info(
`Successfully requested deletion of CloudFormation stack "${validatedData.stackName}"`
)
return NextResponse.json({
success: true,
output: {
message: `Deletion of stack "${validatedData.stackName}" has been initiated`,
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to delete CloudFormation stack')
logger.error('DeleteStack failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,74 @@
import { CloudFormationClient, DescribeChangeSetCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationDescribeChangeSetContract } from '@/lib/api/contracts/tools/aws/cloudformation-describe-change-set'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationDescribeChangeSet')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationDescribeChangeSetContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
try {
const command = new DescribeChangeSetCommand({
ChangeSetName: validatedData.changeSetName,
...(validatedData.stackName && { StackName: validatedData.stackName }),
})
const response = await client.send(command)
const changes = (response.Changes ?? []).map((c) => ({
action: c.ResourceChange?.Action,
logicalResourceId: c.ResourceChange?.LogicalResourceId,
physicalResourceId: c.ResourceChange?.PhysicalResourceId,
resourceType: c.ResourceChange?.ResourceType,
replacement: c.ResourceChange?.Replacement,
}))
return NextResponse.json({
success: true,
output: {
changeSetName: response.ChangeSetName,
changeSetId: response.ChangeSetId,
stackId: response.StackId,
stackName: response.StackName,
description: response.Description,
executionStatus: response.ExecutionStatus,
status: response.Status,
statusReason: response.StatusReason,
creationTime: response.CreationTime?.getTime(),
capabilities: response.Capabilities ?? [],
changes,
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to describe CloudFormation change set')
logger.error('DescribeChangeSet failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,64 @@
import {
CloudFormationClient,
DescribeStackDriftDetectionStatusCommand,
} from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationDescribeStackDriftDetectionStatusContract } from '@/lib/api/contracts/tools/aws/cloudformation-describe-stack-drift-detection-status'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationDescribeStackDriftDetectionStatus')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(
awsCloudformationDescribeStackDriftDetectionStatusContract,
request,
{
errorFormat: 'details',
logger,
}
)
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const command = new DescribeStackDriftDetectionStatusCommand({
StackDriftDetectionId: validatedData.stackDriftDetectionId,
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
stackId: response.StackId ?? '',
stackDriftDetectionId: response.StackDriftDetectionId ?? '',
stackDriftStatus: response.StackDriftStatus,
detectionStatus: response.DetectionStatus ?? 'UNKNOWN',
detectionStatusReason: response.DetectionStatusReason,
driftedStackResourceCount: response.DriftedStackResourceCount,
timestamp: response.Timestamp?.getTime(),
},
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to describe stack drift detection status')
logger.error('DescribeStackDriftDetectionStatus failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,73 @@
import {
CloudFormationClient,
DescribeStackEventsCommand,
type StackEvent,
} from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationDescribeStackEventsContract } from '@/lib/api/contracts/tools/aws/cloudformation-describe-stack-events'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationDescribeStackEvents')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationDescribeStackEventsContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const limit = validatedData.limit ?? 50
const allEvents: StackEvent[] = []
let nextToken: string | undefined
do {
const command = new DescribeStackEventsCommand({
StackName: validatedData.stackName,
...(nextToken && { NextToken: nextToken }),
})
const response = await client.send(command)
allEvents.push(...(response.StackEvents ?? []))
nextToken = allEvents.length >= limit ? undefined : response.NextToken
} while (nextToken)
const events = allEvents.slice(0, limit).map((e) => ({
stackId: e.StackId ?? '',
eventId: e.EventId ?? '',
stackName: e.StackName ?? '',
logicalResourceId: e.LogicalResourceId,
physicalResourceId: e.PhysicalResourceId,
resourceType: e.ResourceType,
resourceStatus: e.ResourceStatus,
resourceStatusReason: e.ResourceStatusReason,
timestamp: e.Timestamp?.getTime(),
}))
return NextResponse.json({
success: true,
output: { events },
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to describe CloudFormation stack events')
logger.error('DescribeStackEvents failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,85 @@
import {
CloudFormationClient,
DescribeStacksCommand,
type Stack,
} from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationDescribeStacksContract } from '@/lib/api/contracts/tools/aws/cloudformation-describe-stacks'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationDescribeStacks')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationDescribeStacksContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const allStacks: Stack[] = []
let nextToken: string | undefined
do {
const command = new DescribeStacksCommand({
...(validatedData.stackName && { StackName: validatedData.stackName }),
...(nextToken && { NextToken: nextToken }),
})
const response = await client.send(command)
allStacks.push(...(response.Stacks ?? []))
nextToken = response.NextToken
} while (nextToken)
const stacks = allStacks.map((s) => ({
stackName: s.StackName ?? '',
stackId: s.StackId ?? '',
stackStatus: s.StackStatus ?? 'UNKNOWN',
stackStatusReason: s.StackStatusReason,
creationTime: s.CreationTime?.getTime(),
lastUpdatedTime: s.LastUpdatedTime?.getTime(),
description: s.Description,
enableTerminationProtection: s.EnableTerminationProtection,
driftInformation: s.DriftInformation
? {
stackDriftStatus: s.DriftInformation.StackDriftStatus,
lastCheckTimestamp: s.DriftInformation.LastCheckTimestamp?.getTime(),
}
: null,
outputs: (s.Outputs ?? []).map((o) => ({
outputKey: o.OutputKey ?? '',
outputValue: o.OutputValue ?? '',
description: o.Description,
})),
tags: (s.Tags ?? []).map((t) => ({
key: t.Key ?? '',
value: t.Value ?? '',
})),
}))
return NextResponse.json({
success: true,
output: { stacks },
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to describe CloudFormation stacks')
logger.error('DescribeStacks failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,55 @@
import { CloudFormationClient, DetectStackDriftCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationDetectStackDriftContract } from '@/lib/api/contracts/tools/aws/cloudformation-detect-stack-drift'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationDetectStackDrift')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationDetectStackDriftContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const command = new DetectStackDriftCommand({
StackName: validatedData.stackName,
})
const response = await client.send(command)
if (!response.StackDriftDetectionId) {
throw new Error('No drift detection ID returned')
}
return NextResponse.json({
success: true,
output: {
stackDriftDetectionId: response.StackDriftDetectionId,
},
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to detect CloudFormation stack drift')
logger.error('DetectStackDrift failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,58 @@
import { CloudFormationClient, ExecuteChangeSetCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationExecuteChangeSetContract } from '@/lib/api/contracts/tools/aws/cloudformation-execute-change-set'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationExecuteChangeSet')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationExecuteChangeSetContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
logger.info(`Executing change set "${validatedData.changeSetName}"`)
try {
const command = new ExecuteChangeSetCommand({
ChangeSetName: validatedData.changeSetName,
...(validatedData.stackName && { StackName: validatedData.stackName }),
})
await client.send(command)
return NextResponse.json({
success: true,
output: {
message: `Change set "${validatedData.changeSetName}" execution has been initiated`,
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to execute CloudFormation change set')
logger.error('ExecuteChangeSet failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,68 @@
import { CloudFormationClient, GetTemplateSummaryCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationGetTemplateSummaryContract } from '@/lib/api/contracts/tools/aws/cloudformation-get-template-summary'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationGetTemplateSummary')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationGetTemplateSummaryContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
try {
const command = new GetTemplateSummaryCommand({
...(validatedData.templateBody && { TemplateBody: validatedData.templateBody }),
...(validatedData.stackName && { StackName: validatedData.stackName }),
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
description: response.Description,
parameters: (response.Parameters ?? []).map((p) => ({
parameterKey: p.ParameterKey,
defaultValue: p.DefaultValue,
parameterType: p.ParameterType,
noEcho: p.NoEcho,
description: p.Description,
})),
capabilities: response.Capabilities ?? [],
capabilitiesReason: response.CapabilitiesReason,
resourceTypes: response.ResourceTypes ?? [],
version: response.Version,
declaredTransforms: response.DeclaredTransforms ?? [],
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to get CloudFormation template summary')
logger.error('GetTemplateSummary failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,53 @@
import { CloudFormationClient, GetTemplateCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationGetTemplateContract } from '@/lib/api/contracts/tools/aws/cloudformation-get-template'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationGetTemplate')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationGetTemplateContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const command = new GetTemplateCommand({
StackName: validatedData.stackName,
...(validatedData.templateStage && { TemplateStage: validatedData.templateStage }),
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
templateBody: response.TemplateBody ?? '',
stagesAvailable: response.StagesAvailable ?? [],
},
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to get CloudFormation template')
logger.error('GetTemplate failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,74 @@
import {
CloudFormationClient,
ListStackResourcesCommand,
type StackResourceSummary,
} from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationListStackResourcesContract } from '@/lib/api/contracts/tools/aws/cloudformation-list-stack-resources'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationListStackResources')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationListStackResourcesContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const allSummaries: StackResourceSummary[] = []
let nextToken: string | undefined
do {
const command = new ListStackResourcesCommand({
StackName: validatedData.stackName,
...(nextToken && { NextToken: nextToken }),
})
const response = await client.send(command)
allSummaries.push(...(response.StackResourceSummaries ?? []))
nextToken = response.NextToken
} while (nextToken)
const resources = allSummaries.map((r) => ({
logicalResourceId: r.LogicalResourceId ?? '',
physicalResourceId: r.PhysicalResourceId,
resourceType: r.ResourceType ?? '',
resourceStatus: r.ResourceStatus ?? 'UNKNOWN',
resourceStatusReason: r.ResourceStatusReason,
lastUpdatedTimestamp: r.LastUpdatedTimestamp?.getTime(),
driftInformation: r.DriftInformation
? {
stackResourceDriftStatus: r.DriftInformation.StackResourceDriftStatus,
lastCheckTimestamp: r.DriftInformation.LastCheckTimestamp?.getTime(),
}
: null,
}))
return NextResponse.json({
success: true,
output: { resources },
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to list CloudFormation stack resources')
logger.error('ListStackResources failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,63 @@
import { CloudFormationClient, UpdateStackCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationUpdateStackContract } from '@/lib/api/contracts/tools/aws/cloudformation-update-stack'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { parseCapabilities, toStackParameters, toStackTags } from '../utils'
const logger = createLogger('CloudFormationUpdateStack')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationUpdateStackContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
logger.info(`Updating CloudFormation stack "${validatedData.stackName}"`)
try {
const command = new UpdateStackCommand({
StackName: validatedData.stackName,
TemplateBody: validatedData.templateBody,
UsePreviousTemplate: validatedData.usePreviousTemplate,
Parameters: toStackParameters(validatedData.parameters),
Capabilities: parseCapabilities(validatedData.capabilities),
Tags: toStackTags(validatedData.tags),
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
stackId: response.StackId ?? '',
},
})
} finally {
client.destroy()
}
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to update CloudFormation stack')
logger.error('UpdateStack failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})
@@ -0,0 +1,36 @@
import type { Capability, Parameter, Tag } from '@aws-sdk/client-cloudformation'
/**
* Parses a comma-separated capabilities string (e.g. "CAPABILITY_IAM,CAPABILITY_NAMED_IAM")
* into the array shape the CloudFormation SDK expects.
*/
export function parseCapabilities(value?: string): Capability[] | undefined {
if (!value) return undefined
const capabilities = value
.split(',')
.map((c) => c.trim())
.filter(Boolean)
return capabilities.length > 0 ? (capabilities as Capability[]) : undefined
}
/**
* Maps camelCase stack parameter inputs to the PascalCase `Parameter` shape CloudFormation expects.
*/
export function toStackParameters(
parameters?: { parameterKey: string; parameterValue?: string; usePreviousValue?: boolean }[]
): Parameter[] | undefined {
if (!parameters || parameters.length === 0) return undefined
return parameters.map((p) => ({
ParameterKey: p.parameterKey,
ParameterValue: p.parameterValue,
UsePreviousValue: p.usePreviousValue,
}))
}
/**
* Maps camelCase tag inputs to the PascalCase `Tag` shape CloudFormation expects.
*/
export function toStackTags(tags?: { key: string; value: string }[]): Tag[] | undefined {
if (!tags || tags.length === 0) return undefined
return tags.map((t) => ({ Key: t.key, Value: t.value }))
}
@@ -0,0 +1,60 @@
import { CloudFormationClient, ValidateTemplateCommand } from '@aws-sdk/client-cloudformation'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { awsCloudformationValidateTemplateContract } from '@/lib/api/contracts/tools/aws/cloudformation-validate-template'
import { parseToolRequest } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
const logger = createLogger('CloudFormationValidateTemplate')
export const POST = withRouteHandler(async (request: NextRequest) => {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const parsed = await parseToolRequest(awsCloudformationValidateTemplateContract, request, {
errorFormat: 'details',
logger,
})
if (!parsed.success) return parsed.response
const validatedData = parsed.data.body
const client = new CloudFormationClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const command = new ValidateTemplateCommand({
TemplateBody: validatedData.templateBody,
})
const response = await client.send(command)
return NextResponse.json({
success: true,
output: {
description: response.Description,
parameters: (response.Parameters ?? []).map((p) => ({
parameterKey: p.ParameterKey,
defaultValue: p.DefaultValue,
noEcho: p.NoEcho,
description: p.Description,
})),
capabilities: response.Capabilities ?? [],
capabilitiesReason: response.CapabilitiesReason,
declaredTransforms: response.DeclaredTransforms ?? [],
},
})
} catch (error) {
const errorMessage = getErrorMessage(error, 'Failed to validate CloudFormation template')
logger.error('ValidateTemplate failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
})