Skip to content

Conversation

@ericallam
Copy link
Member

When using glob patterns with parent directory references (../), the default behavior strips ".." segments resulting in unexpected paths. For example, "../shared/**" would place files at "shared/" instead of preserving the original path structure.

This adds an optional "destination" parameter that allows users to explicitly specify where matched files should be placed:

additionalFiles({
files: ["../shared/**"],
destination: "apps/shared"
})

When destination is specified, files are placed relative to the glob pattern's base directory under the destination path. This is useful in monorepo setups where files need to maintain their structure.

Also updates documentation to explain this behavior and the new option.

When using glob patterns with parent directory references (../), the
default behavior strips ".." segments resulting in unexpected paths.
For example, "../shared/**" would place files at "shared/" instead of
preserving the original path structure.

This adds an optional "destination" parameter that allows users to
explicitly specify where matched files should be placed:

  additionalFiles({
    files: ["../shared/**"],
    destination: "apps/shared"
  })

When destination is specified, files are placed relative to the glob
pattern's base directory under the destination path. This is useful
in monorepo setups where files need to maintain their structure.

Also updates documentation to explain this behavior and the new option.

Slack thread: https://triggerdotdev.slack.com/archives/C08N6PJTK2Q/p1767946469914139?thread_ts=1756405171.439939&cid=C08N6PJTK2Q
@changeset-bot
Copy link

changeset-bot bot commented Jan 9, 2026

🦋 Changeset detected

Latest commit: 674f5c7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
references-telemetry Patch
@trigger.dev/core Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Walkthrough

Adds an optional destination?: string to AdditionalFilesOptions and propagates it through the additional-files discovery and copy flow. Introduces and exports getGlobBase(pattern: string) to compute non-wildcard glob bases and uses it to preserve relative paths when copying files from parent directories into a specified destination. Updates signatures for functions that find and add additional files, adds unit tests for getGlobBase, introduces a Vitest config and test scripts plus a vitest devDependency, and expands documentation with monorepo/copying examples and a changeset describing the new option.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description addresses the issue, explains the solution with concrete examples, and mentions documentation updates. However, it lacks required checklist items, testing steps, and changelog section from the template. Complete the description template by adding the checklist section with all items checked, describing testing steps taken, and including a structured changelog entry as specified in the template.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding a destination option to the additionalFiles extension to fix path resolution issues with parent directory glob patterns.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff856f7 and 674f5c7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • .changeset/polite-lobsters-double.md
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • .changeset/polite-lobsters-double.md
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • .changeset/polite-lobsters-double.md
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Specify task locations in trigger.config.ts using the `dirs` array, with automatic exclusion of .test and .spec files

