Skip to content

Commit 7b3ec13

Browse files
committed
chore: merge main into release for new releases
2 parents bd8bd53 + 03da2ee commit 7b3ec13

File tree

20 files changed

+567
-135
lines changed

20 files changed

+567
-135
lines changed

.github/workflows/trigger-tasks-deploy-main.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,15 @@ jobs:
2424
- name: Install Email package dependencies
2525
working-directory: ./packages/email
2626
run: bun install --frozen-lockfile --ignore-scripts
27-
- name: Generate Prisma client
27+
- name: Build DB package
2828
working-directory: ./packages/db
29-
run: bunx prisma generate
29+
run: bun run build
30+
- name: Copy schema to app and generate client
31+
working-directory: ./apps/app
32+
run: |
33+
mkdir -p prisma
34+
cp ../../packages/db/dist/schema.prisma prisma/schema.prisma
35+
bunx prisma generate
3036
- name: 🚀 Deploy Trigger.dev
3137
working-directory: ./apps/app
3238
timeout-minutes: 20

apps/api/buildspec.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ phases:
2727
- '[ -n "$BASE_URL" ] || { echo "❌ BASE_URL is not set"; exit 1; }'
2828
- '[ -n "$BETTER_AUTH_URL" ] || { echo "❌ BETTER_AUTH_URL is not set"; exit 1; }'
2929
- '[ -n "$TRUST_APP_URL" ] || { echo "❌ TRUST_APP_URL is not set"; exit 1; }'
30+
- '[ -n "$APP_AWS_BUCKET_NAME" ] || { echo "❌ APP_AWS_BUCKET_NAME is not set"; exit 1; }'
31+
- '[ -n "$APP_AWS_ACCESS_KEY_ID" ] || { echo "❌ APP_AWS_ACCESS_KEY_ID is not set"; exit 1; }'
32+
- '[ -n "$APP_AWS_SECRET_ACCESS_KEY" ] || { echo "❌ APP_AWS_SECRET_ACCESS_KEY is not set"; exit 1; }'
3033

3134
# Install only API workspace dependencies
3235
- echo "Installing API dependencies only..."

