Skip to content

Conversation

myftija
Copy link
Member

@myftija myftija commented Sep 15, 2025

This PR enables setting project build settings in the settings page:
root directory, install command and trigger config file path.

For most cases there should be no need to set these explicitly.

image

Copy link

changeset-bot bot commented Sep 15, 2025

⚠️ No Changeset found

Latest commit: 9d9ce24

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

Walkthrough

Adds Build Settings end-to-end. Database: migration adding a nullable JSONB column buildSettings and Prisma model field buildSettings Json?. Domain: new BuildSettingsSchema and exported BuildSettings type. Server: ProjectSettingsService gains updateBuildSettings; presenter parses and includes buildSettings in responses across code paths. Route: loader returns buildSettings; action gains update-build-settings with validation and persistence. UI: route file adds an internal BuildSettingsForm component rendering and submitting triggerConfigFilePath, installCommand, and installDirectory.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description states the high-level intent and includes a screenshot, but it does not follow the repository's required template: it is missing the "Closes #" line, the ✅ checklist, the Testing section with steps taken, and a Changelog entry, so the description is incomplete relative to the template. Please update the PR description to the repository template by adding "Closes #", completing the checklist, and including a Testing section that lists the exact steps and results you used to verify the change; also add a short Changelog entry and place the screenshot under a Screenshots section so reviewers can reproduce and validate the work.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: expose project build settings" is concise, uses the conventional "feat:" prefix, and accurately summarizes the primary change—adding UI and backend support to expose and persist project build settings (trigger config file path, install command, install directory), so a teammate scanning history can quickly understand the intent.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch project-build-settings

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
internal-packages/database/prisma/schema.prisma (1)

398-398: Add a brief field doc and clarify null vs empty-object semantics.

To avoid ambiguity downstream, document that null means “unset/defaults” and {} means “user explicitly set, but no overrides.” This also helps future migrations and UI behavior.

   connectedGithubRepository ConnectedGithubRepository?
-  buildSettings Json?
+  /// Build settings for the project. Use NULL to indicate "unset/defaults".
+  buildSettings Json?
apps/webapp/app/v3/buildSettings.ts (1)

3-7: Normalize inputs: trim, collapse empty strings to undefined, and cap length.

Prevents storing whitespace-only values and overly long commands/paths. Keeps the UI forgiving (empty input → unset).

-export const BuildSettingsSchema = z.object({
-  rootDirectory: z.string().optional(),
-  installCommand: z.string().optional(),
-  triggerConfigFile: z.string().optional(),
-});
+const optionalSanitizedString = z.preprocess(
+  (v) => {
+    if (typeof v !== "string") return v;
+    const t = v.trim();
+    return t === "" ? undefined : t;
+  },
+  z.string().max(512).optional()
+);
+
+export const BuildSettingsSchema = z
+  .object({
+    rootDirectory: optionalSanitizedString,
+    installCommand: optionalSanitizedString,
+    triggerConfigFile: optionalSanitizedString,
+  })
+  .strip();
apps/webapp/app/services/projectSettings.server.ts (1)

1-1: Import Prisma to explicitly set DB NULL for “unset” build settings.

We’ll use Prisma.DbNull to store NULL (vs JSON null) when all fields are empty after normalization.

-import { type PrismaClient } from "@trigger.dev/database";
+import { Prisma, type PrismaClient } from "@trigger.dev/database";
apps/webapp/app/services/projectSettingsPresenter.server.ts (2)

30-49: Remove redundant null check; getProject() already guarantees a project or an error.

Simplifies flow; no behavior change.