Applied to files:

  • .changeset/polite-lobsters-double.md
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • .changeset/polite-lobsters-double.md
⏰ 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). (24)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 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 (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • 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 (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
.changeset/polite-lobsters-double.md (1)

1-7: Changeset is clear and accurate.

The changeset correctly documents the new destination parameter option and clearly explains the problem (parent-directory segment stripping) and the solution (explicit destination placement). The patch version bump is appropriate for this feature addition. Format and content are both correct.


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.

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: fix(build): add destination option to additionalFiles extension

Summary

This PR adds a destination option to the additionalFiles extension to address path resolution issues when using glob patterns with parent directory references (../). The implementation is clean and the documentation is thorough.

Code Quality & Best Practices ✅

  • Good: Well-documented with JSDoc comments including examples
  • Good: The getGlobBase helper function is clearly commented and handles common glob characters
  • Good: Backwards compatible - existing behavior is preserved when destination is not specified
  • Minor: The type AdditionalFilesOptions is duplicated in both packages/build/src/extensions/core/additionalFiles.ts and packages/build/src/internal/additionalFiles.ts. Consider exporting from one location and importing in the other to maintain DRY principles.

Potential Issues ⚠️

  1. Path separator inconsistency: In getGlobBase(), the pattern is split by /[/\\]/ (both forward and back slashes) but rejoined with posix.sep (always /). While this works, it could cause issues on Windows if the input pattern uses backslashes and the result is compared with a path using native separators. Consider using path.sep for the join, or documenting that glob patterns should always use forward slashes.

  2. Glob base extraction edge cases: The getGlobBase function may not handle all glob patterns correctly:

    • Patterns like ../** would return .. which resolves correctly
    • But patterns like {a,b}/** would return . (treating {a,b} as glob)
    • Consider whether patterns starting with brace expansion need special handling
  3. Security consideration: When destination is user-provided, there's no validation that it doesn't contain path traversal sequences like ../../../etc. While this is a build-time operation running in the user's own environment, it's worth documenting that users should not pass untrusted input to this option.

Performance ✅

  • The getGlobBase function is called once per file in the loop at line 115, but since it only depends on the matcher (which is constant for all files from that matcher), it could be hoisted outside the file loop for a minor optimization. However, this is unlikely to matter in practice.

Test Coverage ⚠️

  • No unit tests added: This PR adds new functionality (getGlobBase, destination path computation) but no tests. Consider adding tests for:
    • getGlobBase with various patterns (../shared/**, ./assets/*.txt, **/*.js, {a,b}/**)
    • Path computation with destination specified vs not specified
    • Edge cases like empty destination, absolute destination paths

Documentation ✅

  • Good: Comprehensive documentation update in additionalFiles.mdx
  • Good: Typo fix (effectsaffects)
  • Good: Multiple examples showing different use cases including monorepo setups

Suggestions

  1. Add unit tests for the new functionality, particularly for getGlobBase and the path computation logic.

  2. Consider extracting getGlobBase call outside the file loop:

const globBase = options?.destination ? getGlobBase(matcher) : null;
const absoluteGlobBase = globBase && !isAbsolute(globBase) ? resolve(cwd, globBase) : globBase;

for (const file of files) {
  // ... use absoluteGlobBase
}
  1. Consider validating that destination doesn't start with / or contain .. to prevent unexpected behavior.

Overall

This is a well-implemented feature that solves a real problem for monorepo users. The code is clean, the documentation is excellent, and the implementation is backwards compatible. The main gap is test coverage - adding tests would significantly improve confidence in the path handling logic, especially around edge cases.

Recommendation: Approve with suggestion to add tests in a follow-up PR.

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/build/src/internal/additionalFiles.ts (1)

76-135: Fix Windows path separator bug and add destination path safety validation.

Three security/correctness issues:

  1. Windows separator mismatch (critical): relative(cwd, file) returns platform-specific separators (backslashes on Windows), but code splits by posix.sep (/). This means the .. filtering on line 125 silently fails on Windows—a malicious glob pattern could escape via ../ segments.

  2. Absolute destination bypass: If options.destination is absolute, join(destinationPath, pathInsideDestinationDir) ignores destinationPath entirely, bypassing output directory bounds.

  3. No final safety guard: No validation ensures the computed destination actually stays within destinationPath before copyFile() executes.

Proposed fix
-import { dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
+import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";

 function getGlobBase(pattern: string): string {
   const parts = pattern.split(/[/\\]/);
   const baseParts: string[] = [];

   for (const part of parts) {
     if (part.includes("*") || part.includes("?") || part.includes("[") || part.includes("{")) {
       break;
     }
     baseParts.push(part);
   }

-  return baseParts.length > 0 ? baseParts.join(posix.sep) : ".";
+  return baseParts.length > 0 ? baseParts.join(sep) : ".";
 }

 async function findStaticAssetsForMatcher(
   matcher: string,
   destinationPath: string,
   options?: { cwd?: string; ignore?: string[]; destination?: string }
 ): Promise<MatchedStaticAssets> {
   const result: MatchedStaticAssets = [];

   const files = await glob({
     patterns: [matcher],
     cwd: options?.cwd,
     ignore: options?.ignore ?? [],
     onlyFiles: true,
     absolute: true,
   });

   const cwd = options?.cwd ?? process.cwd();

+  const validateRelativePath = (path: string): void => {
+    if (isAbsolute(path) || path.split(sep).includes("..")) {
+      throw new Error(`[additionalFiles] Path traversal detected: ${path}`);
+    }
+  };
+
+  const resolveDestinationSafely = (root: string, rel: string): string => {
+    const resolved = resolve(root, rel);
+    if (!resolved.startsWith(root + sep) && resolved !== root) {
+      throw new Error(
+        `[additionalFiles] Refusing to write outside outputPath: ${resolved} is not under ${root}`
+      );
+    }
+    return resolved;
+  };

   for (const file of files) {
     let pathInsideDestinationDir: string;

     if (options?.destination) {
+      if (isAbsolute(options.destination)) {
+        throw new Error(`[additionalFiles] destination must be a relative path, got: ${options.destination}`);
+      }
       const globBase = getGlobBase(matcher);
       const absoluteGlobBase = isAbsolute(globBase) ? globBase : resolve(cwd, globBase);
       const relativeToGlobBase = relative(absoluteGlobBase, file);
+      validateRelativePath(relativeToGlobBase);

       pathInsideDestinationDir = join(options.destination, relativeToGlobBase);
     } else {
       pathInsideDestinationDir = relative(cwd, file)
-        .split(posix.sep)
+        .split(sep)
         .filter((p) => p !== "..")
-        .join(posix.sep);
+        .join(sep);
+      validateRelativePath(pathInsideDestinationDir);
     }

-    const relativeDestinationPath = join(destinationPath, pathInsideDestinationDir);
+    const relativeDestinationPath = resolveDestinationSafely(destinationPath, pathInsideDestinationDir);

     result.push({
       source: file,
       destination: relativeDestinationPath,
     });
   }

   return result;
 }
🧹 Nitpick comments (3)
packages/build/src/extensions/core/additionalFiles.ts (1)

4-25: Document (and/or validate) that destination must be a safe relative path.

Given the internal code ultimately does join(manifest.outputPath, destination, ...), allowing absolute paths or .. segments in destination can escape the build directory unless explicitly prevented. Consider adding a one-liner to the JSDoc like “Must be a relative path and must not contain ..”.

docs/config/extensions/additionalFiles.mdx (1)

27-83: Add a short note: destination is relative (no absolute paths / no ..).

This will prevent readers from assuming they can set /tmp/foo or ../../something and also aligns with the safest implementation.

packages/build/src/internal/additionalFiles.ts (1)

30-50: Minor: options.files ?? [] is unnecessary (and can hide invalid inputs).

AdditionalFilesOptions.files is required in the public type; if this can be undefined in practice, consider making it optional at the type level and validating early. Otherwise, dropping ?? [] makes misconfiguration fail fast.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57ba252 and a6e718a.

📒 Files selected for processing (3)
  • docs/config/extensions/additionalFiles.mdx
  • packages/build/src/extensions/core/additionalFiles.ts
  • packages/build/src/internal/additionalFiles.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • packages/build/src/internal/additionalFiles.ts
  • packages/build/src/extensions/core/additionalFiles.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • packages/build/src/internal/additionalFiles.ts
  • packages/build/src/extensions/core/additionalFiles.ts
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • packages/build/src/internal/additionalFiles.ts
  • packages/build/src/extensions/core/additionalFiles.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/build/src/internal/additionalFiles.ts
  • packages/build/src/extensions/core/additionalFiles.ts
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/repo.mdc:0-0
Timestamp: 2025-11-27T16:26:47.602Z
Learning: Refer to the monorepo structure documentation at repo.md before making changes or adding new files
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
  • docs/config/extensions/additionalFiles.mdx
  • packages/build/src/extensions/core/additionalFiles.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Specify task locations in trigger.config.ts using the `dirs` array, with automatic exclusion of .test and .spec files

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
  • docs/config/extensions/additionalFiles.mdx
  • packages/build/src/extensions/core/additionalFiles.ts
📚 Learning: 2025-11-27T16:26:47.602Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/repo.mdc:0-0
Timestamp: 2025-11-27T16:26:47.602Z
Learning: Refer to the monorepo structure documentation at repo.md before making changes or adding new files

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `metadata.parent` and `metadata.root` to update parent and root task metadata from child tasks

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure Trigger.dev project in `trigger.config.ts` using `defineConfig()` with project ref and task directories

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export every task, including subtasks

Applied to files:

  • docs/config/extensions/additionalFiles.mdx
🧬 Code graph analysis (1)
packages/build/src/internal/additionalFiles.ts (1)
packages/build/src/extensions/core/additionalFiles.ts (1)
  • AdditionalFilesOptions (4-25)
⏰ 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). (25)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • 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: typecheck / typecheck
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)

@ericallam
Copy link
Member Author

@claude add a patch changeset file please

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 39s —— View job


Adding Changeset

  • Create patch changeset file for @trigger.dev/build
  • Commit and push changes

Added changeset file .changeset/light-apricots-listen.md with a patch bump for @trigger.dev/build.
|

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: fix(build): add destination option to additionalFiles extension

Summary

This PR adds a destination option to the additionalFiles extension to give users explicit control over where files are placed when using glob patterns with parent directory references (../). This is a useful enhancement for monorepo setups.


Code Quality and Best Practices

Positives:

  • Well-documented with JSDoc comments and comprehensive documentation updates
  • The type definition is duplicated in both files (packages/build/src/extensions/core/additionalFiles.ts and packages/build/src/internal/additionalFiles.ts), which is intentional since the extension re-exports the options type
  • The getGlobBase helper function is well-commented and handles common glob characters
  • The changeset is properly included

Suggestions:

  1. Type duplication: Consider importing the AdditionalFilesOptions type from the internal module in the extension file rather than duplicating it. This ensures they stay in sync:

    export type { AdditionalFilesOptions } from "../../internal/additionalFiles.js";
  2. Typo fix in docs: Good catch on fixing "effects" → "affects" in the documentation.


Potential Bugs or Issues

  1. Cross-platform path handling (Minor): The getGlobBase function uses posix.sep when joining base parts (line 90), but splits on both / and \ (line 79). While this is likely intentional for cross-platform input handling with POSIX-style output, consider adding a comment to clarify this design decision.

  2. Path separator inconsistency: In the new code path (lines 113-120), join() from node:path is used (which is platform-specific), while the original code (lines 123-126) uses posix.sep. This could cause inconsistent behavior on Windows. Consider using posix.join consistently:

    pathInsideDestinationDir = posix.join(options.destination, relativeToGlobBase);
  3. Absolute destination paths: If a user provides an absolute path for destination (e.g., /apps/shared), the behavior might be unexpected. Consider validating that destination is a relative path, or documenting the expected behavior.


Performance Considerations

  • No concerns: The getGlobBase function is called once per file, which is minimal overhead. The implementation is efficient.

Security Concerns

  1. Path traversal (Low risk): The destination option could theoretically allow placing files outside the intended build directory if combined with .. segments. However, since this is a build-time configuration controlled by the developer (not user input), the risk is minimal. Still, you might consider normalizing the destination path and ensuring files stay within the output directory:
    const normalizedDest = posix.normalize(options.destination);
    if (normalizedDest.startsWith('..')) {
      throw new Error('destination cannot reference parent directories');
    }

Test Coverage

Concern: There don't appear to be any unit tests for the additionalFiles functionality, including this new feature. Given that this touches file system operations and path manipulation (which can be tricky cross-platform), I'd recommend adding tests for:

  1. Basic destination functionality with simple patterns
  2. Nested directory structures with destination
  3. Edge cases like empty destination, destination with trailing slashes
  4. The getGlobBase function (could be unit tested in isolation)
  5. Patterns without glob characters (e.g., exact file paths)

Example test cases for getGlobBase:

expect(getGlobBase("../shared/**")).toBe("../shared");
expect(getGlobBase("./assets/*.txt")).toBe("./assets");
expect(getGlobBase("files/**/*.js")).toBe("files");
expect(getGlobBase("**/*.js")).toBe(".");
expect(getGlobBase("exact-file.txt")).toBe("exact-file.txt");

Overall Assessment

This is a well-implemented feature that solves a real pain point for monorepo users. The documentation is thorough and the implementation is straightforward.

Recommended before merge:

  1. Address the path separator inconsistency (use posix.join consistently)
  2. Consider adding basic unit tests for the new functionality

Nice to have:

  • Validate that destination doesn't contain path traversal sequences
  • Consider extracting and sharing the type definition to avoid duplication

Good work on this enhancement! 👍

… option

When a pattern has no glob characters (e.g., './config/settings.json'),
return the parent directory instead of the full path. This ensures that
relative() preserves the filename when computing the destination path.

Co-authored-by: Eric Allam <[email protected]>
@claude claude bot force-pushed the claude/slack-fix-additionalfiles-path-resolution-kDZvR branch from b30a65c to 55aba75 Compare January 9, 2026 09:17
@claude
Copy link

claude bot commented Jan 9, 2026

Code Review: fix(build): add destination option to additionalFiles extension

Thanks for this PR! The feature to add a destination option for the additionalFiles extension is useful for monorepo setups. Here is my feedback:

Overall Assessment

The PR provides a clean solution to a real problem - when using glob patterns with parent directory references (../), the default behavior strips .. segments, which can be confusing. The new destination option gives users explicit control.


Code Quality and Best Practices

Strengths:

  • Good JSDoc documentation on the destination option with practical examples
  • The getGlobBase function is well-documented with clear comments explaining its purpose
  • Documentation updates are thorough and include practical monorepo examples
  • Nice typo fix ("effects" -> "affects") in the docs

Suggestions:

  1. Type Duplication: The AdditionalFilesOptions type is duplicated in both files:

    • packages/build/src/extensions/core/additionalFiles.ts
    • packages/build/src/internal/additionalFiles.ts

    Consider exporting from one location and importing in the other to maintain DRY principles and avoid drift.

  2. Consider extracting getGlobBase: This function might be useful elsewhere. Consider whether it should be a shared utility.


Potential Bugs or Issues

  1. Path Separator Consistency (Medium Priority):
    In getGlobBase, the code splits on both / and \\ (/[/\\]/), but joins with posix.sep (which is always /). While this works, mixing join() (which uses native separators) with posix.sep in getGlobBase could theoretically cause issues on Windows:

    // Line 129 uses join() which uses native path.sep on Windows
    pathInsideDestinationDir = join(options.destination, relativeToGlobBase);

    Consider using posix.join() consistently for the destination path to ensure consistent forward slashes across platforms.

  2. Empty Pattern Edge Case:
    The getGlobBase function returns "." for an empty string input. While unlikely in practice, you might want to validate input patterns before processing.

  3. Single-file Glob Patterns:
    For a pattern like "../shared/specific-file.ts" (no glob characters), getGlobBase returns "../shared" (pops the last segment). This seems intentional but the behavior should be documented or tested to ensure it produces the expected destination paths.


Performance Considerations

The implementation looks efficient:

  • getGlobBase is called once per file rather than being precomputed per matcher, which is slightly suboptimal but unlikely to matter in practice given typical file counts
  • Consider caching getGlobBase(matcher) and absoluteGlobBase outside the file loop (lines 124-125) since they do not change per file

Security Concerns

  1. Path Traversal (Low Risk):
    The destination option allows users to specify arbitrary paths. While this is user-controlled configuration (not untrusted input), users could potentially specify destinations like "../../some-sensitive-location". This is likely acceptable since:

    • Users have full control over their config file
    • Build output is generally sandboxed to the build directory

    However, you might want to add a note in the documentation about ensuring the destination stays within expected boundaries.

  2. The existing .. stripping behavior in the default path (lines 132-135) is a good security practice - this is preserved correctly.


Test Coverage

This is the main concern: I could not find any unit tests for the new functionality. Consider adding tests for:

  1. Basic destination option functionality
  2. getGlobBase function with various patterns:
    • Glob patterns: "../shared/**", "./assets/*.ts"
    • Specific files: "../file.ts"
    • Edge cases: "**", "file.ts", empty strings
  3. Path computation with nested directories
  4. Backward compatibility (ensure behavior without destination is unchanged)

Example test cases for getGlobBase:

expect(getGlobBase("../shared/**")).toBe("../shared");
expect(getGlobBase("./assets/*.ts")).toBe("./assets");
expect(getGlobBase("../shared/specific-file.ts")).toBe("../shared");
expect(getGlobBase("**")).toBe(".");
expect(getGlobBase("file.ts")).toBe(".");

Minor Suggestions

  1. The matches variable was removed (line ~73 in original) - good cleanup since it was unused.

  2. Consider adding a warning log when destination is used with a pattern that does not contain .. (since users might not need destination in that case).


Summary

This is a solid PR that addresses a real user pain point. The code is clean and well-documented. The main recommendation is to add unit tests to ensure the getGlobBase function and destination path computation work correctly across edge cases.

Once tests are added, this looks good to merge! 🎉

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/build/src/internal/additionalFiles.ts (1)

30-51: Add tests for the destination option and getGlobBase helper.

The destination option and getGlobBase helper currently have no test coverage. Add unit tests to verify:

  • Destination path resolution works correctly for various glob patterns
  • Monorepo scenarios with parent directory references like ../ are handled properly
  • Edge cases like single-file patterns, absolute paths, and complex globs are tested
  • The glob base extraction logic handles all pattern types

Use vitest for tests in the packages/build package.

🤖 Fix all issues with AI agents
In @packages/build/src/internal/additionalFiles.ts:
- Around line 76-100: getGlobBase incorrectly returns a filename for single-part
non-glob patterns like "file.txt"; update the non-glob branch in getGlobBase so
that if (!hasGlobCharacters) you return "." when baseParts.length <= 1 (covering
zero- or single-part patterns) and only pop the last part when baseParts.length
> 1, then join with posix.sep as before; reference function name getGlobBase and
variable baseParts to locate the change.
- Around line 131-136: The fallback handling for computing
pathInsideDestinationDir uses relative(cwd, file) then splits on posix.sep,
which fails on Windows because path.relative returns backslashes; update the
logic in the block that sets pathInsideDestinationDir to either split on
path.sep (so split(relative(cwd, file), path.sep) then filter out "..") or
replace the call to relative with posix.relative(cwd, file) so the returned
string uses "/" consistently before splitting by posix.sep; ensure you reference
and update the code around the pathInsideDestinationDir assignment and the use
of relative/cwd/file accordingly.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b30a65c and 55aba75.

📒 Files selected for processing (1)
  • packages/build/src/internal/additionalFiles.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • packages/build/src/internal/additionalFiles.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • packages/build/src/internal/additionalFiles.ts
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • packages/build/src/internal/additionalFiles.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/build/src/internal/additionalFiles.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Specify task locations in trigger.config.ts using the `dirs` array, with automatic exclusion of .test and .spec files

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
🧬 Code graph analysis (1)
packages/build/src/internal/additionalFiles.ts (1)
packages/build/src/extensions/core/additionalFiles.ts (1)
  • AdditionalFilesOptions (4-25)
⏰ 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). (24)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
packages/build/src/internal/additionalFiles.ts (4)

4-4: LGTM!

The new imports isAbsolute and resolve are correctly added and used in the destination path resolution logic.


7-28: Excellent documentation and type definition.

The JSDoc clearly explains the purpose and behavior of the destination option with a helpful monorepo example. The optional field correctly uses TypeScript's type syntax per coding guidelines.


60-64: LGTM!

The function signatures are correctly updated to propagate the destination option through the call chain while maintaining backward compatibility with the optional parameter.

Also applies to: 102-106


122-129: LGTM! Well-implemented destination logic.

The destination path calculation correctly:

  • Extracts the glob pattern's base directory
  • Resolves relative patterns to absolute paths
  • Preserves file structure relative to the glob base
  • Places files under the specified destination

This matches the PR's intended behavior for monorepo setups.

@ericallam
Copy link
Member Author

@claude can you add unit tests to this PR and please address all review comments

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 2m 56s —— View job


Adding Unit Tests and Addressing Review Comments

  • Read current implementation and understand the code
  • Analyze all review comments to address
  • Fix edge case for single-part non-glob patterns in getGlobBase
  • Fix Windows path separator handling in fallback behavior
  • Create vitest config for packages/build
  • Add unit tests for getGlobBase function
  • Commit and push changes

Changes made:

  1. Fixed getGlobBase single-part pattern edge case (packages/build/src/internal/additionalFiles.ts:97-98)

    • Previously, patterns like "file.txt" would return "file.txt" as the base, causing relative() to return an empty string and losing the filename
    • Now correctly returns "." for single-part patterns
  2. Fixed Windows path separator handling (packages/build/src/internal/additionalFiles.ts:136)

    • Changed from .split(posix.sep) to .split(sep) in the fallback behavior
    • This ensures ".." segments are properly filtered on Windows where path.relative() returns backslashes
  3. Added unit tests (packages/build/test/additionalFiles.test.ts)

    • 21 test cases covering:
      • Glob patterns with wildcards (../shared/**, ./assets/*.txt, **/*.js, etc.)
      • Specific file paths without globs (including single-part patterns)
      • Edge cases (empty string, Windows backslashes, mixed separators)
  4. Added vitest configuration for @trigger.dev/build package
    | Branch

…Files

- Fix getGlobBase to return '.' for single-part patterns like 'file.txt'
- Fix Windows path separator handling by using 'sep' instead of 'posix.sep' when splitting
- Export getGlobBase function for testing
- Add comprehensive unit tests for getGlobBase function
- Add vitest configuration for @trigger.dev/build package

Co-authored-by: Eric Allam <[email protected]>
@ericallam
Copy link
Member Author

@claude always run pnpm install and commit the pnpm lockfile when making any changes around deps

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 20s —— View job


Updating pnpm Lockfile

  • Run pnpm install to update lockfile
  • Commit and push the updated lockfile

⚠️ I'm unable to run pnpm install because the command requires approval. To enable this, you'll need to add pnpm to the --allowedTools configuration for the Claude Code Action.

Alternatively, you can run pnpm install locally and commit the updated lockfile.
| Branch

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: fix(build): add destination option to additionalFiles extension

Summary

This PR adds a destination option to the additionalFiles extension to address path resolution issues when using glob patterns with parent directory references (../). The current behavior strips .. segments, which can lead to unexpected file placement in monorepo setups.


✅ Code Quality and Best Practices

Strengths:

  • Well-documented: The JSDoc comments on the destination option are thorough with clear examples
  • Consistent type duplication: The AdditionalFilesOptions type is defined in both the internal module and the extension module, following the existing pattern in the codebase
  • Clean separation of concerns: The getGlobBase helper function is well-isolated and properly exported for testing
  • Documentation: The MDX docs clearly explain the problem and solution with good examples

Minor suggestions:

  1. Consider extracting the AdditionalFilesOptions type to a shared location to avoid duplication between src/extensions/core/additionalFiles.ts and src/internal/additionalFiles.ts. If one is updated without the other, it could lead to inconsistencies.

🐛 Potential Bugs or Issues

  1. Path separator inconsistency in getGlobBase: The function splits on both / and \\ but always joins with posix.sep (forward slash). While this works for the glob pattern parsing use case, it may produce unexpected results on Windows if the caller expects backslashes. This seems intentional for cross-platform glob support, but worth noting.

  2. Edge case with trailing slashes:

    getGlobBase("./")  // returns "."
    getGlobBase("../") // returns ".."

    These work correctly per the tests, but consider whether patterns like "./foo/" (directory with trailing slash but no glob) behave as expected - currently it would return "." because it pops the empty string after the trailing slash.

  3. No validation on destination: If a user provides an absolute path for destination, it would work but could place files outside the build directory unexpectedly. Consider adding validation or documentation warning.


⚡ Performance Considerations

The implementation looks efficient:

  • getGlobBase is called once per file only when destination is specified
  • The glob base calculation is O(n) where n is the number of path segments (typically small)
  • No unnecessary file system operations added

No performance concerns identified.


🔒 Security Concerns

  1. Path traversal consideration: The destination option accepts arbitrary strings. While this is used within the build process context, malicious or accidental values like ../../.. could potentially place files outside the intended build directory. The join() call with destinationPath should prevent escaping the build output directory, but this assumption depends on how destinationPath is constructed upstream.

    Recommendation: Consider adding validation that the final relativeDestinationPath is within the expected build output directory, or document that destination should be a relative path without parent directory references.


🧪 Test Coverage

Strengths:

  • Comprehensive unit tests for getGlobBase covering:
    • Various glob patterns with wildcards
    • Specific file paths without globs
    • Edge cases (empty string, Windows backslashes, mixed slashes, trailing slashes)
  • Good test organization with descriptive test names

Gaps:

  1. No integration tests: The tests only cover the getGlobBase helper function. Consider adding tests for findStaticAssetsForMatcher with the destination option to verify the full path resolution logic works correctly end-to-end.

  2. Missing test cases for getGlobBase:

    • Pattern with only wildcards: "**" or "***"
    • Negation patterns: "!foo/**" (if supported)
    • Very deep nesting: "a/b/c/d/e/f/g/**"
  3. Missing integration test scenarios:

    • Verify files are actually copied to correct destination with destination option
    • Test behavior when destination is an absolute path
    • Test behavior when destination contains .. segments

📝 Additional Notes

  1. Typo fix: Good catch fixing "effects" → "affects" in the documentation.

  2. vitest addition: The package.json changes to add vitest and test scripts are appropriate. The vitest.config.ts follows the pattern established in @trigger.dev/core.

  3. Semicolon missing: In vitest.config.ts line 8, there is no semicolon after the closing parenthesis. This is fine if the project uses a linter that allows it, but check project conventions.


Overall Assessment

This is a well-implemented feature that solves a real problem for monorepo users. The code is clean, well-documented, and includes appropriate tests for the new helper function.

Suggested improvements before merging:

  1. Add at least one integration test that verifies the full destination flow
  2. Consider adding validation/documentation for the destination parameter to prevent path traversal issues
  3. Consider extracting the shared type to avoid duplication

Verdict: 👍 Approve with minor suggestions

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: 1

🤖 Fix all issues with AI agents
In @packages/build/package.json:
- Around line 77-79: The lockfile is outdated after adding the vitest dependency
(scripts "test" and "test:dev" in package.json), causing pipeline failures; run
`pnpm install` in the repository to regenerate `pnpm-lock.yaml`, verify the
lockfile changes reflect the new vitest dependency, and commit the updated
`pnpm-lock.yaml` alongside the package.json changes so the CI pipeline passes.
🧹 Nitpick comments (1)
packages/build/test/additionalFiles.test.ts (1)

43-71: Consider adding a test for absolute paths.

The test suite doesn't appear to cover absolute paths (e.g., /absolute/path/** or C:\\absolute\\path\\**). While these may not be common in practice, they could occur and it would be good to verify the behavior.

Suggested test cases

Add these tests to the "edge cases" describe block:

+    it("handles absolute Unix-style paths with globs", () => {
+      expect(getGlobBase("/absolute/path/**")).toBe("/absolute/path");
+    });
+
+    it("handles absolute Unix-style file paths", () => {
+      expect(getGlobBase("/absolute/path/file.txt")).toBe("/absolute/path");
+    });
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55aba75 and 345722e.

📒 Files selected for processing (4)
  • packages/build/package.json
  • packages/build/src/internal/additionalFiles.ts
  • packages/build/test/additionalFiles.test.ts
  • packages/build/vitest.config.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
  • packages/build/test/additionalFiles.test.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
  • packages/build/test/additionalFiles.test.ts
**/*.{js,ts,jsx,tsx,json,md,css,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier

Files:

  • packages/build/vitest.config.ts
  • packages/build/package.json
  • packages/build/src/internal/additionalFiles.ts
  • packages/build/test/additionalFiles.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
  • packages/build/test/additionalFiles.test.ts
**/*.{test,spec}.{ts,tsx}

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

Use vitest for all tests in the Trigger.dev repository

Files:

  • packages/build/test/additionalFiles.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Avoid mocks or stubs in tests; use helpers from @internal/testcontainers when Redis or Postgres are needed
Use vitest for unit tests

Files:

  • packages/build/test/additionalFiles.test.ts
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build
📚 Learning: 2025-11-27T16:27:48.109Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:27:48.109Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest for unit tests

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/package.json
  • packages/build/test/additionalFiles.test.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/package.json
  • packages/build/test/additionalFiles.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.test.{ts,tsx} : Test files should only import classes and functions from `app/**/*.ts` files and should not import `env.server.ts` directly or indirectly; pass configuration through options instead

Applied to files:

  • packages/build/vitest.config.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure Trigger.dev project in `trigger.config.ts` using `defineConfig()` with project ref and task directories

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
📚 Learning: 2025-11-27T16:27:48.109Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:27:48.109Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Test files should live beside the files under test and use descriptive `describe` and `it` blocks

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/test/additionalFiles.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Specify task locations in trigger.config.ts using the `dirs` array, with automatic exclusion of .test and .spec files

Applied to files:

  • packages/build/vitest.config.ts
  • packages/build/src/internal/additionalFiles.ts
  • packages/build/test/additionalFiles.test.ts
📚 Learning: 2025-11-26T14:40:07.146Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2710
File: packages/schema-to-json/package.json:0-0
Timestamp: 2025-11-26T14:40:07.146Z
Learning: Node.js 24+ has native TypeScript support and can execute .ts files directly without tsx or ts-node for scripts that use only erasable TypeScript syntax (type annotations, interfaces, etc.). The trigger.dev repository uses Node.js 24.11.1+ and scripts like updateVersion.ts can be run with `node` instead of `tsx`.

Applied to files:

  • packages/build/package.json
📚 Learning: 2025-11-27T16:26:44.496Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/executing-commands.mdc:0-0
Timestamp: 2025-11-27T16:26:44.496Z
Learning: For running tests, navigate into the package directory and run `pnpm run test --run` to enable single-file test execution (e.g., `pnpm run test ./src/engine/tests/ttl.test.ts --run`)

Applied to files:

  • packages/build/package.json
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • packages/build/package.json
📚 Learning: 2025-11-27T16:27:48.109Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T16:27:48.109Z
Learning: pnpm version `10.23.0` and Node.js version `20.11.1` are required for development

Applied to files:

  • packages/build/package.json
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • packages/build/src/internal/additionalFiles.ts
🧬 Code graph analysis (3)
packages/build/vitest.config.ts (1)
packages/trigger-sdk/src/v3/config.ts (1)
  • defineConfig (11-13)
packages/build/src/internal/additionalFiles.ts (1)
packages/build/src/extensions/core/additionalFiles.ts (1)
  • AdditionalFilesOptions (4-25)
packages/build/test/additionalFiles.test.ts (1)
packages/build/src/internal/additionalFiles.ts (1)
  • getGlobBase (80-102)
🪛 GitHub Actions: 🤖 PR Checks
packages/build/package.json

[error] 1-1: pnpm install --frozen-lockfile failed: lockfile is outdated. specifiers in the lockfile don't match package.json (1 dependency added: vitest@^2.0.0). Run 'pnpm install' or 'pnpm install --no-frozen-lockfile' to update.

⏰ 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). (2)
  • GitHub Check: claude-review
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (8)
packages/build/package.json (1)

77-79: LGTM!

The test scripts follow standard Vitest conventions. vitest run executes tests once, while vitest runs in watch mode for development.

packages/build/vitest.config.ts (1)

1-8: LGTM!

The Vitest configuration is appropriate for this package. The include patterns cover both dedicated test directories and co-located tests, and enabling globals provides a convenient testing experience.

packages/build/test/additionalFiles.test.ts (1)

1-94: Excellent test coverage!

The test suite is well-organized with descriptive blocks and covers a comprehensive range of cases including:

  • Various glob wildcards (*, ?, [], {})
  • Relative and parent directory references
  • Specific file paths vs. glob patterns
  • Cross-platform path separators (Windows backslashes, mixed slashes)
  • Edge cases (empty string, single filename, dot files)

The tests properly validate that the function normalizes paths to POSIX separators, which is important for cross-platform compatibility.

packages/build/src/internal/additionalFiles.ts (5)

4-4: LGTM!

The additional imports (isAbsolute, resolve) are necessary for the destination-aware path resolution logic introduced in this PR.


9-27: Well-documented feature addition.

The documentation clearly explains the destination option's purpose and behavior, with a practical monorepo example. This addresses the core problem described in the PR objectives where parent directory references were being normalized incorrectly.


124-139: Correct platform-specific path handling.

The path calculation logic properly handles cross-platform differences:

  • Line 136 correctly uses platform-specific sep to split the output of relative(), which returns platform-specific separators
  • Line 138 normalizes to POSIX separators for consistency
  • Lines 124-131 handle the destination option by computing paths relative to the glob base

This approach ensures consistent behavior across Windows and Unix-like systems while preserving the original structure.


80-102: Consider edge case: patterns with only glob characters.

What happens if the pattern consists only of glob characters, like "*" or "**"? The function would:

  1. Split into ["*"] or ["**"]
  2. Immediately detect glob characters and break
  3. baseParts would be empty
  4. Return "."

This behavior seems correct (returns current directory), but it would be good to verify this is intentional and add test coverage if missing.

Looking at the test file, line 23 covers "*.js""." and line 19 covers "**/*.js"".", which validates this behavior. No action needed.


76-102: Verify the intended behavior for glob patterns with trailing slashes.

The getGlobBase function's handling of trailing slashes (e.g., "../shared/") produces "../shared", which occurs because empty strings resulting from trailing separators are also collected and then popped. Confirm whether this behavior is correct by reviewing test cases and actual usage patterns in the codebase, or add explicit test coverage for trailing slash scenarios if it's currently missing.

@ericallam
Copy link
Member Author

@claude can you try doing pnpm install again and committing the lock file

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 35s —— View job


Running pnpm install and Committing Lockfile

  • Run pnpm install to update lockfile
  • Commit and push the updated lockfile

⚠️ I'm unable to run pnpm install because pnpm is not installed in this environment and the commands needed to install it (corepack enable, corepack prepare) require approval.

To fix this, you have a couple of options:

  1. Add pnpm-related commands to your allowed tools - You can update the Claude Code Action --allowedTools configuration to include corepack and pnpm commands.

  2. Run locally - You can run pnpm install on your local machine and commit the updated pnpm-lock.yaml file.

View job | Branch

@ericallam
Copy link
Member Author

@claude try Running pnpm install and Committing Lockfile again

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 2m 7s —— View job


Running pnpm install and Committing Lockfile

  • Run pnpm install to update lockfile
  • Commit and push the updated lockfile

Successfully ran pnpm install and committed the updated pnpm-lock.yaml file. The lockfile now includes the vitest dependency added in packages/build/package.json.
| Branch

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: fix(build): add destination option to additionalFiles extension

Summary

This PR adds a destination option to the additionalFiles build extension, solving a real problem with monorepo setups where glob patterns with .. references get their parent directory segments stripped, leading to unexpected file placement.


✅ Strengths

  1. Well-documented: Excellent JSDoc comments and comprehensive MDX documentation with practical monorepo examples.

  2. Backward compatible: The destination option is optional, preserving existing behavior when not specified.

  3. Good test coverage: The getGlobBase function has thorough unit tests covering glob patterns, specific file paths, and edge cases including Windows-style paths.

  4. Clean implementation: The getGlobBase helper is well-designed with clear comments explaining its purpose.


🔍 Potential Issues & Suggestions

1. Type Definition Duplication

The AdditionalFilesOptions type with the destination property and its JSDoc is duplicated in two files:

  • packages/build/src/extensions/core/additionalFiles.ts (lines 4-25)
  • packages/build/src/internal/additionalFiles.ts (lines 7-28)

Suggestion: Consider exporting the type from one location and importing it in the other to maintain a single source of truth.

2. Missing Integration Tests

The tests only cover the getGlobBase helper function. There are no integration tests for findStaticAssetsForMatcher with the destination option to verify the full path resolution logic.

Suggestion: Add tests that exercise the complete flow, for example:

  • Verify that ../shared/utils.ts with destination: "libs" produces libs/utils.ts
  • Test multiple files with nested directories to ensure structure is preserved

3. Path Separator Handling on Windows

In getGlobBase, the function joins parts with posix.sep (line 101), which correctly normalizes to forward slashes. However, the tests for Windows-style backslashes (line 77-82) expect the output to use forward slashes:

it("handles Windows-style backslashes", () => {
  expect(getGlobBase("..\\shared\\**")).toBe("../shared");
});

This works correctly due to the posix.sep usage, but it's worth noting this intentional cross-platform normalization in a comment.

4. Edge Case: Empty Destination String

What happens if destination: "" is passed? The code would check if (options?.destination) which would be falsy for an empty string, falling back to default behavior. This might be the intended behavior, but it's worth documenting or validating.

5. Potential Path Traversal Consideration

The destination option accepts arbitrary strings. While this is a build-time configuration (not user input), it might be worth considering whether paths like ../../../ in the destination could write files outside the build directory.

// This could potentially write outside the build directory
additionalFiles({
  files: ["./config.json"],
  destination: "../../../../etc"
})

Suggestion: Consider adding validation or a note in the documentation that the destination should be a relative path within the build output.


📝 Minor Suggestions

  1. Vitest config consistency: The new vitest.config.ts matches the pattern in packages/core/vitest.config.ts - good consistency.

  2. Typo fix: Good catch on changing "effects" to "affects" in the documentation.


Overall Assessment

This is a well-implemented feature that solves a genuine pain point for monorepo users. The code is clean, well-documented, and maintains backward compatibility. The main suggestions are around adding integration tests and considering the type duplication.

Recommendation: ✅ Approve with minor suggestions

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: fix(build): add destination option to additionalFiles extension

Summary

This PR adds an optional destination parameter to the additionalFiles extension to control where matched files are placed when using glob patterns with parent directory references (..). This is a useful enhancement for monorepo setups.


✅ Code Quality and Best Practices

Strengths:

  • Clean implementation with clear separation of concerns
  • Good JSDoc documentation on the destination option with practical examples
  • The getGlobBase helper function is well-documented and handles various edge cases
  • Documentation is thorough and provides good examples for users

Minor Suggestions:

  • The AdditionalFilesOptions type is duplicated in both packages/build/src/extensions/core/additionalFiles.ts and packages/build/src/internal/additionalFiles.ts. Consider exporting the type from one location and importing in the other to maintain DRY principles.

🐛 Potential Bugs or Issues

  1. Path traversal concern: When destination is specified, there's no validation to prevent users from accidentally specifying paths that could escape the destination directory (e.g., destination: "../../../etc"). While this is user-controlled configuration, a warning or validation could prevent misconfiguration.

  2. Trailing slash handling in getGlobBase: The function handles "../" and "./" patterns, but the behavior might be unexpected when a trailing slash is present:

    • getGlobBase("../") returns ".." (after popping empty string)
    • This works because splitting "../" gives [".", ".", ""] - wait, actually it should give ["..", ""]. Let me verify: splitting by / gives ["..", ""], and since no glob chars, it pops to get [".."], returning "..". This looks correct.
  3. Cross-platform path handling: The code uses both posix.sep and sep (platform-specific). The logic in getGlobBase joins with posix.sep, which is consistent. However, in findStaticAssetsForMatcher, when destination is set, join() is used (which is platform-specific), but in the default path, posix.sep is used for joining. This inconsistency could lead to different behavior on Windows vs Unix:

    // With destination - uses platform join()
    pathInsideDestinationDir = join(options.destination, relativeToGlobBase);
    
    // Without destination - uses posix.sep
    .join(posix.sep);

⚡ Performance Considerations

  • The implementation is efficient - getGlobBase is called once per matcher with destination specified
  • No performance concerns with this approach

🔒 Security Concerns

  • Low risk: The destination option only controls the output path within the build directory. Since this is configuration provided by the developer in trigger.config.ts, the risk is minimal.
  • Consider documenting that the destination path should be relative and within the project structure.

🧪 Test Coverage

Strengths:

  • Excellent unit test coverage for getGlobBase function
  • Tests cover various glob patterns, specific file paths, and edge cases
  • Good handling of Windows-style paths in tests

Areas for Improvement:

  1. Missing integration tests: The tests only cover the getGlobBase helper. Consider adding tests for findStaticAssetsForMatcher with the destination option to verify end-to-end behavior.

  2. Missing tests for the actual file copying with destination: A test that verifies files are actually copied to the correct destination path would increase confidence.

  3. Edge cases not tested:

    • What happens when destination is an empty string?
    • What happens when destination is an absolute path?
    • What happens with multiple files matching the same glob pattern?

📝 Documentation

  • The docs update is clear and provides helpful examples
  • Good fix for the typo: "effects" → "affects"
  • The monorepo example is practical and demonstrates the problem well

Overall Assessment

This is a well-implemented feature that solves a real problem for monorepo users. The code is clean, well-documented, and the test coverage for the helper function is thorough.

Recommendations before merging:

  1. Consider adding validation or a warning for potentially problematic destination values
  2. Ensure consistent path separator usage across platforms (minor)
  3. Consider adding an integration test for the full flow with destination option

Good work! 👍

@ericallam
Copy link
Member Author

@claude our PR checks unit tests merge reports is failing with the following:

Run pnpm dlx [email protected] run --merge-reports --pass-with-no-tests
Progress: resolved 1, reused 0, downloaded 0, added 0
Packages: +48
++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 97, reused 0, downloaded 48, added 48, done

⎯⎯⎯⎯⎯⎯ Unhandled Error ⎯⎯⎯⎯⎯⎯⎯
Error: vitest.mergeReports() requires all blob files to be generated by the same Vitest version, received

  • "packages_build-blob-1-1.json" uses v2.1.9
  • "packages_core-blob-1-1.json" uses v3.1.4
  • "packages_redis-worker-blob-1-1.json" uses v3.1.4
  • "packages_schema-to-json-blob-1-1.json" uses v3.1.4
  • "packages_trigger-sdk-blob-1-1.json" uses v3.1.4

make sure you are using the correct version of vitest in the build package please

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 1m 34s —— View job


Fixing Vitest Version Mismatch

  • Investigate the vitest version issue in packages/build
  • Update vitest to v3.1.4 to match other packages
  • Update pnpm-lock.yaml
  • Commit and push changes

Issue found: packages/build/package.json had "vitest": "^2.0.0" while the root package.json has "vitest": "3.1.4". This caused the merge-reports step to fail since the blobs were generated with different vitest versions.

Fix applied: Updated packages/build/package.json to use "vitest": "3.1.4" (exact version match with the root) and regenerated the lockfile.
| Branch

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: Add destination option to additionalFiles extension

Summary

This PR adds an optional destination parameter to the additionalFiles build extension, addressing a real pain point in monorepo setups where .. segments in glob patterns get stripped during file copying.


Code Quality and Best Practices ✅

Strengths:

  • Clean, well-documented code with comprehensive JSDoc comments
  • The getGlobBase() function is well-isolated and testable
  • Good separation of concerns between the public API and internal implementation
  • Consistent coding style throughout
  • Type definitions are duplicated appropriately between the public API (extensions/core/additionalFiles.ts) and internal implementation

Minor Suggestions:

  • The type AdditionalFilesOptions is defined in two places (src/extensions/core/additionalFiles.ts and src/internal/additionalFiles.ts). Consider exporting from one location and importing in the other to maintain a single source of truth, or add a comment explaining the intentional duplication.

Potential Bugs or Issues ⚠️

  1. Path separator inconsistency (line 101 in additionalFiles.ts): The getGlobBase() function always joins with posix.sep (forward slash), even when handling Windows-style backslashes in input. This is fine for glob patterns (which typically use forward slashes), but worth verifying that resolve() on Windows handles this correctly when passed to resolve(cwd, globBase) at line 127.

  2. Edge case with trailing slashes: The tests cover "./" and "../" but the behavior when a user passes a destination with a trailing slash (e.g., destination: "apps/shared/") isn't explicitly tested. While join() should handle this, it would be good to add a test case for confidence.

  3. Absolute paths in destination: What happens if a user passes an absolute path as destination? The current implementation would create files outside the build directory. Consider validating that destination is a relative path, or documenting this behavior explicitly.


Performance Considerations ✅

  • The getGlobBase() function is called once per matcher, not per file, so there's no performance concern there
  • File operations are sequential within each matcher, which is appropriate for avoiding file system contention
  • No unnecessary allocations or operations introduced

Security Concerns ⚠️

  1. Path traversal: If destination accepts user input (unlikely in this context but worth considering), a malicious value like ../../../etc could write files outside the intended directory. Since this is a build configuration that developers control, this is low risk, but you might want to add a note in the documentation that destination should be a relative path within the build output.

  2. The existing behavior of stripping .. segments actually served as an implicit security measure - worth noting that the new destination option bypasses this, putting more responsibility on the user to specify safe paths.


Test Coverage ✅

Excellent test coverage for getGlobBase():

  • 21 test cases covering glob patterns, specific files, and edge cases
  • Good coverage of Windows-style paths and mixed separators
  • Edge cases like empty strings handled

Missing test coverage:

  1. No integration tests for findStaticAssetsForMatcher() with the new destination option
  2. No tests verifying the end-to-end behavior of addAdditionalFilesToBuild() with different destination values
  3. Consider adding tests for:
    • Multiple files matching a single glob with destination
    • Nested directory structures preserved correctly
    • Error cases (e.g., invalid patterns)

Documentation ✅

The documentation updates are thorough and well-written:

  • Clear explanation of the problem being solved
  • Practical monorepo example structure
  • Multiple usage examples including combining multiple additionalFiles extensions
  • Appropriate use of <Note> components for important details

Small typo fix included: "effects" → "affects" (good catch!)


Overall Assessment

This is a well-implemented feature that solves a real problem. The code is clean, well-documented, and includes good test coverage for the core logic.

Recommendations before merging:

  1. Consider adding an integration test for the destination option
  2. Optionally add validation/documentation for absolute paths in destination
  3. Consider consolidating the AdditionalFilesOptions type definition

Verdict: Approve with minor suggestions 👍

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: fix(build): add destination option to additionalFiles extension

Overall, this is a well-implemented feature that solves a real problem for monorepo users. The code is clean, well-documented, and includes good test coverage. Here's my detailed review:

Code Quality and Best Practices

Strengths:

  • Excellent JSDoc documentation on both the public API (additionalFiles.ts) and internal implementation (internal/additionalFiles.ts)
  • The getGlobBase function is well-isolated and pure, making it easy to test
  • Good separation of concerns - the glob base extraction logic is cleanly separated from the file path computation
  • Documentation is thorough and includes practical monorepo examples

Minor suggestions:

  1. The AdditionalFilesOptions type is duplicated in both src/extensions/core/additionalFiles.ts and src/internal/additionalFiles.ts with identical JSDoc. Consider exporting from one location and re-exporting from the other to avoid potential drift.

  2. In getGlobBase, the function uses posix.sep for joining but splits on both / and \\. This is intentional cross-platform handling, but a brief comment explaining why would help future maintainers.

Potential Bugs or Issues

  1. Path traversal concern (line 131): When using join(options.destination, relativeToGlobBase), if relativeToGlobBase somehow contains .. segments (which could happen if the glob matches files outside the expected base), files could be written outside the intended destination. Consider validating that the final path is actually under the destination directory, or documenting this as expected behavior.

  2. Empty destination string: If a user passes destination: "", the current code will use the empty string in join(), which should work but may produce unexpected results. Consider either documenting this or adding validation.

  3. Absolute paths in destination: The code doesn't explicitly handle if someone passes an absolute path for destination. The behavior would likely be platform-dependent via join(). This edge case should either be documented or validated.

Performance Considerations

The implementation looks performant:

  • The getGlobBase function is O(n) where n is the number of path segments - efficient
  • No unnecessary iterations or allocations
  • The glob operation is already the bottleneck, and no additional file system operations were added

Security Concerns

  1. Path traversal: As mentioned above, there's a potential for path traversal if the glob matches unexpected files. In a build tool context, this is likely acceptable since the user controls the configuration, but it's worth being aware of.

  2. No new external dependencies for the core logic - vitest is dev-only, which is good.

Test Coverage

Strengths:

  • Comprehensive test suite for getGlobBase covering:
    • Various glob patterns (**, *, ?, brackets, braces)
    • Specific file paths without globs
    • Edge cases (empty strings, Windows paths, mixed separators)
  • Tests are well-organized with descriptive names

Suggestions for improvement:

  1. Integration tests: Consider adding integration tests that actually exercise findStaticAssetsForMatcher with the destination option. The current tests only cover the getGlobBase helper function.

  2. Test the main code path: Tests for scenarios like:

    • destination with a glob pattern that has .. segments
    • destination with an absolute glob base
    • Multiple files matched with a destination
    • Edge case: destination with trailing slash vs without
  3. vitest.config.ts: The config uses globals: true. Consider whether this is consistent with other test configurations in the monorepo.

Documentation

The documentation changes are excellent:

  • Clear explanation of the problem with monorepos
  • Practical examples with directory structures
  • Shows both single and multiple destination configurations
  • Good use of <Note> components to highlight important behavior

Summary

This is a solid PR that addresses a real pain point. The implementation is clean and well-tested for the core utility function. My main recommendations are:

  1. Consider adding integration tests for the destination option path
  2. Document or validate edge cases around empty/absolute destination paths
  3. Consider consolidating the duplicated AdditionalFilesOptions type definition

Verdict: Approve with minor suggestions - The code is production-ready, and the suggestions above are nice-to-haves rather than blockers.

@ericallam
Copy link
Member Author

@claude can you add a patch changeset for this please

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ericallam's task in 1m 12s —— View job


Adding Patch Changeset

  • Find existing changeset file for this PR
  • Create patch changeset for @trigger.dev/build
  • Commit and push changes

Added changeset file .changeset/polite-lobsters-double.md with a patch bump for @trigger.dev/build.
| Branch

@claude
Copy link

claude bot commented Jan 9, 2026

PR Review: Add destination option to additionalFiles extension

Summary

This PR adds a destination option to the additionalFiles build extension, solving a real problem where glob patterns with parent directory references (../) would have their .. segments stripped, causing unexpected file placement. This is particularly useful for monorepo setups.


✅ Strengths

1. Well-designed solution
The destination option elegantly solves the problem while maintaining backward compatibility. The default behavior (stripping .. segments) is preserved when destination is not specified.

2. Excellent documentation
The docs (additionalFiles.mdx) are comprehensive with clear examples showing:

  • The problem with default behavior
  • How to use the destination option
  • How to handle multiple directories with different destinations

3. Good test coverage
The 24 unit tests for getGlobBase cover:

  • Various glob patterns with wildcards
  • Specific file paths without globs
  • Edge cases including empty strings and Windows-style backslashes

4. Clean implementation
The getGlobBase function is well-documented and handles edge cases appropriately (glob vs specific file patterns, cross-platform path separators).


⚠️ Issues & Suggestions

1. Duplicate type definitions (Code Quality)

AdditionalFilesOptions is defined in two places:

  • packages/build/src/internal/additionalFiles.ts:7
  • packages/build/src/extensions/core/additionalFiles.ts:4

While both currently have the same properties, this duplication could lead to inconsistencies if one is updated but not the other. Consider:

  • Having the public extension import and re-export the internal type, OR
  • Creating a shared types file
// Option A: In extensions/core/additionalFiles.ts
export type { AdditionalFilesOptions } from "../../internal/additionalFiles.js";

2. Missing integration tests

The tests only cover the getGlobBase utility function. Consider adding integration tests for findStaticAssetsForMatcher that verify the actual file copying behavior with the destination option. This would ensure:

  • Files are copied to the correct destination paths
  • The relative structure is preserved as documented
  • Edge cases like nested directories work correctly

3. Potential path traversal concern (Security)

When destination is specified, there's no validation that the resulting path stays within the expected output directory. While this is a build-time operation (not runtime) and users control the config, consider adding a check:

// In findStaticAssetsForMatcher after computing pathInsideDestinationDir
const normalizedPath = posix.normalize(pathInsideDestinationDir);
if (normalizedPath.startsWith('..')) {
  throw new Error(`Destination path escapes build directory: ${pathInsideDestinationDir}`);
}

4. Minor: vitest globals configuration

In vitest.config.ts, you have globals: true, but the test file explicitly imports { describe, it, expect } from vitest. Either:

  • Remove the imports in test files to use globals, OR
  • Remove globals: true from the config since you're using explicit imports

5. Consider documenting the empty result warning behavior

When no files match a pattern, a warning is logged. This is good, but users might want to know about this behavior in the docs, especially for patterns that legitimately match zero files in some environments.


🔍 Code Quality Notes

Path handling is correct:

  • Uses platform-specific sep for splitting relative() output
  • Uses posix.sep for joining output paths (ensuring consistent forward slashes)
  • Handles both forward slashes and backslashes in glob patterns

The changeset is appropriate:

  • Patch version bump is correct for this backward-compatible addition
  • Description is clear and concise

Summary

This is a well-implemented feature that solves a real problem for monorepo users. The main suggestions are:

  1. Consider deduplicating the type definitions
  2. Add integration tests for the destination option
  3. Optionally add path traversal validation

The PR is in good shape and ready to merge after addressing the duplicate type definitions.

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.

3 participants