diff --git a/.github/workflows/trigger-api-tasks-deploy-main.yml b/.github/workflows/trigger-api-tasks-deploy-main.yml index 57dd23ec2..6d780ed31 100644 --- a/.github/workflows/trigger-api-tasks-deploy-main.yml +++ b/.github/workflows/trigger-api-tasks-deploy-main.yml @@ -25,6 +25,12 @@ jobs: - name: Install DB package dependencies working-directory: ./packages/db run: bun install --frozen-lockfile --ignore-scripts + - name: Install Integration Platform package dependencies + working-directory: ./packages/integration-platform + run: bun install --frozen-lockfile --ignore-scripts + - name: Build Integration Platform package + working-directory: ./packages/integration-platform + run: bun run build - name: Build DB package working-directory: ./packages/db run: bun run build diff --git a/.github/workflows/trigger-api-tasks-deploy-release.yml b/.github/workflows/trigger-api-tasks-deploy-release.yml index 8ba396896..07861fc9e 100644 --- a/.github/workflows/trigger-api-tasks-deploy-release.yml +++ b/.github/workflows/trigger-api-tasks-deploy-release.yml @@ -28,6 +28,13 @@ jobs: working-directory: ./packages/db run: bun install --frozen-lockfile --ignore-scripts + - name: Install Integration Platform package dependencies + working-directory: ./packages/integration-platform + run: bun install --frozen-lockfile --ignore-scripts + - name: Build Integration Platform package + working-directory: ./packages/integration-platform + run: bun run build + - name: Build DB package working-directory: ./packages/db run: bun run build diff --git a/.github/workflows/trigger-tasks-deploy-main.yml b/.github/workflows/trigger-tasks-deploy-main.yml index 387b35c20..0dbd8b9db 100644 --- a/.github/workflows/trigger-tasks-deploy-main.yml +++ b/.github/workflows/trigger-tasks-deploy-main.yml @@ -24,9 +24,15 @@ jobs: - name: Install Email package dependencies working-directory: ./packages/email run: bun install --frozen-lockfile --ignore-scripts + - name: Install Integration Platform package dependencies + working-directory: ./packages/integration-platform + run: bun install --frozen-lockfile --ignore-scripts - name: Build DB package working-directory: ./packages/db run: bun run build + - name: Build Integration Platform package + working-directory: ./packages/integration-platform + run: bun run build - name: Copy schema to app and generate client working-directory: ./apps/app run: | diff --git a/.github/workflows/trigger-tasks-deploy-release.yml b/.github/workflows/trigger-tasks-deploy-release.yml index f941b1d60..c7c6d6c72 100644 --- a/.github/workflows/trigger-tasks-deploy-release.yml +++ b/.github/workflows/trigger-tasks-deploy-release.yml @@ -27,11 +27,18 @@ jobs: - name: Install Email package dependencies working-directory: ./packages/email run: bun install --frozen-lockfile --ignore-scripts + - name: Install Integration Platform package dependencies + working-directory: ./packages/integration-platform + run: bun install --frozen-lockfile --ignore-scripts - name: Build DB package working-directory: ./packages/db run: bun run build + - name: Build Integration Platform package + working-directory: ./packages/integration-platform + run: bun run build + - name: Copy schema to app and generate client working-directory: ./apps/app run: | diff --git a/apps/api/integrationPlatformExtension.ts b/apps/api/integrationPlatformExtension.ts new file mode 100644 index 000000000..3b8e96002 --- /dev/null +++ b/apps/api/integrationPlatformExtension.ts @@ -0,0 +1,129 @@ +import type { + BuildContext, + BuildExtension, + BuildManifest, +} from '@trigger.dev/build'; +import type { Plugin } from 'esbuild'; +import { existsSync } from 'node:fs'; +import { cp, mkdir } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; + +const PACKAGE_NAME = '@comp/integration-platform'; + +/** + * Custom Trigger.dev build extension for @comp/integration-platform workspace package. + * + * Since @comp/integration-platform is a workspace package (not published to npm), + * we need to: + * 1. Add an esbuild plugin to resolve the import path during build + * 2. Copy the built dist files into the trigger.dev deployment + */ +export function integrationPlatformExtension(): IntegrationPlatformExtension { + return new IntegrationPlatformExtension(); +} + +class IntegrationPlatformExtension implements BuildExtension { + public readonly name = 'IntegrationPlatformExtension'; + private _packagePath: string | undefined; + + async onBuildStart(context: BuildContext) { + if (context.target === 'dev') { + return; + } + + // Find the package path + this._packagePath = this.findPackageRoot(context.workingDir); + + if (!this._packagePath) { + throw new Error( + [ + `IntegrationPlatformExtension could not find ${PACKAGE_NAME}.`, + 'Make sure the package is built (run `bun run build` in packages/integration-platform).', + ].join('\n'), + ); + } + + context.logger.debug(`Found integration-platform at ${this._packagePath}`); + + // Register esbuild plugin to resolve the workspace package + const packagePath = this._packagePath; + const resolvePlugin: Plugin = { + name: 'resolve-integration-platform', + setup(build) { + // Resolve bare import + build.onResolve({ filter: /^@comp\/integration-platform$/ }, () => { + return { + path: resolve(packagePath, 'dist/index.js'), + }; + }); + + // Resolve subpath imports like @comp/integration-platform/types + build.onResolve( + { filter: /^@comp\/integration-platform\// }, + (args) => { + const subpath = args.path.replace(`${PACKAGE_NAME}/`, ''); + return { + path: resolve(packagePath, 'dist', `${subpath}/index.js`), + }; + }, + ); + }, + }; + + context.registerPlugin(resolvePlugin); + } + + async onBuildComplete(context: BuildContext, manifest: BuildManifest) { + if (context.target === 'dev') { + return; + } + + const packagePath = this._packagePath; + if (!packagePath) { + return; + } + + const packageDistPath = resolve(packagePath, 'dist'); + + // Copy the entire dist to the build output + const destPath = resolve( + manifest.outputPath, + 'node_modules/@comp/integration-platform', + ); + const destDistPath = resolve(destPath, 'dist'); + + await mkdir(destDistPath, { recursive: true }); + + // Copy dist files + await cp(packageDistPath, destDistPath, { recursive: true }); + + // Copy package.json for proper module resolution + const packageJsonPath = resolve(packagePath, 'package.json'); + if (existsSync(packageJsonPath)) { + await cp(packageJsonPath, resolve(destPath, 'package.json')); + } + + context.logger.log( + 'Copied @comp/integration-platform to deployment bundle', + ); + } + + private findPackageRoot(workingDir: string): string | undefined { + // Look for the package relative to the api app + const candidates = [ + resolve(workingDir, '../../packages/integration-platform'), + resolve(workingDir, '../packages/integration-platform'), + ]; + + for (const candidate of candidates) { + if ( + existsSync(candidate) && + existsSync(resolve(candidate, 'dist/index.js')) + ) { + return candidate; + } + } + + return undefined; + } +} diff --git a/apps/api/trigger.config.ts b/apps/api/trigger.config.ts index 39dd5e306..7434b3d28 100644 --- a/apps/api/trigger.config.ts +++ b/apps/api/trigger.config.ts @@ -2,6 +2,7 @@ import { PrismaInstrumentation } from '@prisma/instrumentation'; import { syncVercelEnvVars } from '@trigger.dev/build/extensions/core'; import { defineConfig } from '@trigger.dev/sdk'; import { prismaExtension } from './customPrismaExtension'; +import { integrationPlatformExtension } from './integrationPlatformExtension'; export default defineConfig({ project: 'proj_zhioyrusqertqgafqgpj', // API project @@ -14,6 +15,7 @@ export default defineConfig({ version: '6.13.0', dbPackageVersion: '^1.3.15', // Version of @trycompai/db package with compiled JS }), + integrationPlatformExtension(), syncVercelEnvVars(), ], }, diff --git a/apps/app/src/app/(app)/[orgId]/cloud-tests/components/TestsLayout.tsx b/apps/app/src/app/(app)/[orgId]/cloud-tests/components/TestsLayout.tsx index 8e3f7d579..3e8ecbc7b 100644 --- a/apps/app/src/app/(app)/[orgId]/cloud-tests/components/TestsLayout.tsx +++ b/apps/app/src/app/(app)/[orgId]/cloud-tests/components/TestsLayout.tsx @@ -75,7 +75,7 @@ export function TestsLayout({ initialFindings, initialProviders, orgId }: TestsL const { disconnectConnection } = useIntegrationMutations(); const { data: findings = initialFindings, mutate: mutateFindings } = useSWR( - '/api/cloud-tests/findings', + `/api/cloud-tests/findings?orgId=${orgId}`, async (url) => { const res = await fetch(url); if (!res.ok) throw new Error('Failed to fetch'); @@ -89,7 +89,7 @@ export function TestsLayout({ initialFindings, initialProviders, orgId }: TestsL ); const { data: providers = initialProviders, mutate: mutateProviders } = useSWR( - '/api/cloud-tests/providers', + `/api/cloud-tests/providers?orgId=${orgId}`, async (url) => { const res = await fetch(url); if (!res.ok) throw new Error('Failed to fetch'); diff --git a/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/SingleTask.tsx b/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/SingleTask.tsx index 106ee0b1f..5a696d5a9 100644 --- a/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/SingleTask.tsx +++ b/apps/app/src/app/(app)/[orgId]/tasks/[taskId]/components/SingleTask.tsx @@ -205,8 +205,10 @@ export function SingleTask({ {/* Browser Automations Section */} {isWebAutomationsEnabled && } - {/* Custom Automations Section - only show if no mapped integration checks available */} - {!hasMappedChecks && } + {/* Custom Automations Section - always show if automations exist, or show empty state if no integration checks */} + {((automations && automations.length > 0) || !hasMappedChecks) && ( + + )} {/* Comments Section */}
diff --git a/apps/app/src/app/api/cloud-tests/findings/route.ts b/apps/app/src/app/api/cloud-tests/findings/route.ts index 8bdbfe918..b0f3ebe2b 100644 --- a/apps/app/src/app/api/cloud-tests/findings/route.ts +++ b/apps/app/src/app/api/cloud-tests/findings/route.ts @@ -1,20 +1,38 @@ import { auth } from '@/utils/auth'; import { db } from '@db'; import { headers } from 'next/headers'; -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; const CLOUD_PROVIDER_SLUGS = ['aws', 'gcp', 'azure']; -export async function GET() { +export async function GET(request: NextRequest) { try { const session = await auth.api.getSession({ headers: await headers(), }); - const orgId = session?.session.activeOrganizationId; + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const orgId = searchParams.get('orgId'); if (!orgId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 }); + } + + // Verify the user belongs to the requested organization + const member = await db.member.findFirst({ + where: { + userId: session.user.id, + organizationId: orgId, + deactivated: false, + }, + }); + + if (!member) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } // ==================================================================== diff --git a/apps/app/src/app/api/cloud-tests/providers/route.ts b/apps/app/src/app/api/cloud-tests/providers/route.ts index d486f3438..b958c3ad3 100644 --- a/apps/app/src/app/api/cloud-tests/providers/route.ts +++ b/apps/app/src/app/api/cloud-tests/providers/route.ts @@ -2,7 +2,7 @@ import { auth } from '@/utils/auth'; import { getManifest } from '@comp/integration-platform'; import { db } from '@db'; import { headers } from 'next/headers'; -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; const CLOUD_PROVIDER_SLUGS = ['aws', 'gcp', 'azure']; @@ -24,16 +24,34 @@ const getRequiredVariables = (providerSlug: string): string[] => { return Array.from(requiredVars); }; -export async function GET() { +export async function GET(request: NextRequest) { try { const session = await auth.api.getSession({ headers: await headers(), }); - const orgId = session?.session.activeOrganizationId; + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const orgId = searchParams.get('orgId'); if (!orgId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + return NextResponse.json({ error: 'Organization ID is required' }, { status: 400 }); + } + + // Verify the user belongs to the requested organization + const member = await db.member.findFirst({ + where: { + userId: session.user.id, + organizationId: orgId, + deactivated: false, + }, + }); + + if (!member) { + return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); } // Fetch from NEW integration platform (IntegrationConnection)