apps/api/src/attachments/attachments.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ export class AttachmentsService {
2626
// AWS configuration is validated at startup via ConfigModule
2727
// Safe to access environment variables directly since they're validated
2828
this.bucketName = process.env.APP_AWS_BUCKET_NAME!;
29+
30+
if (!process.env.APP_AWS_ACCESS_KEY_ID || !process.env.APP_AWS_SECRET_ACCESS_KEY) {
31+
console.warn('AWS credentials are missing, S3 client may fail');
32+
}
33+
2934
this.s3Client = new S3Client({
3035
region: process.env.APP_AWS_REGION || 'us-east-1',
3136
credentials: {

apps/app/customPrismaExtension.ts

Lines changed: 152 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { binaryForRuntime, BuildContext, BuildExtension, BuildManifest } from '@trigger.dev/build';
22
import assert from 'node:assert';
3+
import { spawn } from 'node:child_process';
34
import { existsSync } from 'node:fs';
4-
import { cp } from 'node:fs/promises';
5+
import { cp, mkdir } from 'node:fs/promises';
56
import { dirname, join, resolve } from 'node:path';
67

78
export type PrismaExtensionOptions = {
@@ -14,6 +15,12 @@ export type PrismaExtensionOptions = {
1415
dbPackageVersion?: string;
1516
};
1617

18+
type ExtendedBuildContext = BuildContext & { workspaceDir?: string };
19+
type SchemaResolution = {
20+
path?: string;
21+
searched: string[];
22+
};
23+
1724
export function prismaExtension(options: PrismaExtensionOptions = {}): PrismaExtension {
1825
return new PrismaExtension(options);
1926
}
@@ -43,51 +50,47 @@ export class PrismaExtension implements BuildExtension {
4350
return;
4451
}
4552

46-
// Resolve the path to the schema from the published @trycompai/db package
47-
// In a monorepo, node_modules are typically hoisted to the workspace root
48-
// Walk up the directory tree to find the workspace root (where node_modules/@trycompai/db exists)
49-
let workspaceRoot = context.workingDir;
50-
let dbPackagePath = resolve(workspaceRoot, 'node_modules/@trycompai/db/dist/schema.prisma');
51-
52-
// If not found in working dir, try parent directories
53-
while (!existsSync(dbPackagePath) && workspaceRoot !== dirname(workspaceRoot)) {
54-
workspaceRoot = dirname(workspaceRoot);
55-
dbPackagePath = resolve(workspaceRoot, 'node_modules/@trycompai/db/dist/schema.prisma');
56-
}
57-
58-
this._resolvedSchemaPath = dbPackagePath;
59-
60-
context.logger.debug(`Workspace root: ${workspaceRoot}`);
61-
context.logger.debug(
62-
`Resolved the prisma schema from @trycompai/db package to: ${this._resolvedSchemaPath}`,
63-
);
53+
const resolution = this.tryResolveSchemaPath(context as ExtendedBuildContext);
6454

65-
// Debug: List contents of the @trycompai/db package directory
66-
const dbPackageDir = resolve(workspaceRoot, 'node_modules/@trycompai/db');
67-
const dbDistDir = resolve(workspaceRoot, 'node_modules/@trycompai/db/dist');
68-
69-
try {
70-
const { readdirSync } = require('node:fs');
71-
context.logger.debug(`@trycompai/db package directory contents:`, readdirSync(dbPackageDir));
72-
context.logger.debug(`@trycompai/db/dist directory contents:`, readdirSync(dbDistDir));
73-
} catch (err) {
74-
context.logger.debug(`Failed to list directory contents:`, err);
75-
}
76-
77-
// Check that the prisma schema exists in the published package
78-
if (!existsSync(this._resolvedSchemaPath)) {
79-
throw new Error(
80-
`PrismaExtension could not find the prisma schema at ${this._resolvedSchemaPath}. Make sure @trycompai/db package is installed with version ${this.options.dbPackageVersion || 'latest'}`,
55+
if (!resolution.path) {
56+
context.logger.debug(
57+
'Prisma schema not found during build start, likely before dependencies are installed.',
58+
{ searched: resolution.searched },
8159
);
60+
return;
8261
}
62+
63+
this._resolvedSchemaPath = resolution.path;
64+
context.logger.debug(`Resolved prisma schema to ${resolution.path}`);
65+
await this.ensureLocalPrismaClient(context as ExtendedBuildContext, resolution.path);
8366
}
8467

8568
async onBuildComplete(context: BuildContext, manifest: BuildManifest) {
8669
if (context.target === 'dev') {
8770
return;
8871
}
8972

73+
if (!this._resolvedSchemaPath || !existsSync(this._resolvedSchemaPath)) {
74+
const resolution = this.tryResolveSchemaPath(context as ExtendedBuildContext);
75+
76+
if (!resolution.path) {
77+
throw new Error(
78+
[
79+
'PrismaExtension could not find the prisma schema. Make sure @trycompai/db is installed',
80+
`with version ${this.options.dbPackageVersion || 'latest'} and that its dist files are built.`,
81+
'Searched the following locations:',
82+
...resolution.searched.map((candidate) => ` - ${candidate}`),
83+
].join('\n'),
84+
);
85+
}
86+
87+
this._resolvedSchemaPath = resolution.path;
88+
}
89+
9090
assert(this._resolvedSchemaPath, 'Resolved schema path is not set');
91+
const schemaPath = this._resolvedSchemaPath;
92+
93+
await this.ensureLocalPrismaClient(context as ExtendedBuildContext, schemaPath);
9194

9295
context.logger.debug('Looking for @prisma/client in the externals', {
9396
externals: manifest.externals,
@@ -114,9 +117,9 @@ export class PrismaExtension implements BuildExtension {
114117
// Copy the prisma schema from the published package to the build output path
115118
const schemaDestinationPath = join(manifest.outputPath, 'prisma', 'schema.prisma');
116119
context.logger.debug(
117-
`Copying the prisma schema from ${this._resolvedSchemaPath} to ${schemaDestinationPath}`,
120+
`Copying the prisma schema from ${schemaPath} to ${schemaDestinationPath}`,
118121
);
119-
await cp(this._resolvedSchemaPath, schemaDestinationPath);
122+
await cp(schemaPath, schemaDestinationPath);
120123

121124
// Add prisma generate command to generate the client from the copied schema
122125
commands.push(
@@ -176,4 +179,116 @@ export class PrismaExtension implements BuildExtension {
176179
},
177180
});
178181
}
182+
183+
private async ensureLocalPrismaClient(
184+
context: ExtendedBuildContext,
185+
schemaSourcePath: string,
186+
): Promise<void> {
187+
const schemaDir = resolve(context.workingDir, 'prisma');
188+
const schemaDestinationPath = resolve(schemaDir, 'schema.prisma');
189+
190+
await mkdir(schemaDir, { recursive: true });
191+
await cp(schemaSourcePath, schemaDestinationPath);
192+
193+
const clientEntryPoint = resolve(context.workingDir, 'node_modules/.prisma/client/default.js');
194+
195+
if (existsSync(clientEntryPoint) && !process.env.TRIGGER_PRISMA_FORCE_GENERATE) {
196+
context.logger.debug('Prisma client already generated locally, skipping regenerate.');
197+
return;
198+
}
199+
200+
const prismaBinary = this.resolvePrismaBinary(context.workingDir);
201+
202+
if (!prismaBinary) {
203+
context.logger.debug(
204+
'Prisma CLI not available yet, skipping local generate until install finishes.',
205+
);
206+
return;
207+
}
208+
209+
context.logger.log('Prisma client missing. Generating before Trigger indexing.');
210+
await this.runPrismaGenerate(context, prismaBinary, schemaDestinationPath);
211+
}
212+
213+
private runPrismaGenerate(
214+
context: ExtendedBuildContext,
215+
prismaBinary: string,
216+
schemaPath: string,
217+
): Promise<void> {
218+
return new Promise((resolvePromise, rejectPromise) => {
219+
const child = spawn(prismaBinary, ['generate', `--schema=${schemaPath}`], {
220+
cwd: context.workingDir,
221+
env: {
222+
...process.env,
223+
PRISMA_HIDE_UPDATE_MESSAGE: '1',
224+
},
225+
});
226+
227+
child.stdout?.on('data', (data: Buffer) => {
228+
context.logger.debug(data.toString().trim());
229+
});
230+
231+
child.stderr?.on('data', (data: Buffer) => {
232+
context.logger.warn(data.toString().trim());
233+
});
234+
235+
child.on('error', (error) => {
236+
rejectPromise(error);
237+
});
238+
239+
child.on('close', (code) => {
240+
if (code === 0) {
241+
resolvePromise();
242+
} else {
243+
rejectPromise(new Error(`prisma generate exited with code ${code}`));
244+
}
245+
});
246+
});
247+
}
248+
249+
private resolvePrismaBinary(workingDir: string): string | undefined {
250+
const binDir = resolve(workingDir, 'node_modules', '.bin');
251+
const executable = process.platform === 'win32' ? 'prisma.cmd' : 'prisma';
252+
const binaryPath = resolve(binDir, executable);
253+
254+
if (!existsSync(binaryPath)) {
255+
return undefined;
256+
}
257+
258+
return binaryPath;
259+
}
260+
261+
private tryResolveSchemaPath(context: ExtendedBuildContext): SchemaResolution {
262+
const candidates = this.buildSchemaCandidates(context);
263+
const path = candidates.find((candidate) => existsSync(candidate));
264+
return { path, searched: candidates };
265+
}
266+
267+
private buildSchemaCandidates(context: ExtendedBuildContext): string[] {
268+
const candidates = new Set<string>();
269+
270+
const addNodeModuleCandidates = (start: string | undefined) => {
271+
if (!start) {
272+
return;
273+
}
274+
275+
let current = start;
276+
while (true) {
277+
candidates.add(resolve(current, 'node_modules/@trycompai/db/dist/schema.prisma'));
278+
const parent = dirname(current);
279+
if (parent === current) {
280+
break;
281+
}
282+
current = parent;
283+
}
284+
};
285+
286+
addNodeModuleCandidates(context.workingDir);
287+
addNodeModuleCandidates(context.workspaceDir);
288+
289+
candidates.add(resolve(context.workingDir, '../../packages/db/dist/schema.prisma'));
290+
candidates.add(resolve(context.workingDir, '../packages/db/dist/schema.prisma'));
291+
292+
return Array.from(candidates);
293+
}
179294
}

apps/app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"playwright-core": "^1.52.0",
8585
"posthog-js": "^1.236.6",
8686
"posthog-node": "^5.8.2",
87+
"prisma": "^6.13.0",
8788
"puppeteer-core": "^24.7.2",
8889
"react": "^19.1.1",
8990
"react-dom": "^19.1.0",
@@ -133,7 +134,6 @@
133134
"glob": "^11.0.3",
134135
"jsdom": "^26.1.0",
135136
"postcss": "^8.5.4",
136-
"prisma": "^6.13.0",
137137
"raw-loader": "^4.0.2",
138138
"tailwindcss": "^4.1.8",
139139
"typescript": "^5.8.3",
@@ -164,6 +164,7 @@
164164
"dev": "bun i && bunx concurrently --kill-others --names \"next,trigger\" --prefix-colors \"yellow,blue\" \"next dev --turbo -p 3000\" \"bunx [email protected] dev\"",
165165
"lint": "next lint && prettier --check .",
166166
"prebuild": "bun run db:generate",
167+
"postinstall": "prisma generate --schema=./prisma/schema.prisma || exit 0",
167168
"start": "next start",
168169
"test": "vitest",
169170
"test:all": "./scripts/test-all.sh",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

apps/portal/src/app/(app)/(home)/[orgId]/components/tasks/DeviceAgentAccordionItem.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'use client';
22

3+
import {
4+
MAC_APPLE_SILICON_FILENAME,
5+
MAC_INTEL_FILENAME,
6+
WINDOWS_FILENAME,
7+
} from '@/app/api/download-agent/constants';
38
import { detectOSFromUserAgent, SupportedOS } from '@/utils/os';
49
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@comp/ui/accordion';
510
import { Button } from '@comp/ui/button';
@@ -51,6 +56,11 @@ export function DeviceAgentAccordionItem({
5156
const isCompleted = hasInstalledAgent && failedPoliciesCount === 0;
5257

5358
const handleDownload = async () => {
59+
if (!detectedOS) {
60+
toast.error('Could not detect your OS. Please refresh and try again.');
61+
return;
62+
}
63+
5464
setIsDownloading(true);
5565

5666
try {
@@ -61,6 +71,7 @@ export function DeviceAgentAccordionItem({
6171
body: JSON.stringify({
6272
orgId: member.organizationId,
6373
employeeId: member.id,
74+
os: detectedOS,
6475
}),
6576
});
6677

@@ -73,20 +84,17 @@ export function DeviceAgentAccordionItem({
7384

7485
// Now trigger the actual download using the browser's native download mechanism
7586
// This will show in the browser's download UI immediately
76-
const downloadUrl = `/api/download-agent?token=${encodeURIComponent(token)}&os=${detectedOS}`;
87+
const downloadUrl = `/api/download-agent?token=${encodeURIComponent(token)}`;
7788

7889
// Method 1: Using a temporary link (most reliable)
7990
const a = document.createElement('a');
8091
a.href = downloadUrl;
8192

8293
// Set filename based on OS and architecture
8394
if (isMacOS) {
84-
a.download =
85-
detectedOS === 'macos'
86-
? 'Comp AI Agent-1.0.0-arm64.dmg'
87-
: 'Comp AI Agent-1.0.0-intel.dmg';
95+
a.download = detectedOS === 'macos' ? MAC_APPLE_SILICON_FILENAME : MAC_INTEL_FILENAME;
8896
} else {
89-
a.download = 'Comp AI Agent 1.0.0.exe';
97+
a.download = WINDOWS_FILENAME;
9098
}
9199

92100
document.body.appendChild(a);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const MAC_APPLE_SILICON_FILENAME = 'Comp AI Agent-1.0.0-arm64.dmg';
2+
export const MAC_INTEL_FILENAME = 'Comp AI Agent-1.0.0.dmg';
3+
export const WINDOWS_FILENAME = 'Comp AI Agent 1.0.0.exe';

0 commit comments

Comments
 (0)