diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 41732af3b2..8eb32d5e6f 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -5512,6 +5512,33 @@ export function CloudWatchIcon(props: SVGProps) { ) } +export function CodePipelineIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function TextractIcon(props: SVGProps) { return ( = { cloudflare: CloudflareIcon, cloudformation: CloudFormationIcon, cloudwatch: CloudWatchIcon, + codepipeline: CodePipelineIcon, confluence: ConfluenceIcon, confluence_v2: ConfluenceIcon, crowdstrike: CrowdStrikeIcon, diff --git a/apps/docs/content/docs/en/tools/codepipeline.mdx b/apps/docs/content/docs/en/tools/codepipeline.mdx new file mode 100644 index 0000000000..29144ab1df --- /dev/null +++ b/apps/docs/content/docs/en/tools/codepipeline.mdx @@ -0,0 +1,237 @@ +--- +title: CodePipeline +description: Run, monitor, and approve AWS CodePipeline pipelines +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate AWS CodePipeline into workflows. Start, stop, and monitor pipeline executions, retry failed stages, and approve or reject manual approval actions. Requires AWS access key and secret access key. + + + +## Tools + +### `codepipeline_list_pipelines` + +List all CodePipeline pipelines in an AWS account and region + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `maxResults` | number | No | Maximum number of pipelines to return \(1-1000\) | +| `nextToken` | string | No | Pagination token from a previous call | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelines` | array | List of pipelines with name, version, type, and timestamps | +| ↳ `name` | string | Pipeline name | +| ↳ `version` | number | Pipeline version number | +| ↳ `pipelineType` | string | Pipeline type \(V1 or V2\) | +| ↳ `executionMode` | string | Execution mode \(QUEUED, SUPERSEDED, PARALLEL\) | +| ↳ `created` | number | Epoch ms when the pipeline was created | +| ↳ `updated` | number | Epoch ms when the pipeline was last updated | +| `nextToken` | string | Pagination token for the next page of results | + +### `codepipeline_get_pipeline_state` + +Get the current state of a CodePipeline pipeline, including stage and action status and pending approval tokens + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelineName` | string | Pipeline name | +| `pipelineVersion` | number | Pipeline version number | +| `created` | number | Epoch ms when the pipeline was created | +| `updated` | number | Epoch ms when the pipeline was last updated | +| `stageStates` | array | Per-stage state including latest execution status and action details | +| ↳ `stageName` | string | Stage name | +| ↳ `status` | string | Latest stage execution status \(InProgress, Succeeded, Failed, Stopped, Cancelled\) | +| ↳ `pipelineExecutionId` | string | Pipeline execution ID currently in the stage | +| ↳ `inboundTransitionEnabled` | boolean | Whether the inbound transition into the stage is enabled | +| ↳ `actionStates` | array | Per-action state with status, summary, error details, and approval token \(for pending manual approvals\) | + +### `codepipeline_get_pipeline_execution` + +Get details of a CodePipeline execution, including status, trigger, source revisions, and resolved variables + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline | +| `pipelineExecutionId` | string | Yes | ID of the pipeline execution | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelineExecutionId` | string | Pipeline execution ID | +| `pipelineName` | string | Pipeline name | +| `pipelineVersion` | number | Pipeline version number | +| `status` | string | Execution status \(Cancelled, InProgress, Stopped, Stopping, Succeeded, Superseded, Failed\) | +| `statusSummary` | string | Status summary for the execution | +| `executionMode` | string | Execution mode \(QUEUED, SUPERSEDED, PARALLEL\) | +| `executionType` | string | Execution type \(STANDARD or ROLLBACK\) | +| `triggerType` | string | What triggered the execution \(e.g., Webhook, StartPipelineExecution\) | +| `triggerDetail` | string | Detail about the trigger \(e.g., user ARN\) | +| `artifactRevisions` | array | Source artifact revisions for the execution | +| ↳ `name` | string | Artifact name | +| ↳ `revisionId` | string | Revision ID \(e.g., commit SHA\) | +| ↳ `revisionSummary` | string | Revision summary \(e.g., commit message\) | +| ↳ `revisionUrl` | string | URL of the revision | +| ↳ `created` | number | Epoch ms when the revision was created | +| `variables` | array | Resolved pipeline variables for the execution | +| ↳ `name` | string | Variable name | +| ↳ `resolvedValue` | string | Resolved variable value | + +### `codepipeline_list_pipeline_executions` + +List recent executions of a CodePipeline pipeline with status and source revisions + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline | +| `maxResults` | number | No | Maximum number of executions to return \(1-100, default 100\) | +| `nextToken` | string | No | Pagination token from a previous call | +| `succeededInStage` | string | No | Only return executions that succeeded in this stage | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `executions` | array | Pipeline execution summaries, most recent first | +| ↳ `pipelineExecutionId` | string | Pipeline execution ID | +| ↳ `status` | string | Execution status \(Cancelled, InProgress, Stopped, Stopping, Succeeded, Superseded, Failed\) | +| ↳ `statusSummary` | string | Status summary for the execution | +| ↳ `startTime` | number | Epoch ms when the execution started | +| ↳ `lastUpdateTime` | number | Epoch ms when the execution was last updated | +| ↳ `executionMode` | string | Execution mode \(QUEUED, SUPERSEDED, PARALLEL\) | +| ↳ `executionType` | string | Execution type \(STANDARD or ROLLBACK\) | +| ↳ `stopTriggerReason` | string | Reason the execution was stopped, if applicable | +| ↳ `triggerType` | string | What triggered the execution | +| ↳ `triggerDetail` | string | Detail about the trigger | +| ↳ `sourceRevisions` | array | Source revisions \(commit IDs, summaries, URLs\) for the execution | +| `nextToken` | string | Pagination token for the next page of results | + +### `codepipeline_start_execution` + +Start a CodePipeline pipeline execution, optionally overriding pipeline variables + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline to start | +| `clientRequestToken` | string | No | Idempotency token to identify a unique execution request | +| `variables` | json | No | Pipeline variable overrides as an array of \{ name, value \} objects | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelineExecutionId` | string | ID of the pipeline execution that was started | + +### `codepipeline_stop_execution` + +Stop a CodePipeline pipeline execution, either finishing in-progress actions or abandoning them + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline | +| `pipelineExecutionId` | string | Yes | ID of the pipeline execution to stop | +| `abandon` | boolean | No | Abandon in-progress actions instead of letting them finish \(default false\) | +| `reason` | string | No | Reason for stopping the execution \(max 200 characters\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelineExecutionId` | string | ID of the pipeline execution that was stopped | + +### `codepipeline_retry_stage_execution` + +Retry the failed actions (or all actions) of a failed CodePipeline stage + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline | +| `stageName` | string | Yes | Name of the failed stage to retry | +| `pipelineExecutionId` | string | Yes | ID of the pipeline execution in the failed stage | +| `retryMode` | string | Yes | Scope of the retry: FAILED_ACTIONS or ALL_ACTIONS | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pipelineExecutionId` | string | ID of the pipeline execution with the retried stage | + +### `codepipeline_put_approval_result` + +Approve or reject a pending CodePipeline manual approval action. The approval token is available from Get Pipeline State on the pending approval action + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) | +| `awsAccessKeyId` | string | Yes | AWS access key ID | +| `awsSecretAccessKey` | string | Yes | AWS secret access key | +| `pipelineName` | string | Yes | Name of the pipeline | +| `stageName` | string | Yes | Name of the stage containing the approval action | +| `actionName` | string | Yes | Name of the manual approval action | +| `token` | string | Yes | Approval token from Get Pipeline State for the pending approval | +| `status` | string | Yes | Approval decision: Approved or Rejected | +| `summary` | string | Yes | Summary explaining the approval decision \(max 512 characters\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `approvedAt` | number | Epoch ms when the approval or rejection was submitted | +| `status` | string | The submitted approval decision \(Approved or Rejected\) | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index c2edcceb16..b91b8fa144 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -30,6 +30,7 @@ "cloudflare", "cloudformation", "cloudwatch", + "codepipeline", "confluence", "crowdstrike", "cursor", diff --git a/apps/sim/app/api/tools/codepipeline/get-pipeline-execution/route.ts b/apps/sim/app/api/tools/codepipeline/get-pipeline-execution/route.ts new file mode 100644 index 0000000000..f56a815371 --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/get-pipeline-execution/route.ts @@ -0,0 +1,87 @@ +import { CodePipelineClient, GetPipelineExecutionCommand } from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineGetPipelineExecutionContract } from '@/lib/api/contracts/tools/aws/codepipeline-get-pipeline-execution' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineGetPipelineExecution') + +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(awsCodepipelineGetPipelineExecutionContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Getting CodePipeline pipeline execution') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new GetPipelineExecutionCommand({ + pipelineName: validatedData.pipelineName, + pipelineExecutionId: validatedData.pipelineExecutionId, + }) + + const response = await client.send(command) + const execution = response.pipelineExecution + + if (!execution) { + throw new Error('Pipeline execution not found in response') + } + + logger.info('Successfully got pipeline execution') + + return NextResponse.json({ + success: true, + output: { + pipelineExecutionId: execution.pipelineExecutionId ?? validatedData.pipelineExecutionId, + pipelineName: execution.pipelineName ?? validatedData.pipelineName, + pipelineVersion: execution.pipelineVersion, + status: execution.status ?? 'Unknown', + statusSummary: execution.statusSummary, + executionMode: execution.executionMode, + executionType: execution.executionType, + triggerType: execution.trigger?.triggerType, + triggerDetail: execution.trigger?.triggerDetail, + artifactRevisions: (execution.artifactRevisions ?? []).map((r) => ({ + name: r.name ?? '', + revisionId: r.revisionId, + revisionSummary: r.revisionSummary, + revisionUrl: r.revisionUrl, + created: r.created?.getTime(), + })), + variables: (execution.variables ?? []).map((v) => ({ + name: v.name ?? '', + resolvedValue: v.resolvedValue ?? '', + })), + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('GetPipelineExecution failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to get CodePipeline pipeline execution: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/get-pipeline-state/route.ts b/apps/sim/app/api/tools/codepipeline/get-pipeline-state/route.ts new file mode 100644 index 0000000000..8dcbc8dcf6 --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/get-pipeline-state/route.ts @@ -0,0 +1,84 @@ +import { CodePipelineClient, GetPipelineStateCommand } from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineGetPipelineStateContract } from '@/lib/api/contracts/tools/aws/codepipeline-get-pipeline-state' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineGetPipelineState') + +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(awsCodepipelineGetPipelineStateContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Getting CodePipeline pipeline state') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new GetPipelineStateCommand({ name: validatedData.pipelineName }) + const response = await client.send(command) + + const stageStates = (response.stageStates ?? []).map((stage) => ({ + stageName: stage.stageName ?? '', + status: stage.latestExecution?.status, + pipelineExecutionId: stage.latestExecution?.pipelineExecutionId, + inboundTransitionEnabled: stage.inboundTransitionState?.enabled, + actionStates: (stage.actionStates ?? []).map((action) => ({ + actionName: action.actionName ?? '', + status: action.latestExecution?.status, + summary: action.latestExecution?.summary, + lastStatusChange: action.latestExecution?.lastStatusChange?.getTime(), + externalExecutionId: action.latestExecution?.externalExecutionId, + externalExecutionUrl: action.latestExecution?.externalExecutionUrl, + errorCode: action.latestExecution?.errorDetails?.code, + errorMessage: action.latestExecution?.errorDetails?.message, + percentComplete: action.latestExecution?.percentComplete, + token: action.latestExecution?.token, + revisionId: action.currentRevision?.revisionId, + entityUrl: action.entityUrl, + })), + })) + + logger.info(`Successfully got pipeline state with ${stageStates.length} stages`) + + return NextResponse.json({ + success: true, + output: { + pipelineName: response.pipelineName ?? validatedData.pipelineName, + pipelineVersion: response.pipelineVersion, + created: response.created?.getTime(), + updated: response.updated?.getTime(), + stageStates, + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('GetPipelineState failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to get CodePipeline pipeline state: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/list-pipeline-executions/route.ts b/apps/sim/app/api/tools/codepipeline/list-pipeline-executions/route.ts new file mode 100644 index 0000000000..2b8a579ca7 --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/list-pipeline-executions/route.ts @@ -0,0 +1,87 @@ +import { CodePipelineClient, ListPipelineExecutionsCommand } from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineListPipelineExecutionsContract } from '@/lib/api/contracts/tools/aws/codepipeline-list-pipeline-executions' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineListPipelineExecutions') + +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(awsCodepipelineListPipelineExecutionsContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Listing CodePipeline pipeline executions') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new ListPipelineExecutionsCommand({ + pipelineName: validatedData.pipelineName, + ...(validatedData.maxResults !== undefined && { maxResults: validatedData.maxResults }), + ...(validatedData.nextToken && { nextToken: validatedData.nextToken }), + ...(validatedData.succeededInStage && { + filter: { succeededInStage: { stageName: validatedData.succeededInStage } }, + }), + }) + + const response = await client.send(command) + + const executions = (response.pipelineExecutionSummaries ?? []).map((e) => ({ + pipelineExecutionId: e.pipelineExecutionId ?? '', + status: e.status ?? 'Unknown', + statusSummary: e.statusSummary, + startTime: e.startTime?.getTime(), + lastUpdateTime: e.lastUpdateTime?.getTime(), + executionMode: e.executionMode, + executionType: e.executionType, + stopTriggerReason: e.stopTrigger?.reason, + triggerType: e.trigger?.triggerType, + triggerDetail: e.trigger?.triggerDetail, + sourceRevisions: (e.sourceRevisions ?? []).map((r) => ({ + actionName: r.actionName ?? '', + revisionId: r.revisionId, + revisionSummary: r.revisionSummary, + revisionUrl: r.revisionUrl, + })), + })) + + logger.info(`Successfully listed ${executions.length} pipeline executions`) + + return NextResponse.json({ + success: true, + output: { + executions, + ...(response.nextToken && { nextToken: response.nextToken }), + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('ListPipelineExecutions failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to list CodePipeline pipeline executions: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/list-pipelines/route.ts b/apps/sim/app/api/tools/codepipeline/list-pipelines/route.ts new file mode 100644 index 0000000000..bf3cb9e96d --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/list-pipelines/route.ts @@ -0,0 +1,73 @@ +import { CodePipelineClient, ListPipelinesCommand } from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineListPipelinesContract } from '@/lib/api/contracts/tools/aws/codepipeline-list-pipelines' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineListPipelines') + +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(awsCodepipelineListPipelinesContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Listing CodePipeline pipelines') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new ListPipelinesCommand({ + ...(validatedData.maxResults !== undefined && { maxResults: validatedData.maxResults }), + ...(validatedData.nextToken && { nextToken: validatedData.nextToken }), + }) + + const response = await client.send(command) + + const pipelines = (response.pipelines ?? []).map((p) => ({ + name: p.name ?? '', + version: p.version, + pipelineType: p.pipelineType, + executionMode: p.executionMode, + created: p.created?.getTime(), + updated: p.updated?.getTime(), + })) + + logger.info(`Successfully listed ${pipelines.length} pipelines`) + + return NextResponse.json({ + success: true, + output: { + pipelines, + ...(response.nextToken && { nextToken: response.nextToken }), + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('ListPipelines failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to list CodePipeline pipelines: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/put-approval-result/route.ts b/apps/sim/app/api/tools/codepipeline/put-approval-result/route.ts new file mode 100644 index 0000000000..c58d2c27dc --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/put-approval-result/route.ts @@ -0,0 +1,74 @@ +import { + type ApprovalStatus, + CodePipelineClient, + PutApprovalResultCommand, +} from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelinePutApprovalResultContract } from '@/lib/api/contracts/tools/aws/codepipeline-put-approval-result' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelinePutApprovalResult') + +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(awsCodepipelinePutApprovalResultContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Submitting CodePipeline approval result') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new PutApprovalResultCommand({ + pipelineName: validatedData.pipelineName, + stageName: validatedData.stageName, + actionName: validatedData.actionName, + token: validatedData.token, + result: { + status: validatedData.status as ApprovalStatus, + summary: validatedData.summary, + }, + }) + + const response = await client.send(command) + + logger.info('Successfully submitted approval result') + + return NextResponse.json({ + success: true, + output: { + approvedAt: response.approvedAt?.getTime(), + status: validatedData.status, + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('PutApprovalResult failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to submit CodePipeline approval result: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/retry-stage-execution/route.ts b/apps/sim/app/api/tools/codepipeline/retry-stage-execution/route.ts new file mode 100644 index 0000000000..0886a5409f --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/retry-stage-execution/route.ts @@ -0,0 +1,69 @@ +import { + CodePipelineClient, + RetryStageExecutionCommand, + type StageRetryMode, +} from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineRetryStageExecutionContract } from '@/lib/api/contracts/tools/aws/codepipeline-retry-stage-execution' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineRetryStageExecution') + +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(awsCodepipelineRetryStageExecutionContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Retrying CodePipeline stage execution') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new RetryStageExecutionCommand({ + pipelineName: validatedData.pipelineName, + stageName: validatedData.stageName, + pipelineExecutionId: validatedData.pipelineExecutionId, + retryMode: validatedData.retryMode as StageRetryMode, + }) + + const response = await client.send(command) + + logger.info('Successfully retried stage execution') + + return NextResponse.json({ + success: true, + output: { + pipelineExecutionId: response.pipelineExecutionId ?? validatedData.pipelineExecutionId, + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('RetryStageExecution failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to retry CodePipeline stage execution: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/start-execution/route.ts b/apps/sim/app/api/tools/codepipeline/start-execution/route.ts new file mode 100644 index 0000000000..6d438d1bd7 --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/start-execution/route.ts @@ -0,0 +1,69 @@ +import { CodePipelineClient, StartPipelineExecutionCommand } from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineStartExecutionContract } from '@/lib/api/contracts/tools/aws/codepipeline-start-execution' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineStartExecution') + +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(awsCodepipelineStartExecutionContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Starting CodePipeline pipeline execution') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new StartPipelineExecutionCommand({ + name: validatedData.pipelineName, + ...(validatedData.clientRequestToken && { + clientRequestToken: validatedData.clientRequestToken, + }), + ...(validatedData.variables && + validatedData.variables.length > 0 && { variables: validatedData.variables }), + }) + + const response = await client.send(command) + + if (!response.pipelineExecutionId) { + throw new Error('No pipeline execution ID returned') + } + + logger.info('Successfully started pipeline execution') + + return NextResponse.json({ + success: true, + output: { pipelineExecutionId: response.pipelineExecutionId }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('StartPipelineExecution failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to start CodePipeline pipeline execution: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/stop-execution/route.ts b/apps/sim/app/api/tools/codepipeline/stop-execution/route.ts new file mode 100644 index 0000000000..bf01f37945 --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/stop-execution/route.ts @@ -0,0 +1,65 @@ +import { CodePipelineClient, StopPipelineExecutionCommand } from '@aws-sdk/client-codepipeline' +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsCodepipelineStopExecutionContract } from '@/lib/api/contracts/tools/aws/codepipeline-stop-execution' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { awsErrorStatus } from '@/app/api/tools/codepipeline/utils' + +const logger = createLogger('CodePipelineStopExecution') + +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(awsCodepipelineStopExecutionContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const validatedData = parsed.data.body + + logger.info('Stopping CodePipeline pipeline execution') + + const client = new CodePipelineClient({ + region: validatedData.region, + credentials: { + accessKeyId: validatedData.accessKeyId, + secretAccessKey: validatedData.secretAccessKey, + }, + }) + + try { + const command = new StopPipelineExecutionCommand({ + pipelineName: validatedData.pipelineName, + pipelineExecutionId: validatedData.pipelineExecutionId, + ...(validatedData.abandon !== undefined && { abandon: validatedData.abandon }), + ...(validatedData.reason && { reason: validatedData.reason }), + }) + + const response = await client.send(command) + + logger.info('Successfully stopped pipeline execution') + + return NextResponse.json({ + success: true, + output: { + pipelineExecutionId: response.pipelineExecutionId ?? validatedData.pipelineExecutionId, + }, + }) + } finally { + client.destroy() + } + } catch (error) { + logger.error('StopPipelineExecution failed', { error: toError(error).message }) + return NextResponse.json( + { error: `Failed to stop CodePipeline pipeline execution: ${toError(error).message}` }, + { status: awsErrorStatus(error) } + ) + } +}) diff --git a/apps/sim/app/api/tools/codepipeline/utils.ts b/apps/sim/app/api/tools/codepipeline/utils.ts new file mode 100644 index 0000000000..9b191bfdff --- /dev/null +++ b/apps/sim/app/api/tools/codepipeline/utils.ts @@ -0,0 +1,10 @@ +/** + * Maps an AWS SDK error to a response status. Client faults (e.g. + * PipelineNotFoundException, InvalidApprovalTokenException) keep the 4xx + * status AWS reports via `$metadata`; everything else maps to 500. + */ +export function awsErrorStatus(error: unknown): number { + const status = (error as { $metadata?: { httpStatusCode?: number } } | null)?.$metadata + ?.httpStatusCode + return typeof status === 'number' && status >= 400 && status < 500 ? status : 500 +} diff --git a/apps/sim/blocks/blocks/codepipeline.ts b/apps/sim/blocks/blocks/codepipeline.ts new file mode 100644 index 0000000000..b3bd4c36ac --- /dev/null +++ b/apps/sim/blocks/blocks/codepipeline.ts @@ -0,0 +1,608 @@ +import { CodePipelineIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import { + parseOptionalBooleanInput, + parseOptionalJsonInput, + parseOptionalNumberInput, +} from '@/blocks/utils' +import type { + CodePipelineGetPipelineExecutionResponse, + CodePipelineGetPipelineStateResponse, + CodePipelineListPipelineExecutionsResponse, + CodePipelineListPipelinesResponse, + CodePipelinePutApprovalResultResponse, + CodePipelineRetryStageExecutionResponse, + CodePipelineStartExecutionResponse, + CodePipelineStopExecutionResponse, +} from '@/tools/codepipeline/types' + +export const CodePipelineBlock: BlockConfig< + | CodePipelineListPipelinesResponse + | CodePipelineGetPipelineStateResponse + | CodePipelineGetPipelineExecutionResponse + | CodePipelineListPipelineExecutionsResponse + | CodePipelineStartExecutionResponse + | CodePipelineStopExecutionResponse + | CodePipelineRetryStageExecutionResponse + | CodePipelinePutApprovalResultResponse +> = { + type: 'codepipeline', + name: 'CodePipeline', + description: 'Run, monitor, and approve AWS CodePipeline pipelines', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate AWS CodePipeline into workflows. Start, stop, and monitor pipeline executions, retry failed stages, and approve or reject manual approval actions. Requires AWS access key and secret access key.', + docsLink: 'https://docs.sim.ai/tools/codepipeline', + category: 'tools', + integrationType: IntegrationType.DevOps, + bgColor: 'linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)', + iconColor: '#527FFF', + icon: CodePipelineIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Start Execution', id: 'start_execution' }, + { label: 'Get Pipeline State', id: 'get_pipeline_state' }, + { label: 'List Pipelines', id: 'list_pipelines' }, + { label: 'List Executions', id: 'list_pipeline_executions' }, + { label: 'Get Execution', id: 'get_pipeline_execution' }, + { label: 'Stop Execution', id: 'stop_execution' }, + { label: 'Retry Stage', id: 'retry_stage_execution' }, + { label: 'Approve / Reject Approval', id: 'put_approval_result' }, + ], + value: () => 'start_execution', + }, + { + id: 'awsRegion', + title: 'AWS Region', + type: 'short-input', + placeholder: 'us-east-1', + required: true, + }, + { + id: 'awsAccessKeyId', + title: 'AWS Access Key ID', + type: 'short-input', + placeholder: 'AKIA...', + password: true, + required: true, + }, + { + id: 'awsSecretAccessKey', + title: 'AWS Secret Access Key', + type: 'short-input', + placeholder: 'Your secret access key', + password: true, + required: true, + }, + { + id: 'pipelineName', + title: 'Pipeline Name', + type: 'short-input', + placeholder: 'my-pipeline', + condition: { + field: 'operation', + value: [ + 'start_execution', + 'get_pipeline_state', + 'list_pipeline_executions', + 'get_pipeline_execution', + 'stop_execution', + 'retry_stage_execution', + 'put_approval_result', + ], + }, + required: { + field: 'operation', + value: [ + 'start_execution', + 'get_pipeline_state', + 'list_pipeline_executions', + 'get_pipeline_execution', + 'stop_execution', + 'retry_stage_execution', + 'put_approval_result', + ], + }, + }, + { + id: 'pipelineExecutionId', + title: 'Execution ID', + type: 'short-input', + placeholder: 'e.g., 3137f7cb-7cf7-4abc-9f1d-d7eu3471a2b1', + condition: { + field: 'operation', + value: ['get_pipeline_execution', 'stop_execution', 'retry_stage_execution'], + }, + required: { + field: 'operation', + value: ['get_pipeline_execution', 'stop_execution', 'retry_stage_execution'], + }, + }, + { + id: 'stageName', + title: 'Stage Name', + type: 'short-input', + placeholder: 'e.g., Deploy', + condition: { field: 'operation', value: ['retry_stage_execution', 'put_approval_result'] }, + required: { field: 'operation', value: ['retry_stage_execution', 'put_approval_result'] }, + }, + { + id: 'retryMode', + title: 'Retry Mode', + type: 'dropdown', + options: [ + { label: 'Failed Actions Only', id: 'FAILED_ACTIONS' }, + { label: 'All Actions', id: 'ALL_ACTIONS' }, + ], + value: () => 'FAILED_ACTIONS', + condition: { field: 'operation', value: 'retry_stage_execution' }, + required: { field: 'operation', value: 'retry_stage_execution' }, + }, + { + id: 'actionName', + title: 'Approval Action Name', + type: 'short-input', + placeholder: 'e.g., ManualApproval', + condition: { field: 'operation', value: 'put_approval_result' }, + required: { field: 'operation', value: 'put_approval_result' }, + }, + { + id: 'approvalToken', + title: 'Approval Token', + type: 'short-input', + placeholder: 'Token from Get Pipeline State', + condition: { field: 'operation', value: 'put_approval_result' }, + required: { field: 'operation', value: 'put_approval_result' }, + }, + { + id: 'approvalStatus', + title: 'Decision', + type: 'dropdown', + options: [ + { label: 'Approve', id: 'Approved' }, + { label: 'Reject', id: 'Rejected' }, + ], + value: () => 'Approved', + condition: { field: 'operation', value: 'put_approval_result' }, + required: { field: 'operation', value: 'put_approval_result' }, + }, + { + id: 'approvalSummary', + title: 'Summary', + type: 'short-input', + placeholder: 'Why the change is approved or rejected', + condition: { field: 'operation', value: 'put_approval_result' }, + required: { field: 'operation', value: 'put_approval_result' }, + }, + { + id: 'pipelineVariables', + title: 'Pipeline Variables', + type: 'table', + columns: ['name', 'value'], + condition: { field: 'operation', value: 'start_execution' }, + }, + { + id: 'clientRequestToken', + title: 'Client Request Token', + type: 'short-input', + placeholder: 'Idempotency token (letters, digits, hyphens)', + condition: { field: 'operation', value: 'start_execution' }, + mode: 'advanced', + }, + { + id: 'abandon', + title: 'Abandon In-Progress Actions', + type: 'switch', + condition: { field: 'operation', value: 'stop_execution' }, + mode: 'advanced', + }, + { + id: 'stopReason', + title: 'Stop Reason', + type: 'short-input', + placeholder: 'Why the execution is being stopped', + condition: { field: 'operation', value: 'stop_execution' }, + mode: 'advanced', + }, + { + id: 'succeededInStage', + title: 'Succeeded In Stage', + type: 'short-input', + placeholder: 'Only executions that succeeded in this stage', + condition: { field: 'operation', value: 'list_pipeline_executions' }, + mode: 'advanced', + }, + { + id: 'maxResults', + title: 'Max Results', + type: 'short-input', + placeholder: '100', + condition: { field: 'operation', value: ['list_pipelines', 'list_pipeline_executions'] }, + mode: 'advanced', + }, + { + id: 'nextToken', + title: 'Next Token', + type: 'short-input', + placeholder: 'Pagination token from a previous call', + condition: { field: 'operation', value: ['list_pipelines', 'list_pipeline_executions'] }, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'codepipeline_list_pipelines', + 'codepipeline_get_pipeline_state', + 'codepipeline_get_pipeline_execution', + 'codepipeline_list_pipeline_executions', + 'codepipeline_start_execution', + 'codepipeline_stop_execution', + 'codepipeline_retry_stage_execution', + 'codepipeline_put_approval_result', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'list_pipelines': + return 'codepipeline_list_pipelines' + case 'get_pipeline_state': + return 'codepipeline_get_pipeline_state' + case 'get_pipeline_execution': + return 'codepipeline_get_pipeline_execution' + case 'list_pipeline_executions': + return 'codepipeline_list_pipeline_executions' + case 'start_execution': + return 'codepipeline_start_execution' + case 'stop_execution': + return 'codepipeline_stop_execution' + case 'retry_stage_execution': + return 'codepipeline_retry_stage_execution' + case 'put_approval_result': + return 'codepipeline_put_approval_result' + default: + throw new Error(`Invalid CodePipeline operation: ${params.operation}`) + } + }, + params: (params) => { + const { operation, maxResults, ...rest } = params + + const awsRegion = rest.awsRegion + const awsAccessKeyId = rest.awsAccessKeyId + const awsSecretAccessKey = rest.awsSecretAccessKey + const parsedMaxResults = parseOptionalNumberInput(maxResults, 'Max results', { + integer: true, + min: 1, + }) + + switch (operation) { + case 'list_pipelines': + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + ...(parsedMaxResults !== undefined && { maxResults: parsedMaxResults }), + ...(rest.nextToken && { nextToken: rest.nextToken }), + } + + case 'get_pipeline_state': + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + } + + case 'get_pipeline_execution': + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + pipelineExecutionId: rest.pipelineExecutionId, + } + + case 'list_pipeline_executions': + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + ...(parsedMaxResults !== undefined && { maxResults: parsedMaxResults }), + ...(rest.nextToken && { nextToken: rest.nextToken }), + ...(rest.succeededInStage && { succeededInStage: rest.succeededInStage }), + } + + case 'start_execution': { + const rows = parseOptionalJsonInput(rest.pipelineVariables, 'Pipeline variables') + const variables = (() => { + if (rows === undefined) return undefined + if (!Array.isArray(rows)) { + throw new Error('Pipeline variables must be an array of { name, value } objects') + } + const entries = rows + .map((row) => ({ + name: row?.cells?.name ?? row?.name, + value: row?.cells?.value ?? row?.value, + })) + .filter((entry) => entry.name && entry.value !== undefined && entry.value !== '') + .map((entry) => ({ name: String(entry.name), value: String(entry.value) })) + return entries.length > 0 ? entries : undefined + })() + + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + ...(rest.clientRequestToken && { clientRequestToken: rest.clientRequestToken }), + ...(variables && { variables }), + } + } + + case 'stop_execution': { + const abandon = parseOptionalBooleanInput(rest.abandon) + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + pipelineExecutionId: rest.pipelineExecutionId, + ...(abandon !== undefined && { abandon }), + ...(rest.stopReason && { reason: rest.stopReason }), + } + } + + case 'retry_stage_execution': + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + stageName: rest.stageName, + pipelineExecutionId: rest.pipelineExecutionId, + retryMode: rest.retryMode, + } + + case 'put_approval_result': + return { + awsRegion, + awsAccessKeyId, + awsSecretAccessKey, + pipelineName: rest.pipelineName, + stageName: rest.stageName, + actionName: rest.actionName, + token: rest.approvalToken, + status: rest.approvalStatus, + summary: rest.approvalSummary, + } + + default: + throw new Error(`Invalid CodePipeline operation: ${operation}`) + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'CodePipeline operation to perform' }, + awsRegion: { type: 'string', description: 'AWS region' }, + awsAccessKeyId: { type: 'string', description: 'AWS access key ID' }, + awsSecretAccessKey: { type: 'string', description: 'AWS secret access key' }, + pipelineName: { type: 'string', description: 'Pipeline name' }, + pipelineExecutionId: { type: 'string', description: 'Pipeline execution ID' }, + stageName: { type: 'string', description: 'Stage name for retry or approval' }, + retryMode: { + type: 'string', + description: 'Retry scope: FAILED_ACTIONS or ALL_ACTIONS', + }, + actionName: { type: 'string', description: 'Manual approval action name' }, + approvalToken: { + type: 'string', + description: 'Approval token from Get Pipeline State for the pending approval', + }, + approvalStatus: { type: 'string', description: 'Approval decision: Approved or Rejected' }, + approvalSummary: { type: 'string', description: 'Summary explaining the approval decision' }, + pipelineVariables: { + type: 'json', + description: 'Pipeline variable overrides (name/value pairs)', + }, + clientRequestToken: { + type: 'string', + description: 'Idempotency token for starting an execution', + }, + abandon: { + type: 'boolean', + description: 'Abandon in-progress actions instead of letting them finish', + }, + stopReason: { type: 'string', description: 'Reason for stopping the execution' }, + succeededInStage: { + type: 'string', + description: 'Only list executions that succeeded in this stage', + }, + maxResults: { type: 'number', description: 'Maximum number of results' }, + nextToken: { type: 'string', description: 'Pagination token from a previous call' }, + }, + outputs: { + pipelines: { + type: 'array', + description: 'List of pipelines with name, version, type, and timestamps', + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + }, + pipelineName: { + type: 'string', + description: 'Pipeline name', + }, + pipelineVersion: { + type: 'number', + description: 'Pipeline version number', + }, + created: { + type: 'number', + description: 'Epoch ms when the pipeline was created', + }, + updated: { + type: 'number', + description: 'Epoch ms when the pipeline was last updated', + }, + stageStates: { + type: 'array', + description: 'Per-stage state including action status and pending approval tokens', + }, + pipelineExecutionId: { + type: 'string', + description: 'Pipeline execution ID', + }, + status: { + type: 'string', + description: 'Execution status or submitted approval decision', + }, + statusSummary: { + type: 'string', + description: 'Status summary for the execution', + }, + executionMode: { + type: 'string', + description: 'Execution mode (QUEUED, SUPERSEDED, PARALLEL)', + }, + executionType: { + type: 'string', + description: 'Execution type (STANDARD or ROLLBACK)', + }, + triggerType: { + type: 'string', + description: 'What triggered the execution', + }, + triggerDetail: { + type: 'string', + description: 'Detail about the trigger', + }, + artifactRevisions: { + type: 'array', + description: 'Source artifact revisions for the execution', + }, + variables: { + type: 'array', + description: 'Resolved pipeline variables for the execution', + }, + executions: { + type: 'array', + description: 'Pipeline execution summaries, most recent first', + }, + approvedAt: { + type: 'number', + description: 'Epoch ms when the approval or rejection was submitted', + }, + }, +} + +export const CodePipelineBlockMeta = { + tags: ['cloud', 'ci-cd'], + templates: [ + { + icon: CodePipelineIcon, + title: 'CodePipeline deploy approver', + prompt: + 'Build a workflow that checks a CodePipeline pipeline for pending manual approvals, posts the change summary and source revisions to Slack, and approves or rejects the deployment based on the team lead reply.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'ci-cd', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: CodePipelineIcon, + title: 'CodePipeline failure triage', + prompt: + 'Create a scheduled workflow that polls CodePipeline executions every few minutes, and when one fails, pulls the pipeline state to find the failing stage and action error, opens a Linear issue, and alerts the on-call channel in Slack.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'monitoring', 'automation'], + alsoIntegrations: ['linear', 'slack'], + }, + { + icon: CodePipelineIcon, + title: 'CodePipeline release train', + prompt: + 'Build a scheduled workflow that starts the release CodePipeline pipeline every weekday at 9am with the release version as a pipeline variable, then posts the execution ID and a link to Slack.', + modules: ['scheduled', 'workflows'], + category: 'engineering', + tags: ['devops', 'ci-cd', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: CodePipelineIcon, + title: 'CodePipeline deploy digest', + prompt: + 'Create a scheduled daily workflow that lists executions across the team CodePipeline pipelines, summarizes successes, failures, and rollbacks with their source revisions, and posts a digest to Slack.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'reporting'], + alsoIntegrations: ['slack'], + }, + { + icon: CodePipelineIcon, + title: 'CodePipeline flaky-stage retrier', + prompt: + 'Build a scheduled workflow that finds failed CodePipeline executions, retries the failed stage once with failed-actions mode, and escalates to PagerDuty if the retry fails again.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'automation', 'monitoring'], + alsoIntegrations: ['pagerduty'], + }, + { + icon: CodePipelineIcon, + title: 'CodePipeline rollback brake', + prompt: + 'Create a workflow that watches CloudWatch alarms after a deployment, and when an error-rate alarm fires while a CodePipeline execution is in progress, stops the execution with a reason and notifies the release channel.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'monitoring', 'automation'], + alsoIntegrations: ['cloudwatch', 'slack'], + }, + { + icon: CodePipelineIcon, + title: 'CodePipeline deployment audit log', + prompt: + 'Build a scheduled workflow that records every CodePipeline execution — pipeline, status, trigger, source revisions, and timing — into a table for compliance and deployment-frequency reporting.', + modules: ['scheduled', 'tables', 'workflows'], + category: 'operations', + tags: ['devops', 'enterprise', 'reporting'], + }, + ], + skills: [ + { + name: 'approve-pending-deployment', + description: + 'Find a pending CodePipeline manual approval, summarize the change, and approve or reject it.', + content: + '# Approve Pending CodePipeline Deployment\n\nHandle a manual approval gate in a pipeline.\n\n## Steps\n1. Get the pipeline state and locate the action awaiting approval (status InProgress on a manual approval action) and its approval token.\n2. Pull the execution details for the stage to summarize what is being deployed (source revisions, trigger).\n3. Submit the approval result with the token, the Approved or Rejected decision, and a summary explaining the decision.\n\n## Output\nThe decision that was submitted, the approval summary, and the pipeline/stage/action it applied to.', + }, + { + name: 'investigate-failed-pipeline', + description: + 'Find the failing stage and action of a CodePipeline execution and report the error details.', + content: + '# Investigate Failed CodePipeline Execution\n\nDiagnose why a pipeline run failed.\n\n## Steps\n1. List recent executions for the pipeline and identify the failed one (or use the provided execution ID).\n2. Get the pipeline state and find the stage and action with a Failed status.\n3. Capture the action error code, error message, and external execution URL, plus the source revisions that were being deployed.\n\n## Output\nThe failing stage and action, the error details, the commit/revision involved, and a link to the external execution.', + }, + { + name: 'trigger-pipeline-release', + description: + 'Start a CodePipeline execution, optionally with variable overrides, and report the execution ID.', + content: + '# Trigger CodePipeline Release\n\nKick off a pipeline run.\n\n## Steps\n1. Confirm the pipeline name (list pipelines if unsure).\n2. Start the execution, passing any pipeline variable overrides (e.g. version or environment) and an idempotency token if retries are possible.\n3. Optionally poll the pipeline state to confirm the execution entered the first stage.\n\n## Output\nThe pipeline execution ID that was started and the variables it ran with.', + }, + { + name: 'retry-failed-stage', + description: + 'Retry the failed actions of a CodePipeline stage and confirm the stage re-entered execution.', + content: + '# Retry Failed CodePipeline Stage\n\nRe-run a failed stage without restarting the whole pipeline.\n\n## Steps\n1. Get the pipeline state and identify the failed stage and the execution ID stuck in it.\n2. Retry the stage with FAILED_ACTIONS mode (or ALL_ACTIONS if the whole stage should re-run).\n3. Check the pipeline state again to confirm the stage is InProgress.\n\n## Output\nThe stage that was retried, the retry mode used, and the current stage status.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 53e346953d..b731f65ba8 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -34,6 +34,7 @@ import { ClickHouseBlock, ClickHouseBlockMeta } from '@/blocks/blocks/clickhouse import { CloudflareBlock, CloudflareBlockMeta } from '@/blocks/blocks/cloudflare' import { CloudFormationBlock, CloudFormationBlockMeta } from '@/blocks/blocks/cloudformation' import { CloudWatchBlock, CloudWatchBlockMeta } from '@/blocks/blocks/cloudwatch' +import { CodePipelineBlock, CodePipelineBlockMeta } from '@/blocks/blocks/codepipeline' import { ConditionBlock } from '@/blocks/blocks/condition' import { ConfluenceBlock, ConfluenceBlockMeta, ConfluenceV2Block } from '@/blocks/blocks/confluence' import { CredentialBlock } from '@/blocks/blocks/credential' @@ -351,6 +352,7 @@ const BLOCK_REGISTRY: Record = { cloudflare: CloudflareBlock, cloudformation: CloudFormationBlock, cloudwatch: CloudWatchBlock, + codepipeline: CodePipelineBlock, condition: ConditionBlock, confluence: ConfluenceBlock, confluence_v2: ConfluenceV2Block, @@ -639,6 +641,7 @@ const BLOCK_META_REGISTRY: Record = { cloudflare: CloudflareBlockMeta, cloudformation: CloudFormationBlockMeta, cloudwatch: CloudWatchBlockMeta, + codepipeline: CodePipelineBlockMeta, confluence: ConfluenceBlockMeta, crowdstrike: CrowdStrikeBlockMeta, cursor: CursorBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 41732af3b2..8eb32d5e6f 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5512,6 +5512,33 @@ export function CloudWatchIcon(props: SVGProps) { ) } +export function CodePipelineIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function TextractIcon(props: SVGProps) { return ( validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), + pipelineExecutionId: z.string().min(1, 'Pipeline execution ID is required'), +}) + +const GetPipelineExecutionResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + pipelineExecutionId: z.string(), + pipelineName: z.string(), + pipelineVersion: z.number().optional(), + status: z.string(), + statusSummary: z.string().optional(), + executionMode: z.string().optional(), + executionType: z.string().optional(), + triggerType: z.string().optional(), + triggerDetail: z.string().optional(), + artifactRevisions: z.array( + z.object({ + name: z.string(), + revisionId: z.string().optional(), + revisionSummary: z.string().optional(), + revisionUrl: z.string().optional(), + created: z.number().optional(), + }) + ), + variables: z.array( + z.object({ + name: z.string(), + resolvedValue: z.string(), + }) + ), + }), +}) + +export const awsCodepipelineGetPipelineExecutionContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/get-pipeline-execution', + body: GetPipelineExecutionSchema, + response: { mode: 'json', schema: GetPipelineExecutionResponseSchema }, +}) +export type AwsCodepipelineGetPipelineExecutionRequest = ContractBodyInput< + typeof awsCodepipelineGetPipelineExecutionContract +> +export type AwsCodepipelineGetPipelineExecutionBody = ContractBody< + typeof awsCodepipelineGetPipelineExecutionContract +> +export type AwsCodepipelineGetPipelineExecutionResponse = ContractJsonResponse< + typeof awsCodepipelineGetPipelineExecutionContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-get-pipeline-state.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-get-pipeline-state.ts new file mode 100644 index 0000000000..5e23204f86 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-get-pipeline-state.ts @@ -0,0 +1,73 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const GetPipelineStateSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), +}) + +const ActionStateSchema = z.object({ + actionName: z.string(), + status: z.string().optional(), + summary: z.string().optional(), + lastStatusChange: z.number().optional(), + externalExecutionId: z.string().optional(), + externalExecutionUrl: z.string().optional(), + errorCode: z.string().optional(), + errorMessage: z.string().optional(), + percentComplete: z.number().optional(), + token: z.string().optional(), + revisionId: z.string().optional(), + entityUrl: z.string().optional(), +}) + +const StageStateSchema = z.object({ + stageName: z.string(), + status: z.string().optional(), + pipelineExecutionId: z.string().optional(), + inboundTransitionEnabled: z.boolean().optional(), + actionStates: z.array(ActionStateSchema), +}) + +const GetPipelineStateResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + pipelineName: z.string(), + pipelineVersion: z.number().optional(), + created: z.number().optional(), + updated: z.number().optional(), + stageStates: z.array(StageStateSchema), + }), +}) + +export const awsCodepipelineGetPipelineStateContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/get-pipeline-state', + body: GetPipelineStateSchema, + response: { mode: 'json', schema: GetPipelineStateResponseSchema }, +}) +export type AwsCodepipelineGetPipelineStateRequest = ContractBodyInput< + typeof awsCodepipelineGetPipelineStateContract +> +export type AwsCodepipelineGetPipelineStateBody = ContractBody< + typeof awsCodepipelineGetPipelineStateContract +> +export type AwsCodepipelineGetPipelineStateResponse = ContractJsonResponse< + typeof awsCodepipelineGetPipelineStateContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-list-pipeline-executions.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-list-pipeline-executions.ts new file mode 100644 index 0000000000..be2b912da1 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-list-pipeline-executions.ts @@ -0,0 +1,74 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const ListPipelineExecutionsSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), + maxResults: z.preprocess( + (v) => (v === '' || v === undefined || v === null ? undefined : v), + z.coerce.number().int().min(1).max(100).optional() + ), + nextToken: z.string().min(1).max(2048).optional(), + succeededInStage: z.string().min(1).max(100).optional(), +}) + +const PipelineExecutionSummarySchema = z.object({ + pipelineExecutionId: z.string(), + status: z.string(), + statusSummary: z.string().optional(), + startTime: z.number().optional(), + lastUpdateTime: z.number().optional(), + executionMode: z.string().optional(), + executionType: z.string().optional(), + stopTriggerReason: z.string().optional(), + triggerType: z.string().optional(), + triggerDetail: z.string().optional(), + sourceRevisions: z.array( + z.object({ + actionName: z.string(), + revisionId: z.string().optional(), + revisionSummary: z.string().optional(), + revisionUrl: z.string().optional(), + }) + ), +}) + +const ListPipelineExecutionsResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + executions: z.array(PipelineExecutionSummarySchema), + nextToken: z.string().optional(), + }), +}) + +export const awsCodepipelineListPipelineExecutionsContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/list-pipeline-executions', + body: ListPipelineExecutionsSchema, + response: { mode: 'json', schema: ListPipelineExecutionsResponseSchema }, +}) +export type AwsCodepipelineListPipelineExecutionsRequest = ContractBodyInput< + typeof awsCodepipelineListPipelineExecutionsContract +> +export type AwsCodepipelineListPipelineExecutionsBody = ContractBody< + typeof awsCodepipelineListPipelineExecutionsContract +> +export type AwsCodepipelineListPipelineExecutionsResponse = ContractJsonResponse< + typeof awsCodepipelineListPipelineExecutionsContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-list-pipelines.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-list-pipelines.ts new file mode 100644 index 0000000000..997891aee4 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-list-pipelines.ts @@ -0,0 +1,57 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const ListPipelinesSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + maxResults: z.preprocess( + (v) => (v === '' || v === undefined || v === null ? undefined : v), + z.coerce.number().int().min(1).max(1000).optional() + ), + nextToken: z.string().min(1).max(2048).optional(), +}) + +const ListPipelinesResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + pipelines: z.array( + z.object({ + name: z.string(), + version: z.number().optional(), + pipelineType: z.string().optional(), + executionMode: z.string().optional(), + created: z.number().optional(), + updated: z.number().optional(), + }) + ), + nextToken: z.string().optional(), + }), +}) + +export const awsCodepipelineListPipelinesContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/list-pipelines', + body: ListPipelinesSchema, + response: { mode: 'json', schema: ListPipelinesResponseSchema }, +}) +export type AwsCodepipelineListPipelinesRequest = ContractBodyInput< + typeof awsCodepipelineListPipelinesContract +> +export type AwsCodepipelineListPipelinesBody = ContractBody< + typeof awsCodepipelineListPipelinesContract +> +export type AwsCodepipelineListPipelinesResponse = ContractJsonResponse< + typeof awsCodepipelineListPipelinesContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-put-approval-result.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-put-approval-result.ts new file mode 100644 index 0000000000..8e50edb52b --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-put-approval-result.ts @@ -0,0 +1,61 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const PutApprovalResultSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), + stageName: z + .string() + .min(1, 'Stage name is required') + .max(100, 'Stage name must be at most 100 characters'), + actionName: z + .string() + .min(1, 'Action name is required') + .max(100, 'Action name must be at most 100 characters'), + token: z.string().min(1, 'Approval token is required'), + status: z.enum(['Approved', 'Rejected']), + summary: z + .string() + .min(1, 'Approval summary is required') + .max(512, 'Approval summary must be at most 512 characters'), +}) + +const PutApprovalResultResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + approvedAt: z.number().optional(), + status: z.string(), + }), +}) + +export const awsCodepipelinePutApprovalResultContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/put-approval-result', + body: PutApprovalResultSchema, + response: { mode: 'json', schema: PutApprovalResultResponseSchema }, +}) +export type AwsCodepipelinePutApprovalResultRequest = ContractBodyInput< + typeof awsCodepipelinePutApprovalResultContract +> +export type AwsCodepipelinePutApprovalResultBody = ContractBody< + typeof awsCodepipelinePutApprovalResultContract +> +export type AwsCodepipelinePutApprovalResultResponse = ContractJsonResponse< + typeof awsCodepipelinePutApprovalResultContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-retry-stage-execution.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-retry-stage-execution.ts new file mode 100644 index 0000000000..ad3c0c044a --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-retry-stage-execution.ts @@ -0,0 +1,52 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const RetryStageExecutionSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), + stageName: z + .string() + .min(1, 'Stage name is required') + .max(100, 'Stage name must be at most 100 characters'), + pipelineExecutionId: z.string().min(1, 'Pipeline execution ID is required'), + retryMode: z.enum(['FAILED_ACTIONS', 'ALL_ACTIONS']), +}) + +const RetryStageExecutionResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + pipelineExecutionId: z.string(), + }), +}) + +export const awsCodepipelineRetryStageExecutionContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/retry-stage-execution', + body: RetryStageExecutionSchema, + response: { mode: 'json', schema: RetryStageExecutionResponseSchema }, +}) +export type AwsCodepipelineRetryStageExecutionRequest = ContractBodyInput< + typeof awsCodepipelineRetryStageExecutionContract +> +export type AwsCodepipelineRetryStageExecutionBody = ContractBody< + typeof awsCodepipelineRetryStageExecutionContract +> +export type AwsCodepipelineRetryStageExecutionResponse = ContractJsonResponse< + typeof awsCodepipelineRetryStageExecutionContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-start-execution.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-start-execution.ts new file mode 100644 index 0000000000..30affbe3a6 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-start-execution.ts @@ -0,0 +1,62 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const StartExecutionSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), + clientRequestToken: z + .string() + .min(1) + .max(128) + .regex(/^[a-zA-Z0-9-]+$/, 'Client request token may only contain letters, digits, and hyphens') + .optional(), + variables: z + .array( + z.object({ + name: z.string().min(1, 'Variable name is required'), + value: z.string().min(1, 'Variable value cannot be empty'), + }) + ) + .min(1) + .max(50) + .optional(), +}) + +const StartExecutionResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + pipelineExecutionId: z.string(), + }), +}) + +export const awsCodepipelineStartExecutionContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/start-execution', + body: StartExecutionSchema, + response: { mode: 'json', schema: StartExecutionResponseSchema }, +}) +export type AwsCodepipelineStartExecutionRequest = ContractBodyInput< + typeof awsCodepipelineStartExecutionContract +> +export type AwsCodepipelineStartExecutionBody = ContractBody< + typeof awsCodepipelineStartExecutionContract +> +export type AwsCodepipelineStartExecutionResponse = ContractJsonResponse< + typeof awsCodepipelineStartExecutionContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/codepipeline-stop-execution.ts b/apps/sim/lib/api/contracts/tools/aws/codepipeline-stop-execution.ts new file mode 100644 index 0000000000..44399128fb --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/codepipeline-stop-execution.ts @@ -0,0 +1,49 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const StopExecutionSchema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + pipelineName: z + .string() + .min(1, 'Pipeline name is required') + .max(100, 'Pipeline name must be at most 100 characters'), + pipelineExecutionId: z.string().min(1, 'Pipeline execution ID is required'), + abandon: z.boolean().optional(), + reason: z.string().max(200, 'Stop reason must be at most 200 characters').optional(), +}) + +const StopExecutionResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + pipelineExecutionId: z.string(), + }), +}) + +export const awsCodepipelineStopExecutionContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/codepipeline/stop-execution', + body: StopExecutionSchema, + response: { mode: 'json', schema: StopExecutionResponseSchema }, +}) +export type AwsCodepipelineStopExecutionRequest = ContractBodyInput< + typeof awsCodepipelineStopExecutionContract +> +export type AwsCodepipelineStopExecutionBody = ContractBody< + typeof awsCodepipelineStopExecutionContract +> +export type AwsCodepipelineStopExecutionResponse = ContractJsonResponse< + typeof awsCodepipelineStopExecutionContract +> diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 7704a069ac..014f09ed9b 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -35,6 +35,7 @@ import { CloudFormationIcon, CloudflareIcon, CloudWatchIcon, + CodePipelineIcon, ConfluenceIcon, CrowdStrikeIcon, CursorIcon, @@ -241,6 +242,7 @@ export const blockTypeToIconMap: Record = { cloudflare: CloudflareIcon, cloudformation: CloudFormationIcon, cloudwatch: CloudWatchIcon, + codepipeline: CodePipelineIcon, confluence_v2: ConfluenceIcon, crowdstrike: CrowdStrikeIcon, cursor_v2: CursorIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index dc45cec20f..8828250001 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -2826,6 +2826,57 @@ "integrationType": "observability", "tags": ["cloud", "monitoring"] }, + { + "type": "codepipeline", + "slug": "codepipeline", + "name": "CodePipeline", + "description": "Run, monitor, and approve AWS CodePipeline pipelines", + "longDescription": "Integrate AWS CodePipeline into workflows. Start, stop, and monitor pipeline executions, retry failed stages, and approve or reject manual approval actions. Requires AWS access key and secret access key.", + "bgColor": "linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)", + "iconName": "CodePipelineIcon", + "docsUrl": "https://docs.sim.ai/tools/codepipeline", + "operations": [ + { + "name": "Start Execution", + "description": "Start a CodePipeline pipeline execution, optionally overriding pipeline variables" + }, + { + "name": "Get Pipeline State", + "description": "Get the current state of a CodePipeline pipeline, including stage and action status and pending approval tokens" + }, + { + "name": "List Pipelines", + "description": "List all CodePipeline pipelines in an AWS account and region" + }, + { + "name": "List Executions", + "description": "List recent executions of a CodePipeline pipeline with status and source revisions" + }, + { + "name": "Get Execution", + "description": "Get details of a CodePipeline execution, including status, trigger, source revisions, and resolved variables" + }, + { + "name": "Stop Execution", + "description": "Stop a CodePipeline pipeline execution, either finishing in-progress actions or abandoning them" + }, + { + "name": "Retry Stage", + "description": "Retry the failed actions (or all actions) of a failed CodePipeline stage" + }, + { + "name": "Approve / Reject Approval", + "description": "Approve or reject a pending CodePipeline manual approval action. The approval token is available from Get Pipeline State on the pending approval action" + } + ], + "operationCount": 8, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "devops", + "tags": ["cloud", "ci-cd"] + }, { "type": "confluence_v2", "slug": "confluence", diff --git a/apps/sim/package.json b/apps/sim/package.json index 259f9dca72..31d799eac3 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -40,6 +40,7 @@ "@aws-sdk/client-cloudformation": "3.1032.0", "@aws-sdk/client-cloudwatch": "3.1032.0", "@aws-sdk/client-cloudwatch-logs": "3.1032.0", + "@aws-sdk/client-codepipeline": "3.1032.0", "@aws-sdk/client-dynamodb": "3.1032.0", "@aws-sdk/client-iam": "3.1032.0", "@aws-sdk/client-identitystore": "3.1032.0", diff --git a/apps/sim/tools/codepipeline/get_pipeline_execution.ts b/apps/sim/tools/codepipeline/get_pipeline_execution.ts new file mode 100644 index 0000000000..12c47a59f8 --- /dev/null +++ b/apps/sim/tools/codepipeline/get_pipeline_execution.ts @@ -0,0 +1,153 @@ +import type { + CodePipelineGetPipelineExecutionParams, + CodePipelineGetPipelineExecutionResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const getPipelineExecutionTool: ToolConfig< + CodePipelineGetPipelineExecutionParams, + CodePipelineGetPipelineExecutionResponse +> = { + id: 'codepipeline_get_pipeline_execution', + name: 'CodePipeline Get Pipeline Execution', + description: + 'Get details of a CodePipeline execution, including status, trigger, source revisions, and resolved variables', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline', + }, + pipelineExecutionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the pipeline execution', + }, + }, + + request: { + url: '/api/tools/codepipeline/get-pipeline-execution', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + pipelineExecutionId: params.pipelineExecutionId, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to get CodePipeline pipeline execution') + } + + return { + success: true, + output: { + pipelineExecutionId: data.output.pipelineExecutionId, + pipelineName: data.output.pipelineName, + pipelineVersion: data.output.pipelineVersion, + status: data.output.status, + statusSummary: data.output.statusSummary, + executionMode: data.output.executionMode, + executionType: data.output.executionType, + triggerType: data.output.triggerType, + triggerDetail: data.output.triggerDetail, + artifactRevisions: data.output.artifactRevisions, + variables: data.output.variables, + }, + } + }, + + outputs: { + pipelineExecutionId: { type: 'string', description: 'Pipeline execution ID' }, + pipelineName: { type: 'string', description: 'Pipeline name' }, + pipelineVersion: { type: 'number', description: 'Pipeline version number', optional: true }, + status: { + type: 'string', + description: + 'Execution status (Cancelled, InProgress, Stopped, Stopping, Succeeded, Superseded, Failed)', + }, + statusSummary: { + type: 'string', + description: 'Status summary for the execution', + optional: true, + }, + executionMode: { + type: 'string', + description: 'Execution mode (QUEUED, SUPERSEDED, PARALLEL)', + optional: true, + }, + executionType: { + type: 'string', + description: 'Execution type (STANDARD or ROLLBACK)', + optional: true, + }, + triggerType: { + type: 'string', + description: 'What triggered the execution (e.g., Webhook, StartPipelineExecution)', + optional: true, + }, + triggerDetail: { + type: 'string', + description: 'Detail about the trigger (e.g., user ARN)', + optional: true, + }, + artifactRevisions: { + type: 'array', + description: 'Source artifact revisions for the execution', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Artifact name' }, + revisionId: { type: 'string', description: 'Revision ID (e.g., commit SHA)' }, + revisionSummary: { + type: 'string', + description: 'Revision summary (e.g., commit message)', + }, + revisionUrl: { type: 'string', description: 'URL of the revision' }, + created: { type: 'number', description: 'Epoch ms when the revision was created' }, + }, + }, + }, + variables: { + type: 'array', + description: 'Resolved pipeline variables for the execution', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Variable name' }, + resolvedValue: { type: 'string', description: 'Resolved variable value' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/codepipeline/get_pipeline_state.ts b/apps/sim/tools/codepipeline/get_pipeline_state.ts new file mode 100644 index 0000000000..e850ae682f --- /dev/null +++ b/apps/sim/tools/codepipeline/get_pipeline_state.ts @@ -0,0 +1,119 @@ +import type { + CodePipelineGetPipelineStateParams, + CodePipelineGetPipelineStateResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const getPipelineStateTool: ToolConfig< + CodePipelineGetPipelineStateParams, + CodePipelineGetPipelineStateResponse +> = { + id: 'codepipeline_get_pipeline_state', + name: 'CodePipeline Get Pipeline State', + description: + 'Get the current state of a CodePipeline pipeline, including stage and action status and pending approval tokens', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline', + }, + }, + + request: { + url: '/api/tools/codepipeline/get-pipeline-state', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to get CodePipeline pipeline state') + } + + return { + success: true, + output: { + pipelineName: data.output.pipelineName, + pipelineVersion: data.output.pipelineVersion, + created: data.output.created, + updated: data.output.updated, + stageStates: data.output.stageStates, + }, + } + }, + + outputs: { + pipelineName: { type: 'string', description: 'Pipeline name' }, + pipelineVersion: { type: 'number', description: 'Pipeline version number', optional: true }, + created: { + type: 'number', + description: 'Epoch ms when the pipeline was created', + optional: true, + }, + updated: { + type: 'number', + description: 'Epoch ms when the pipeline was last updated', + optional: true, + }, + stageStates: { + type: 'array', + description: 'Per-stage state including latest execution status and action details', + items: { + type: 'object', + properties: { + stageName: { type: 'string', description: 'Stage name' }, + status: { + type: 'string', + description: + 'Latest stage execution status (InProgress, Succeeded, Failed, Stopped, Cancelled)', + }, + pipelineExecutionId: { + type: 'string', + description: 'Pipeline execution ID currently in the stage', + }, + inboundTransitionEnabled: { + type: 'boolean', + description: 'Whether the inbound transition into the stage is enabled', + }, + actionStates: { + type: 'array', + description: + 'Per-action state with status, summary, error details, and approval token (for pending manual approvals)', + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/codepipeline/index.ts b/apps/sim/tools/codepipeline/index.ts new file mode 100644 index 0000000000..9ceeb9baf1 --- /dev/null +++ b/apps/sim/tools/codepipeline/index.ts @@ -0,0 +1,19 @@ +import { getPipelineExecutionTool } from '@/tools/codepipeline/get_pipeline_execution' +import { getPipelineStateTool } from '@/tools/codepipeline/get_pipeline_state' +import { listPipelineExecutionsTool } from '@/tools/codepipeline/list_pipeline_executions' +import { listPipelinesTool } from '@/tools/codepipeline/list_pipelines' +import { putApprovalResultTool } from '@/tools/codepipeline/put_approval_result' +import { retryStageExecutionTool } from '@/tools/codepipeline/retry_stage_execution' +import { startExecutionTool } from '@/tools/codepipeline/start_execution' +import { stopExecutionTool } from '@/tools/codepipeline/stop_execution' + +export * from './types' + +export const codepipelineGetPipelineExecutionTool = getPipelineExecutionTool +export const codepipelineGetPipelineStateTool = getPipelineStateTool +export const codepipelineListPipelineExecutionsTool = listPipelineExecutionsTool +export const codepipelineListPipelinesTool = listPipelinesTool +export const codepipelinePutApprovalResultTool = putApprovalResultTool +export const codepipelineRetryStageExecutionTool = retryStageExecutionTool +export const codepipelineStartExecutionTool = startExecutionTool +export const codepipelineStopExecutionTool = stopExecutionTool diff --git a/apps/sim/tools/codepipeline/list_pipeline_executions.ts b/apps/sim/tools/codepipeline/list_pipeline_executions.ts new file mode 100644 index 0000000000..7b8c2d977a --- /dev/null +++ b/apps/sim/tools/codepipeline/list_pipeline_executions.ts @@ -0,0 +1,140 @@ +import type { + CodePipelineListPipelineExecutionsParams, + CodePipelineListPipelineExecutionsResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const listPipelineExecutionsTool: ToolConfig< + CodePipelineListPipelineExecutionsParams, + CodePipelineListPipelineExecutionsResponse +> = { + id: 'codepipeline_list_pipeline_executions', + name: 'CodePipeline List Pipeline Executions', + description: 'List recent executions of a CodePipeline pipeline with status and source revisions', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of executions to return (1-100, default 100)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous call', + }, + succeededInStage: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return executions that succeeded in this stage', + }, + }, + + request: { + url: '/api/tools/codepipeline/list-pipeline-executions', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken && { nextToken: params.nextToken }), + ...(params.succeededInStage && { succeededInStage: params.succeededInStage }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to list CodePipeline pipeline executions') + } + + return { + success: true, + output: { + executions: data.output.executions, + nextToken: data.output.nextToken, + }, + } + }, + + outputs: { + executions: { + type: 'array', + description: 'Pipeline execution summaries, most recent first', + items: { + type: 'object', + properties: { + pipelineExecutionId: { type: 'string', description: 'Pipeline execution ID' }, + status: { + type: 'string', + description: + 'Execution status (Cancelled, InProgress, Stopped, Stopping, Succeeded, Superseded, Failed)', + }, + statusSummary: { type: 'string', description: 'Status summary for the execution' }, + startTime: { type: 'number', description: 'Epoch ms when the execution started' }, + lastUpdateTime: { + type: 'number', + description: 'Epoch ms when the execution was last updated', + }, + executionMode: { + type: 'string', + description: 'Execution mode (QUEUED, SUPERSEDED, PARALLEL)', + }, + executionType: { + type: 'string', + description: 'Execution type (STANDARD or ROLLBACK)', + }, + stopTriggerReason: { + type: 'string', + description: 'Reason the execution was stopped, if applicable', + }, + triggerType: { type: 'string', description: 'What triggered the execution' }, + triggerDetail: { type: 'string', description: 'Detail about the trigger' }, + sourceRevisions: { + type: 'array', + description: 'Source revisions (commit IDs, summaries, URLs) for the execution', + }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/codepipeline/list_pipelines.ts b/apps/sim/tools/codepipeline/list_pipelines.ts new file mode 100644 index 0000000000..dd4a35f3f1 --- /dev/null +++ b/apps/sim/tools/codepipeline/list_pipelines.ts @@ -0,0 +1,105 @@ +import type { + CodePipelineListPipelinesParams, + CodePipelineListPipelinesResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const listPipelinesTool: ToolConfig< + CodePipelineListPipelinesParams, + CodePipelineListPipelinesResponse +> = { + id: 'codepipeline_list_pipelines', + name: 'CodePipeline List Pipelines', + description: 'List all CodePipeline pipelines in an AWS account and region', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of pipelines to return (1-1000)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous call', + }, + }, + + request: { + url: '/api/tools/codepipeline/list-pipelines', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to list CodePipeline pipelines') + } + + return { + success: true, + output: { + pipelines: data.output.pipelines, + nextToken: data.output.nextToken, + }, + } + }, + + outputs: { + pipelines: { + type: 'array', + description: 'List of pipelines with name, version, type, and timestamps', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Pipeline name' }, + version: { type: 'number', description: 'Pipeline version number' }, + pipelineType: { type: 'string', description: 'Pipeline type (V1 or V2)' }, + executionMode: { + type: 'string', + description: 'Execution mode (QUEUED, SUPERSEDED, PARALLEL)', + }, + created: { type: 'number', description: 'Epoch ms when the pipeline was created' }, + updated: { type: 'number', description: 'Epoch ms when the pipeline was last updated' }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page of results', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/codepipeline/put_approval_result.ts b/apps/sim/tools/codepipeline/put_approval_result.ts new file mode 100644 index 0000000000..8199eb0f74 --- /dev/null +++ b/apps/sim/tools/codepipeline/put_approval_result.ts @@ -0,0 +1,120 @@ +import type { + CodePipelinePutApprovalResultParams, + CodePipelinePutApprovalResultResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const putApprovalResultTool: ToolConfig< + CodePipelinePutApprovalResultParams, + CodePipelinePutApprovalResultResponse +> = { + id: 'codepipeline_put_approval_result', + name: 'CodePipeline Put Approval Result', + description: + 'Approve or reject a pending CodePipeline manual approval action. The approval token is available from Get Pipeline State on the pending approval action', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline', + }, + stageName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the stage containing the approval action', + }, + actionName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the manual approval action', + }, + token: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Approval token from Get Pipeline State for the pending approval', + }, + status: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Approval decision: Approved or Rejected', + }, + summary: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Summary explaining the approval decision (max 512 characters)', + }, + }, + + request: { + url: '/api/tools/codepipeline/put-approval-result', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + stageName: params.stageName, + actionName: params.actionName, + token: params.token, + status: params.status, + summary: params.summary, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to submit CodePipeline approval result') + } + + return { + success: true, + output: { + approvedAt: data.output.approvedAt, + status: data.output.status, + }, + } + }, + + outputs: { + approvedAt: { + type: 'number', + description: 'Epoch ms when the approval or rejection was submitted', + optional: true, + }, + status: { + type: 'string', + description: 'The submitted approval decision (Approved or Rejected)', + }, + }, +} diff --git a/apps/sim/tools/codepipeline/retry_stage_execution.ts b/apps/sim/tools/codepipeline/retry_stage_execution.ts new file mode 100644 index 0000000000..1a106ef632 --- /dev/null +++ b/apps/sim/tools/codepipeline/retry_stage_execution.ts @@ -0,0 +1,99 @@ +import type { + CodePipelineRetryStageExecutionParams, + CodePipelineRetryStageExecutionResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const retryStageExecutionTool: ToolConfig< + CodePipelineRetryStageExecutionParams, + CodePipelineRetryStageExecutionResponse +> = { + id: 'codepipeline_retry_stage_execution', + name: 'CodePipeline Retry Stage Execution', + description: 'Retry the failed actions (or all actions) of a failed CodePipeline stage', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline', + }, + stageName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the failed stage to retry', + }, + pipelineExecutionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the pipeline execution in the failed stage', + }, + retryMode: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Scope of the retry: FAILED_ACTIONS or ALL_ACTIONS', + }, + }, + + request: { + url: '/api/tools/codepipeline/retry-stage-execution', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + stageName: params.stageName, + pipelineExecutionId: params.pipelineExecutionId, + retryMode: params.retryMode, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to retry CodePipeline stage execution') + } + + return { + success: true, + output: { + pipelineExecutionId: data.output.pipelineExecutionId, + }, + } + }, + + outputs: { + pipelineExecutionId: { + type: 'string', + description: 'ID of the pipeline execution with the retried stage', + }, + }, +} diff --git a/apps/sim/tools/codepipeline/start_execution.ts b/apps/sim/tools/codepipeline/start_execution.ts new file mode 100644 index 0000000000..0b31e0a31d --- /dev/null +++ b/apps/sim/tools/codepipeline/start_execution.ts @@ -0,0 +1,92 @@ +import type { + CodePipelineStartExecutionParams, + CodePipelineStartExecutionResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const startExecutionTool: ToolConfig< + CodePipelineStartExecutionParams, + CodePipelineStartExecutionResponse +> = { + id: 'codepipeline_start_execution', + name: 'CodePipeline Start Execution', + description: 'Start a CodePipeline pipeline execution, optionally overriding pipeline variables', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline to start', + }, + clientRequestToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Idempotency token to identify a unique execution request', + }, + variables: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Pipeline variable overrides as an array of { name, value } objects', + }, + }, + + request: { + url: '/api/tools/codepipeline/start-execution', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + ...(params.clientRequestToken && { clientRequestToken: params.clientRequestToken }), + ...(params.variables && params.variables.length > 0 && { variables: params.variables }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to start CodePipeline pipeline execution') + } + + return { + success: true, + output: { + pipelineExecutionId: data.output.pipelineExecutionId, + }, + } + }, + + outputs: { + pipelineExecutionId: { + type: 'string', + description: 'ID of the pipeline execution that was started', + }, + }, +} diff --git a/apps/sim/tools/codepipeline/stop_execution.ts b/apps/sim/tools/codepipeline/stop_execution.ts new file mode 100644 index 0000000000..b591e9c827 --- /dev/null +++ b/apps/sim/tools/codepipeline/stop_execution.ts @@ -0,0 +1,100 @@ +import type { + CodePipelineStopExecutionParams, + CodePipelineStopExecutionResponse, +} from '@/tools/codepipeline/types' +import type { ToolConfig } from '@/tools/types' + +export const stopExecutionTool: ToolConfig< + CodePipelineStopExecutionParams, + CodePipelineStopExecutionResponse +> = { + id: 'codepipeline_stop_execution', + name: 'CodePipeline Stop Execution', + description: + 'Stop a CodePipeline pipeline execution, either finishing in-progress actions or abandoning them', + version: '1.0.0', + + params: { + awsRegion: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + awsAccessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + awsSecretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + pipelineName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the pipeline', + }, + pipelineExecutionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the pipeline execution to stop', + }, + abandon: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Abandon in-progress actions instead of letting them finish (default false)', + }, + reason: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Reason for stopping the execution (max 200 characters)', + }, + }, + + request: { + url: '/api/tools/codepipeline/stop-execution', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => ({ + region: params.awsRegion, + accessKeyId: params.awsAccessKeyId, + secretAccessKey: params.awsSecretAccessKey, + pipelineName: params.pipelineName, + pipelineExecutionId: params.pipelineExecutionId, + ...(params.abandon !== undefined && { abandon: params.abandon }), + ...(params.reason && { reason: params.reason }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error || 'Failed to stop CodePipeline pipeline execution') + } + + return { + success: true, + output: { + pipelineExecutionId: data.output.pipelineExecutionId, + }, + } + }, + + outputs: { + pipelineExecutionId: { + type: 'string', + description: 'ID of the pipeline execution that was stopped', + }, + }, +} diff --git a/apps/sim/tools/codepipeline/types.ts b/apps/sim/tools/codepipeline/types.ts new file mode 100644 index 0000000000..ae185d3b56 --- /dev/null +++ b/apps/sim/tools/codepipeline/types.ts @@ -0,0 +1,182 @@ +import type { ToolResponse } from '@/tools/types' + +interface CodePipelineConnectionConfig { + awsRegion: string + awsAccessKeyId: string + awsSecretAccessKey: string +} + +export interface CodePipelineListPipelinesParams extends CodePipelineConnectionConfig { + maxResults?: number + nextToken?: string +} + +export interface CodePipelineListPipelinesResponse extends ToolResponse { + output: { + pipelines: { + name: string + version: number | undefined + pipelineType: string | undefined + executionMode: string | undefined + created: number | undefined + updated: number | undefined + }[] + nextToken?: string + } +} + +export interface CodePipelineGetPipelineStateParams extends CodePipelineConnectionConfig { + pipelineName: string +} + +export interface CodePipelineActionState { + actionName: string + status: string | undefined + summary: string | undefined + lastStatusChange: number | undefined + externalExecutionId: string | undefined + externalExecutionUrl: string | undefined + errorCode: string | undefined + errorMessage: string | undefined + percentComplete: number | undefined + token: string | undefined + revisionId: string | undefined + entityUrl: string | undefined +} + +export interface CodePipelineStageState { + stageName: string + status: string | undefined + pipelineExecutionId: string | undefined + inboundTransitionEnabled: boolean | undefined + actionStates: CodePipelineActionState[] +} + +export interface CodePipelineGetPipelineStateResponse extends ToolResponse { + output: { + pipelineName: string + pipelineVersion: number | undefined + created: number | undefined + updated: number | undefined + stageStates: CodePipelineStageState[] + } +} + +export interface CodePipelineGetPipelineExecutionParams extends CodePipelineConnectionConfig { + pipelineName: string + pipelineExecutionId: string +} + +export interface CodePipelineGetPipelineExecutionResponse extends ToolResponse { + output: { + pipelineExecutionId: string + pipelineName: string + pipelineVersion: number | undefined + status: string + statusSummary: string | undefined + executionMode: string | undefined + executionType: string | undefined + triggerType: string | undefined + triggerDetail: string | undefined + artifactRevisions: { + name: string + revisionId: string | undefined + revisionSummary: string | undefined + revisionUrl: string | undefined + created: number | undefined + }[] + variables: { + name: string + resolvedValue: string + }[] + } +} + +export interface CodePipelineListPipelineExecutionsParams extends CodePipelineConnectionConfig { + pipelineName: string + maxResults?: number + nextToken?: string + succeededInStage?: string +} + +export interface CodePipelineListPipelineExecutionsResponse extends ToolResponse { + output: { + executions: { + pipelineExecutionId: string + status: string + statusSummary: string | undefined + startTime: number | undefined + lastUpdateTime: number | undefined + executionMode: string | undefined + executionType: string | undefined + stopTriggerReason: string | undefined + triggerType: string | undefined + triggerDetail: string | undefined + sourceRevisions: { + actionName: string + revisionId: string | undefined + revisionSummary: string | undefined + revisionUrl: string | undefined + }[] + }[] + nextToken?: string + } +} + +export interface CodePipelineStartExecutionParams extends CodePipelineConnectionConfig { + pipelineName: string + clientRequestToken?: string + variables?: { name: string; value: string }[] +} + +export interface CodePipelineStartExecutionResponse extends ToolResponse { + output: { + pipelineExecutionId: string + } +} + +export interface CodePipelineStopExecutionParams extends CodePipelineConnectionConfig { + pipelineName: string + pipelineExecutionId: string + abandon?: boolean + reason?: string +} + +export interface CodePipelineStopExecutionResponse extends ToolResponse { + output: { + pipelineExecutionId: string + } +} + +export type CodePipelineRetryMode = 'FAILED_ACTIONS' | 'ALL_ACTIONS' + +export interface CodePipelineRetryStageExecutionParams extends CodePipelineConnectionConfig { + pipelineName: string + stageName: string + pipelineExecutionId: string + retryMode: CodePipelineRetryMode +} + +export interface CodePipelineRetryStageExecutionResponse extends ToolResponse { + output: { + pipelineExecutionId: string + } +} + +export type CodePipelineApprovalStatus = 'Approved' | 'Rejected' + +export interface CodePipelinePutApprovalResultParams extends CodePipelineConnectionConfig { + pipelineName: string + stageName: string + actionName: string + token: string + status: CodePipelineApprovalStatus + summary: string +} + +export interface CodePipelinePutApprovalResultResponse extends ToolResponse { + output: { + approvedAt: number | undefined + status: string + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 740202ee7f..829553329b 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -434,6 +434,16 @@ import { cloudwatchQueryLogsTool, cloudwatchUnmuteAlarmTool, } from '@/tools/cloudwatch' +import { + codepipelineGetPipelineExecutionTool, + codepipelineGetPipelineStateTool, + codepipelineListPipelineExecutionsTool, + codepipelineListPipelinesTool, + codepipelinePutApprovalResultTool, + codepipelineRetryStageExecutionTool, + codepipelineStartExecutionTool, + codepipelineStopExecutionTool, +} from '@/tools/codepipeline' import { confluenceAddLabelTool, confluenceCreateBlogPostTool, @@ -4294,6 +4304,14 @@ export const tools: Record = { cloudwatch_put_metric_data: cloudwatchPutMetricDataTool, cloudwatch_query_logs: cloudwatchQueryLogsTool, cloudwatch_unmute_alarm: cloudwatchUnmuteAlarmTool, + codepipeline_get_pipeline_execution: codepipelineGetPipelineExecutionTool, + codepipeline_get_pipeline_state: codepipelineGetPipelineStateTool, + codepipeline_list_pipeline_executions: codepipelineListPipelineExecutionsTool, + codepipeline_list_pipelines: codepipelineListPipelinesTool, + codepipeline_put_approval_result: codepipelinePutApprovalResultTool, + codepipeline_retry_stage_execution: codepipelineRetryStageExecutionTool, + codepipeline_start_execution: codepipelineStartExecutionTool, + codepipeline_stop_execution: codepipelineStopExecutionTool, crowdstrike_get_sensor_aggregates: crowdstrikeGetSensorAggregatesTool, crowdstrike_get_sensor_details: crowdstrikeGetSensorDetailsTool, crowdstrike_query_sensors: crowdstrikeQuerySensorsTool, diff --git a/bun.lock b/bun.lock index 7498ac72ab..339cacd6bf 100644 --- a/bun.lock +++ b/bun.lock @@ -97,6 +97,7 @@ "@aws-sdk/client-cloudformation": "3.1032.0", "@aws-sdk/client-cloudwatch": "3.1032.0", "@aws-sdk/client-cloudwatch-logs": "3.1032.0", + "@aws-sdk/client-codepipeline": "3.1032.0", "@aws-sdk/client-dynamodb": "3.1032.0", "@aws-sdk/client-iam": "3.1032.0", "@aws-sdk/client-identitystore": "3.1032.0", @@ -580,6 +581,8 @@ "@aws-sdk/client-cloudwatch-logs": ["@aws-sdk/client-cloudwatch-logs@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/eventstream-serde-browser": "^4.2.14", "@smithy/eventstream-serde-config-resolver": "^4.3.14", "@smithy/eventstream-serde-node": "^4.2.14", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-pzvsXRUrlq5q0HTmpEUF07koRw1cikeWY4M5brPQimMBZx5VahiIVyacNwD1tr40rKwo72SyFDToBWSnXFVYKA=="], + "@aws-sdk/client-codepipeline": ["@aws-sdk/client-codepipeline@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-VNoKgaIrzM+kQxJTDt+rXshkrj4pf9wgfwSi23DNE1cmaeDGJQJEoScD0vPqry8B8Qaj0omS7UzZyVCVv1ZOFw=="], + "@aws-sdk/client-dynamodb": ["@aws-sdk/client-dynamodb@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/dynamodb-codec": "^3.973.1", "@aws-sdk/middleware-endpoint-discovery": "^3.972.11", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.16", "tslib": "^2.6.2" } }, "sha512-kkXiZBNdWCQAg/8opqAu10TxzdpqMkcGrNAT2ScdfWhCpzYZ2pmSpP8W7BOlA32jYIWnYrEdb808UZsNWYBPAA=="], "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.16", "tslib": "^2.6.2" } }, "sha512-dzLygZx+PIUJ1Iob2l6a3ToqRtF1FQzF+Ps8lPeFaJSibslUt12hmBGUJ7uIVvoXhGzRRsRwtXTCH++XZpVYag=="], diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index b639bedd2b..90bad6c5a4 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 804, - zodRoutes: 804, + totalRoutes: 811, + zodRoutes: 811, nonZodRoutes: 0, } as const