-      return getProject().andThen((project) => {
-        if (!project) {
-          return err({ type: "project_not_found" as const });
-        }
-
-        const buildSettingsOrFailure = BuildSettingsSchema.safeParse(project.buildSettings);
+      return getProject().andThen((project) => {
+        const buildSettingsOrFailure = BuildSettingsSchema.safeParse(project.buildSettings);
         const buildSettings = buildSettingsOrFailure.success
           ? buildSettingsOrFailure.data
           : undefined;

36-49: DRY the BuildSettings parsing.

Extract a tiny helper to avoid repeating safeParse and ensure consistent behavior.

-        const buildSettingsOrFailure = BuildSettingsSchema.safeParse(project.buildSettings);
-        const buildSettings = buildSettingsOrFailure.success
-          ? buildSettingsOrFailure.data
-          : undefined;
+        const buildSettings = getBuildSettings(project.buildSettings);

Add this helper near the top of the file:

function getBuildSettings(raw: unknown) {
  const r = BuildSettingsSchema.safeParse(raw);
  return r.success ? r.data : undefined;
}

Also applies to: 135-139, 159-161

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (3)

65-65: Avoid default React import; prefer fragment shorthand.

No need to import the default React object when using the automatic JSX runtime. Import Fragment or use <>…</> instead.

Apply:

-import React, { useEffect, useState } from "react";
+import { useEffect, useState, Fragment } from "react";

And replace React.Fragment usages accordingly:

-              <React.Fragment>
+              <Fragment>
...
-              </React.Fragment>
+              </Fragment>

388-412: Move normalization into the schema; stop coalescing in the action.

Once the schema coerces blanks to undefined, this extra “|| undefined” is unnecessary and can mask intent.

Apply:

-      const resultOrFail = await projectSettingsService.updateBuildSettings(projectId, {
-        rootDirectory: rootDirectory || undefined,
-        installCommand: installCommand || undefined,
-        triggerConfigFile: triggerConfigFile || undefined,
-      });
+      const resultOrFail = await projectSettingsService.updateBuildSettings(projectId, {
+        rootDirectory,
+        installCommand,
+        triggerConfigFile,
+      });

1087-1196: Tighten UX copy and placeholders for clarity; avoid suggesting “/” root.

“/” reads as an absolute path; these should be repo-relative. Also simplify placeholders.

Apply:

-          <Input
+          <Input
             {...conform.input(fields.rootDirectory, { type: "text" })}
             defaultValue={buildSettings?.rootDirectory || ""}
-            placeholder="/"
+            placeholder="e.g., packages/app"
             onChange={(e) => {
               setBuildSettingsValues((prev) => ({
                 ...prev,
                 rootDirectory: e.target.value,
               }));
             }}
           />
-          <Hint>The directory that contains your code.</Hint>
+          <Hint>Relative to your repository root. Leave blank to use the repo root.</Hint>
-          <Input
+          <Input
             {...conform.input(fields.installCommand, { type: "text" })}
             defaultValue={buildSettings?.installCommand || ""}
-            placeholder="e.g., `npm install`, or `bun install`"
+            placeholder="e.g., npm install or bun install"
             onChange={(e) => {
               setBuildSettingsValues((prev) => ({
                 ...prev,
                 installCommand: e.target.value,
               }));
             }}
           />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc42ae7 and d86288f.

📒 Files selected for processing (6)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (9 hunks)
  • apps/webapp/app/services/projectSettings.server.ts (2 hunks)
  • apps/webapp/app/services/projectSettingsPresenter.server.ts (5 hunks)
  • apps/webapp/app/v3/buildSettings.ts (1 hunks)
  • internal-packages/database/prisma/migrations/20250915141201_add_build_settings_to_project/migration.sql (1 hunks)
  • internal-packages/database/prisma/schema.prisma (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/services/projectSettings.server.ts
  • apps/webapp/app/services/projectSettingsPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/services/projectSettings.server.ts
  • apps/webapp/app/services/projectSettingsPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

When importing from @trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from @trigger.dev/core’s package.json

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/services/projectSettings.server.ts
  • apps/webapp/app/services/projectSettingsPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
apps/webapp/app/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Modules intended for test consumption under apps/webapp/app/**/*.ts must not read environment variables; accept configuration via options instead

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/services/projectSettings.server.ts
  • apps/webapp/app/services/projectSettingsPresenter.server.ts
{apps/webapp/app/**/*.server.{ts,tsx},apps/webapp/app/routes/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Access environment variables only via the env export from app/env.server.ts; do not reference process.env directly

Files:

  • apps/webapp/app/services/projectSettings.server.ts
  • apps/webapp/app/services/projectSettingsPresenter.server.ts
🧠 Learnings (3)
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Declare build options and extensions (external, jsx, conditions, extensions) via the build block in trigger.config.ts rather than custom scripts

Applied to files:

  • apps/webapp/app/v3/buildSettings.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : We use zod a lot in packages/core and in the webapp

Applied to files:

  • apps/webapp/app/v3/buildSettings.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
🧬 Code graph analysis (3)
apps/webapp/app/services/projectSettings.server.ts (1)
apps/webapp/app/v3/buildSettings.ts (1)
  • BuildSettings (9-9)
apps/webapp/app/services/projectSettingsPresenter.server.ts (1)
apps/webapp/app/v3/buildSettings.ts (1)
  • BuildSettingsSchema (3-7)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (2)
apps/webapp/app/models/message.server.ts (2)
  • redirectBackWithErrorMessage (200-207)
  • redirectBackWithSuccessMessage (209-216)
apps/webapp/app/v3/buildSettings.ts (1)
  • BuildSettings (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
internal-packages/database/prisma/migrations/20250915141201_add_build_settings_to_project/migration.sql (1)

1-1: LGTM: additive, nullable JSONB column.

No data backfill required; safe forward migration.

apps/webapp/app/services/projectSettings.server.ts (2)

248-263: No buildSettings values are being interpolated into shell commands — advisory only

Searches for buildSettings/installCommand/rootDirectory/triggerConfigFile (including template interpolation/concatenation) and for child_process/execa usage found no instances where buildSettings values are passed into exec/spawn/execa. Relevant locations: apps/webapp/app/services/projectSettings.server.ts (updateBuildSettings), apps/webapp/app/v3/buildSettings.ts (schema), apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (form + action). Child_process/execa usages exist (e.g., packages/docker-provider/src/index.ts, scripts/*) but they do not reference buildSettings. If these fields are ever used to run shell commands, pass values as argument arrays (execFile/spawn/execa with args) or strictly whitelist/validate/escape them.


248-263: Normalize and store DB NULL for empty buildSettings.

Trim string fields and drop empty strings; if the normalized object is empty, persist Prisma.DbNull instead of {}. The generated Prisma client was not found at internal-packages/database/src/generated/prisma — verify that Prisma.DbNull is exported by @trigger.dev/database (or import Prisma from @prisma/client) before applying.

-  updateBuildSettings(projectId: string, buildSettings: BuildSettings) {
-    return fromPromise(
-      this.#prismaClient.project.update({
-        where: {
-          id: projectId,
-        },
-        data: {
-          buildSettings: buildSettings,
-        },
-      }),
-      (error) => ({
-        type: "other" as const,
-        cause: error,
-      })
-    );
-  }
+  updateBuildSettings(projectId: string, buildSettings: BuildSettings) {
+    const normalized = Object.fromEntries(
+      Object.entries(buildSettings)
+        .filter(([_, v]) => typeof v === "string")
+        .map(([k, v]) => [k, (v as string).trim()])
+        .filter(([_, v]) => (v as string).length > 0)
+    ) as BuildSettings;
+
+    const data =
+      Object.keys(normalized).length === 0
+        ? { buildSettings: Prisma.DbNull }
+        : { buildSettings: normalized };
+
+    return fromPromise(
+      this.#prismaClient.project.update({
+        where: { id: projectId },
+        data,
+      }),
+      (error) => ({ type: "other" as const, cause: error })
+    );
+  }
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (3)

123-138: Good: loader surfaces buildSettings with other project metadata.

This keeps the page render simple and consistent with existing loader patterns.


199-200: LGTM: discriminated union extended with update-build-settings.

Keeps action parsing consistent with existing patterns.


426-427: LGTM: loader data consumption typed via useTypedLoaderData.

Props stay aligned with loader return type.

@myftija myftija force-pushed the project-build-settings branch from 9d3adb5 to 9d9ce24 Compare September 16, 2025 12:52
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (3)

80-80: Good: reusing the shared BuildSettings type.

This avoids local type drift. Matches prior feedback.


574-600: Don’t gate Build settings behind GitHub enablement.

Users should edit build settings without connecting GitHub. Render Build settings unconditionally; keep Git settings conditional.

Apply:

-            {githubAppEnabled && (
-              <React.Fragment>
-                <div>
-                  <Header2 spacing>Git settings</Header2>
-                  <div className="w-full rounded-sm border border-grid-dimmed p-4">
-                    {connectedGithubRepository ? (
-                      <ConnectedGitHubRepoForm connectedGitHubRepo={connectedGithubRepository} />
-                    ) : (
-                      <GitHubConnectionPrompt
-                        gitHubAppInstallations={githubAppInstallations ?? []}
-                        organizationSlug={organization.slug}
-                        projectSlug={project.slug}
-                        environmentSlug={environment.slug}
-                        openGitHubRepoConnectionModal={openGitHubRepoConnectionModal}
-                      />
-                    )}
-                  </div>
-                </div>
-
-                <div>
-                  <Header2 spacing>Build settings</Header2>
-                  <div className="w-full rounded-sm border border-grid-dimmed p-4">
-                    <BuildSettingsForm buildSettings={buildSettings ?? {}} />
-                  </div>
-                </div>
-              </React.Fragment>
-            )}
+            {githubAppEnabled && (
+              <div>
+                <Header2 spacing>Git settings</Header2>
+                <div className="w-full rounded-sm border border-grid-dimmed p-4">
+                  {connectedGithubRepository ? (
+                    <ConnectedGitHubRepoForm connectedGitHubRepo={connectedGithubRepository} />
+                  ) : (
+                    <GitHubConnectionPrompt
+                      gitHubAppInstallations={githubAppInstallations ?? []}
+                      organizationSlug={organization.slug}
+                      projectSlug={project.slug}
+                      environmentSlug={environment.slug}
+                      openGitHubRepoConnectionModal={openGitHubRepoConnectionModal}
+                    />
+                  )}
+                </div>
+              </div>
+            )}
+
+            <div>
+              <Header2 spacing>Build settings</Header2>
+              <div className="w-full rounded-sm border border-grid-dimmed p-4">
+                <BuildSettingsForm buildSettings={buildSettings ?? {}} />
+              </div>
+            </div>

160-188: Harden path validation (Windows/UNC, backslashes, traversal) and normalize empty strings.

Current rules strip leading “/” but allow “C:\…”, “\server\…”, “..\…”, and “\…”. Also, rely on action to coerce empty → undefined. Tighten here to reduce bad states.

Apply:

-const UpdateBuildSettingsFormSchema = z.object({
+const UpdateBuildSettingsFormSchema = z.object({
   action: z.literal("update-build-settings"),
   triggerConfigFilePath: z
-    .string()
-    .trim()
-    .optional()
-    .transform((val) => (val ? val.replace(/^\/+/, "") : val))
+    .string()
+    .trim()
+    .optional()
+    .transform((val) => (val && val.length > 0 ? val.replace(/^[/\\]+/, "") : undefined))
+    .refine((val) => !val || !/^(?:[a-zA-Z]:\\|\\\\)/.test(val), {
+      message: "Use a relative path (no drive letters or UNC paths)",
+    })
+    .refine((val) => !val || !/(^|[\\/])\.\.(?:[\\/]|$)/.test(val), {
+      message: "Parent directory traversal '..' is not allowed",
+    })
     .refine((val) => !val || val.length <= 255, {
       message: "Config file path must not exceed 255 characters",
     }),
   installDirectory: z
-    .string()
-    .trim()
-    .optional()
-    .transform((val) => (val ? val.replace(/^\/+/, "") : val))
+    .string()
+    .trim()
+    .optional()
+    .transform((val) => (val && val.length > 0 ? val.replace(/^[/\\]+/, "") : undefined))
+    .refine((val) => !val || !/^(?:[a-zA-Z]:\\|\\\\)/.test(val), {
+      message: "Use a relative path (no drive letters or UNC paths)",
+    })
+    .refine((val) => !val || !/(^|[\\/])\.\.(?:[\\/]|$)/.test(val), {
+      message: "Parent directory traversal '..' is not allowed",
+    })
     .refine((val) => !val || val.length <= 255, {
       message: "Install directory must not exceed 255 characters",
     }),
   installCommand: z
     .string()
     .trim()
-    .optional()
+    .optional()
     .refine((val) => !val || !val.includes("\n"), {
       message: "Install command must be a single line",
     })
     .refine((val) => !val || val.length <= 500, {
       message: "Install command must not exceed 500 characters",
     }),
 });

Optional: Import and .and(BuildSettingsInputSchema) to avoid duplication if you add it in v3/buildSettings.ts.

🧹 Nitpick comments (6)
apps/webapp/app/v3/buildSettings.ts (1)

3-9: Share a stricter input schema (trim, empty→undefined, block absolute/traversal) to avoid drift across layers.

Right now the base schema allows raw strings and the route re-implements normalization. Export a reusable “input” schema here and consume it in the route to keep behavior consistent and safer.

Apply:

 import { z } from "zod";

 export const BuildSettingsSchema = z.object({
   triggerConfigFilePath: z.string().optional(),
   installDirectory: z.string().optional(),
   installCommand: z.string().optional(),
 });
 
 export type BuildSettings = z.infer<typeof BuildSettingsSchema>;
+
+// Optional but recommended: shared, stricter input schema for UI/API payloads
+const relPath = z
+  .string()
+  .trim()
+  .transform((s) => (s === "" ? undefined : s))
+  // strip leading slashes/backslashes
+  .transform((s) => (s ? s.replace(/^[/\\]+/, "") : s))
+  // disallow absolute Windows/UNC, and parent traversal
+  .refine((v) => !v || !/^(?:[a-zA-Z]:\\|\\\\)/.test(v), {
+    message: "Use a relative path (no drive letters or UNC paths).",
+  })
+  .refine((v) => !v || !/(^|[\\/])\.\.(?:[\\/]|$)/.test(v), {
+    message: "Parent directory traversal '..' is not allowed.",
+  })
+  .refine((v) => !v || v.length <= 255, { message: "Max 255 characters." });
+
+export const BuildSettingsInputSchema = z.object({
+  triggerConfigFilePath: relPath.optional(),
+  installDirectory: relPath.optional(),
+  installCommand: z
+    .string()
+    .trim()
+    .transform((s) => (s === "" ? undefined : s))
+    .refine((v) => !v || !/[\r\n]/.test(v), { message: "Command must be a single line." })
+    .refine((v) => !v || v.length <= 500, { message: "Max 500 characters." })
+    .optional(),
+});
+
+export type BuildSettingsInput = z.infer<typeof BuildSettingsInputSchema>;
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (5)

65-65: Unnecessary React default import (modern JSX).

You can drop the default React import; keep only named hooks. Safe in TS/Remix with react-jsx.

-import React, { useEffect, useState } from "react";
+import { useEffect, useState } from "react";

190-191: Avoid type/value name collision; rename inferred type.

Prevents confusion with the schema constant.

-type UpdateBuildSettingsFormSchema = z.infer<typeof UpdateBuildSettingsFormSchema>;
+type UpdateBuildSettingsValues = z.infer<typeof UpdateBuildSettingsFormSchema>;

414-438: Pass normalized values directly; drop manual “|| undefined”.

If you adopt the schema change (empty → undefined), you can forward submission.value as-is.

-      const { installDirectory, installCommand, triggerConfigFilePath } = submission.value;
-
-      const resultOrFail = await projectSettingsService.updateBuildSettings(projectId, {
-        installDirectory: installDirectory || undefined,
-        installCommand: installCommand || undefined,
-        triggerConfigFilePath: triggerConfigFilePath || undefined,
-      });
+      const { installDirectory, installCommand, triggerConfigFilePath } = submission.value;
+      const resultOrFail = await projectSettingsService.updateBuildSettings(projectId, {
+        installDirectory,
+        installCommand,
+        triggerConfigFilePath,
+      });

1107-1217: Mark fields optional in UI and mirror server limits with maxLength.

Improves UX consistency and prevents oversized inputs client‑side.

-          <Label htmlFor={fields.triggerConfigFilePath.id}>Trigger config file</Label>
+          <Label htmlFor={fields.triggerConfigFilePath.id} required={false}>
+            Trigger config file
+          </Label>
           <Input
             {...conform.input(fields.triggerConfigFilePath, { type: "text" })}
             defaultValue={buildSettings?.triggerConfigFilePath || ""}
             placeholder="trigger.config.ts"
+            maxLength={255}
             onChange={(e) => {
...
-          <Label htmlFor={fields.installCommand.id}>Install command</Label>
+          <Label htmlFor={fields.installCommand.id} required={false}>Install command</Label>
           <Input
             {...conform.input(fields.installCommand, { type: "text" })}
             defaultValue={buildSettings?.installCommand || ""}
-            placeholder="e.g., `npm install`, or `bun install`"
+            placeholder="e.g., npm install or bun install"
+            maxLength={500}
             onChange={(e) => {
...
-          <Label htmlFor={fields.installDirectory.id}>Install directory</Label>
+          <Label htmlFor={fields.installDirectory.id} required={false}>
+            Install directory
+          </Label>
           <Input
             {...conform.input(fields.installDirectory, { type: "text" })}
             defaultValue={buildSettings?.installDirectory || ""}
-            placeholder=""
+            placeholder="e.g., apps/webapp"
+            maxLength={255}
             onChange={(e) => {

160-188: Optional: reuse shared input schema to eliminate duplication.

If you add BuildSettingsInputSchema, import and use it here:

-const UpdateBuildSettingsFormSchema = z.object({
-  action: z.literal("update-build-settings"),
-  ...
-});
+import { BuildSettingsInputSchema } from "~/v3/buildSettings";
+const UpdateBuildSettingsFormSchema = z
+  .object({ action: z.literal("update-build-settings") })
+  .and(BuildSettingsInputSchema);

Also applies to: 414-438, 1107-1217

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d86288f and 9d3adb5.

📒 Files selected for processing (2)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (10 hunks)
  • apps/webapp/app/v3/buildSettings.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

When importing from @trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from @trigger.dev/core’s package.json

Files:

  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
apps/webapp/app/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

Modules intended for test consumption under apps/webapp/app/**/*.ts must not read environment variables; accept configuration via options instead

Files:

  • apps/webapp/app/v3/buildSettings.ts
🧬 Code graph analysis (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (8)
apps/webapp/app/models/message.server.ts (2)
  • redirectBackWithErrorMessage (200-207)
  • redirectBackWithSuccessMessage (209-216)
apps/webapp/app/components/primitives/Headers.tsx (1)
  • Header2 (52-70)
apps/webapp/app/v3/buildSettings.ts (1)
  • BuildSettings (9-9)
apps/webapp/app/components/primitives/Label.tsx (1)
  • Label (25-41)
apps/webapp/app/components/primitives/Hint.tsx (1)
  • Hint (3-9)
apps/webapp/app/components/primitives/FormButtons.tsx (1)
  • FormButtons (3-22)
apps/webapp/app/components/primitives/Buttons.tsx (1)
  • Button (296-331)
apps/webapp/app/components/primitives/Spinner.tsx (1)
  • SpinnerWhite (76-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (22)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (2)

124-139: Loader correctly exposes buildSettings.

Shape looks consistent with presenter/service. LGTM.


160-188: Verify server-side consumers guard against command/path misuse.

Checked: UI validation in apps/webapp/.../route.tsx trims input, strips leading slashes, and limits length/newlines; BuildSettingsSchema (apps/webapp/app/v3/buildSettings.ts) is permissive and projectSettings.server.updateBuildSettings simply persists the JSON. I found no direct usage of installCommand/installDirectory/triggerConfigFilePath being executed, but exec helpers exist (packages/core/src/v3/apps/exec.ts, apps/coordinator/src/exec.ts, apps/docker-provider/src/index.ts).

Action required: ensure any runner that consumes these fields (installCommand / paths) runs in an isolated sandbox/container, uses safe path-joining and confinement (realpath/whitelist, forbid ".."), never shell-interpolates user strings (use execFile/execa with arg arrays), and enforces timeouts and resource limits.

@myftija myftija merged commit 501a383 into main Sep 16, 2025
30 of 31 checks passed
@myftija myftija deleted the project-build-settings branch September 16, 2025 13:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (2)

160-188: Reuse BuildSettingsSchema; harden normalization (Windows abs paths, CRLF) and coerce blanks to undefined.

Avoid schema drift and enforce stronger constraints at the edge.

-import { type BuildSettings } from "~/v3/buildSettings";
+import { type BuildSettings, BuildSettingsSchema } from "~/v3/buildSettings";
-const UpdateBuildSettingsFormSchema = z.object({
-  action: z.literal("update-build-settings"),
-  triggerConfigFilePath: z
-    .string()
-    .trim()
-    .optional()
-    .transform((val) => (val ? val.replace(/^\/+/, "") : val))
-    .refine((val) => !val || val.length <= 255, {
-      message: "Config file path must not exceed 255 characters",
-    }),
-  installDirectory: z
-    .string()
-    .trim()
-    .optional()
-    .transform((val) => (val ? val.replace(/^\/+/, "") : val))
-    .refine((val) => !val || val.length <= 255, {
-      message: "Install directory must not exceed 255 characters",
-    }),
-  installCommand: z
-    .string()
-    .trim()
-    .optional()
-    .refine((val) => !val || !val.includes("\n"), {
-      message: "Install command must be a single line",
-    })
-    .refine((val) => !val || val.length <= 500, {
-      message: "Install command must not exceed 500 characters",
-    }),
-});
+const UpdateBuildSettingsFormSchema = z
+  .object({ action: z.literal("update-build-settings") })
+  .and(
+    BuildSettingsSchema
+      // Normalize and coerce blanks to undefined
+      .transform((v) => ({
+        ...v,
+        triggerConfigFilePath:
+          v.triggerConfigFilePath && v.triggerConfigFilePath.trim() !== ""
+            ? v.triggerConfigFilePath.replace(/^[/\\]+/, "")
+            : undefined,
+        installDirectory:
+          v.installDirectory && v.installDirectory.trim() !== ""
+            ? v.installDirectory.replace(/^[/\\]+/, "")
+            : undefined,
+        installCommand:
+          v.installCommand && v.installCommand.trim() !== ""
+            ? v.installCommand.trim()
+            : undefined,
+      }))
+      // Disallow absolute paths (POSIX and Windows)
+      .refine(
+        (v) =>
+          !v.triggerConfigFilePath ||
+          !/^(?:[a-zA-Z]:[\\/]|[\\/])/.test(v.triggerConfigFilePath),
+        { path: ["triggerConfigFilePath"], message: "Use a relative path (no leading slash/drive)." }
+      )
+      .refine(
+        (v) => !v.installDirectory || !/^(?:[a-zA-Z]:[\\/]|[\\/])/.test(v.installDirectory),
+        { path: ["installDirectory"], message: "Use a relative path (no leading slash/drive)." }
+      )
+      // Enforce single-line commands (handle CRLF and LF)
+      .refine((v) => !v.installCommand || !/[\r\n]/.test(v.installCommand), {
+        path: ["installCommand"],
+        message: "Command must be a single line.",
+      })
+  );

574-600: Ungate Build settings from GitHub app enablement.

Users should edit build settings regardless of GitHub connection.

-            {githubAppEnabled && (
-              <Fragment>
-                <div>
-                  <Header2 spacing>Git settings</Header2>
-                  <div className="w-full rounded-sm border border-grid-dimmed p-4">
-                    {connectedGithubRepository ? (
-                      <ConnectedGitHubRepoForm connectedGitHubRepo={connectedGithubRepository} />
-                    ) : (
-                      <GitHubConnectionPrompt
-                        gitHubAppInstallations={githubAppInstallations ?? []}
-                        organizationSlug={organization.slug}
-                        projectSlug={project.slug}
-                        environmentSlug={environment.slug}
-                        openGitHubRepoConnectionModal={openGitHubRepoConnectionModal}
-                      />
-                    )}
-                  </div>
-                </div>
-
-                <div>
-                  <Header2 spacing>Build settings</Header2>
-                  <div className="w-full rounded-sm border border-grid-dimmed p-4">
-                    <BuildSettingsForm buildSettings={buildSettings ?? {}} />
-                  </div>
-                </div>
-              </Fragment>
-            )}
+            {githubAppEnabled && (
+              <div>
+                <Header2 spacing>Git settings</Header2>
+                <div className="w-full rounded-sm border border-grid-dimmed p-4">
+                  {connectedGithubRepository ? (
+                    <ConnectedGitHubRepoForm connectedGitHubRepo={connectedGithubRepository} />
+                  ) : (
+                    <GitHubConnectionPrompt
+                      gitHubAppInstallations={githubAppInstallations ?? []}
+                      organizationSlug={organization.slug}
+                      projectSlug={project.slug}
+                      environmentSlug={environment.slug}
+                      openGitHubRepoConnectionModal={openGitHubRepoConnectionModal}
+                    />
+                  )}
+                </div>
+              </div>
+            )}
+
+            <div>
+              <Header2 spacing>Build settings</Header2>
+              <div className="w-full rounded-sm border border-grid-dimmed p-4">
+                <BuildSettingsForm buildSettings={buildSettings ?? {}} />
+              </div>
+            </div>
🧹 Nitpick comments (3)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (3)

65-65: Drop default React import; use Fragment shorthand.

Slight cleanup; no behavior change.

-import React, { useEffect, useState } from "react";
+import { Fragment, useEffect, useState } from "react";
-              <React.Fragment>
+              <Fragment>
...
-              </React.Fragment>
+              </Fragment>

Also applies to: 574-575, 599-599


190-191: Avoid identifier shadowing between value and type.

Rename the inferred type to reduce confusion.

-type UpdateBuildSettingsFormSchema = z.infer<typeof UpdateBuildSettingsFormSchema>;
+type UpdateBuildSettingsFormValues = z.infer<typeof UpdateBuildSettingsFormSchema>;

1107-1217: Polish the form UX: maxLength, spellCheck, and clearer placeholders.

Keeps client-side consistent with schema and avoids autocorrect surprises.

-          <Input
+          <Input
             {...conform.input(fields.triggerConfigFilePath, { type: "text" })}
             defaultValue={buildSettings?.triggerConfigFilePath || ""}
             placeholder="trigger.config.ts"
+            spellCheck={false}
+            maxLength={255}
             onChange={(e) => {
...
-          <Input
+          <Input
             {...conform.input(fields.installCommand, { type: "text" })}
             defaultValue={buildSettings?.installCommand || ""}
-            placeholder="e.g., `npm install`, or `bun install`"
+            placeholder="e.g., npm install or bun install"
+            spellCheck={false}
+            maxLength={500}
             onChange={(e) => {
...
-          <Input
+          <Input
             {...conform.input(fields.installDirectory, { type: "text" })}
             defaultValue={buildSettings?.installDirectory || ""}
-            placeholder=""
+            placeholder="e.g., . or apps/webapp"
+            spellCheck={false}
+            maxLength={255}
             onChange={(e) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3adb5 and 9d9ce24.

📒 Files selected for processing (6)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (10 hunks)
  • apps/webapp/app/services/projectSettings.server.ts (2 hunks)
  • apps/webapp/app/services/projectSettingsPresenter.server.ts (5 hunks)
  • apps/webapp/app/v3/buildSettings.ts (1 hunks)
  • internal-packages/database/prisma/migrations/20250915141201_add_build_settings_to_project/migration.sql (1 hunks)
  • internal-packages/database/prisma/schema.prisma (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/webapp/app/services/projectSettings.server.ts
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/database/prisma/migrations/20250915141201_add_build_settings_to_project/migration.sql
  • apps/webapp/app/v3/buildSettings.ts
  • apps/webapp/app/services/projectSettingsPresenter.server.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

We use zod a lot in packages/core and in the webapp

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

When importing from @trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from @trigger.dev/core’s package.json

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Declare build options and extensions (external, jsx, conditions, extensions) via the build block in trigger.config.ts rather than custom scripts

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
PR: triggerdotdev/trigger.dev#0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
🧬 Code graph analysis (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (7)
apps/webapp/app/models/message.server.ts (2)
  • redirectBackWithErrorMessage (200-207)
  • redirectBackWithSuccessMessage (209-216)
apps/webapp/app/v3/buildSettings.ts (1)
  • BuildSettings (9-9)
apps/webapp/app/components/primitives/Label.tsx (1)
  • Label (25-41)
apps/webapp/app/components/primitives/Hint.tsx (1)
  • Hint (3-9)
apps/webapp/app/components/primitives/FormButtons.tsx (1)
  • FormButtons (3-22)
apps/webapp/app/components/primitives/Buttons.tsx (1)
  • Button (296-331)
apps/webapp/app/components/primitives/Spinner.tsx (1)
  • SpinnerWhite (76-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: units / webapp / 📊 Merge Reports
🔇 Additional comments (6)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (6)

80-81: Good: use shared BuildSettings type.


124-139: Loader plumbs buildSettings to client — LGTM.

Keep presenter output JSON-safe. Strings-only here are fine.


225-225: Good: union includes the new action.


414-438: Server action for update-build-settings — LGTM.

Blank-to-undefined coercion on write path is correct.


452-453: Client consumes buildSettings — LGTM.


1165-1198: Align naming: code uses installDirectory; PR uses "root directory"

BuildSettingsSchema defines installDirectory (apps/webapp/app/v3/buildSettings.ts) and projectSettings.server.ts exposes updateBuildSettings; UI shows "Install directory" in apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (lines 1165–1198). Update the PR description to "install directory" or rename the schema/UI to "rootDirectory" if that is the intended product term.

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants