Repository: triggerdotdev/trigger.dev
Pull request #2083: Add experimental externals detection
Author: login: nicktrn
Created: 2025-05-21T10:13:55Z | Merged: 2025-05-21T15:40:37Z

If your tasks fail to build you can try the new `build.autoDetectExternal` trigger config option. It should NOT be used otherwise as it may break deploys, we're still testing this.


Diff:
diff --git a/.changeset/grumpy-wasps-fold.md b/.changeset/grumpy-wasps-fold.md
new file mode 100644
index 00000000000..78b74f73b74
--- /dev/null
+++ b/.changeset/grumpy-wasps-fold.md
@@ -0,0 +1,6 @@
+---
+"trigger.dev": patch
+"@trigger.dev/core": patch
+---
+
+Add `experimental_autoDetectExternal` trigger config option
diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json
index 98e95700493..9dcf7af298f 100644
--- a/packages/cli-v3/package.json
+++ b/packages/cli-v3/package.json
@@ -96,6 +96,7 @@
     "@trigger.dev/build": "workspace:4.0.0-v4-beta.16",
     "@trigger.dev/core": "workspace:4.0.0-v4-beta.16",
     "ansi-escapes": "^7.0.0",
+    "braces": "^3.0.3",
     "c12": "^1.11.1",
     "chalk": "^5.2.0",
     "chokidar": "^3.6.0",
diff --git a/packages/cli-v3/src/build/braces.d.ts b/packages/cli-v3/src/build/braces.d.ts
new file mode 100644
index 00000000000..413f4775f94
--- /dev/null
+++ b/packages/cli-v3/src/build/braces.d.ts
@@ -0,0 +1,126 @@
+/*
+    MIT License
+
+    Copyright (c) Microsoft Corporation.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE
+ */
+
+type Transform = (str: string) => string;
+
+interface Options {
+  /**
+   * Limit the length of the input string. Useful when the input string is generated or your application allows
+   * users to pass a string, et cetera.
+   *
+   * @default 65536
+   * @example
+   * console.log(braces('a/{b,c}/d', { maxLength: 3 }));
+   * //=> throws an error
+   */
+  maxLength?: number | undefined;
+  /**
+   * Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method).
+   *
+   * @default undefined
+   * @example
+   * console.log(braces('a/{b,c}/d', { expand: true }));
+   * //=> [ 'a/b/d', 'a/c/d' ]
+   */
+  expand?: boolean | undefined;
+  /**
+   * Remove duplicates from the returned array.
+   *
+   * @default undefined
+   */
+  nodupes?: boolean | undefined;
+  /**
+   * To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()`
+   * is used or `options.expand` is true and the generated range will exceed the `rangeLimit`.
+   *
+   * You can customize `options.rangeLimit` or set it to `Infinity` to disable this altogether.
+   *
+   * @default 1000
+   * @example
+   * // pattern exceeds the "rangeLimit", so it's optimized automatically
+   * console.log(braces.expand('{1..1000}'));
+   * //=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
+   *
+   * // pattern does not exceed "rangeLimit", so it's NOT optimized
+   * console.log(braces.expand('{1..100}'));
+   * //=> ['1', '2', '3', '4', '5', …, '100']
+   */
+  rangeLimit?: number | undefined;
+  /**
+   * Customize range expansion.
+   *
+   * @default undefined
+   * @example
+   * const range = braces.expand('x{a..e}y', {
+   *   transform: (str) => `foo/${str}`
+   * });
+   *
+   * console.log(range);
+   * //=> [ 'xfooay', 'xfooby', 'xfoocy', 'xfoody', 'xfooey' ]
+   */
+  transform?: Transform | undefined;
+  /**
+   * In regular expressions, quanitifiers can be used to specify how many times a token can be repeated.
+   * For example, `a{1,3}` will match the letter `a` one to three times.
+   *
+   * Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#lists)
+   *
+   * The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers)
+   * are defined in the given pattern, and not to try to expand them as lists.
+   *
+   * @default undefined
+   * @example
+   * const braces = require('braces');
+   * console.log(braces('a/b{1,3}/{x,y,z}'));
+   * //=> [ 'a/b(1|3)/(x|y|z)' ]
+   * console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true}));
+   * //=> [ 'a/b{1,3}/(x|y|z)' ]
+   * console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true, expand: true}));
+   * //=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ]
+   */
+  quantifiers?: boolean | undefined;
+  /**
+   * Do not strip backslashes that were used for escaping from the result.
+   *
+   * @default undefined
+   */
+  keepEscaping?: boolean | undefined;
+  /**
+   * Do not strip quotes from the result.
+   *
+   * @default undefined
+   */
+  keepQuotes?: boolean | undefined;
+}
+
+// Ambient type override for braces to allow string or string[] as pattern
+declare module "braces" {
+  function braces(pattern: string | string[], options?: Options): string[];
+
+  namespace braces {
+    function expand(pattern: string | string[], options?: Omit<Options, "expand">): string[];
+  }
+
+  export default braces;
+}
diff --git a/packages/cli-v3/src/build/externals.ts b/packages/cli-v3/src/build/externals.ts
index 9d9554a91cd..90a2e075144 100644
--- a/packages/cli-v3/src/build/externals.ts
+++ b/packages/cli-v3/src/build/externals.ts
@@ -1,6 +1,6 @@
 import * as esbuild from "esbuild";
 import { makeRe } from "minimatch";
-import { mkdir, symlink } from "node:fs/promises";
+import { access, mkdir, symlink } from "node:fs/promises";
 import { dirname, join } from "node:path";
 import { readPackageJSON, resolvePackageJSON } from "pkg-types";
 import nodeResolve from "resolve";
@@ -14,6 +14,10 @@ import {
 import { logger } from "../utilities/logger.js";
 import { CliApiClient } from "../apiClient.js";
 import { resolvePathSync as esmResolveSync } from "mlly";
+import braces from "braces";
+import { builtinModules } from "node:module";
+import { tryCatch } from "@trigger.dev/core/v3";
+import { resolveModule } from "./resolveModule.js";
 
 /**
  * externals in dev might not be resolvable from the worker directory
@@ -140,6 +144,11 @@ function createExternalsCollector(
 
   const maybeExternals = discoverMaybeExternals(target, resolvedConfig, forcedExternal);
 
+  // Cache: resolvedPath (dir) -> packageJsonPath (null = failed to resolve)
+  const packageJsonCache = new Map<string, string | null>();
+  // Cache: packageRoot (dir) -> boolean (true = mark as external)
+  const isExternalCache = new Map<string, boolean>();
+
   return {
     externals,
     plugin: {
@@ -147,10 +156,17 @@ function createExternalsCollector(
       setup: (build) => {
         build.onStart(async () => {
           externals.splice(0);
+          isExternalCache.clear();
         });
 
         build.onEnd(async () => {
-          logger.debug("[externals][onEnd] Collected externals", { externals });
+          logger.debug("[externals][onEnd] Collected externals", {
+            externals,
+            maybeExternals,
+            autoDetectExternal: !!resolvedConfig.build?.experimental_autoDetectExternal,
+            packageJsonCache: packageJsonCache.size,
+            isExternalCache: isExternalCache.size,
+          });
         });
 
         maybeExternals.forEach((external) => {
@@ -248,6 +264,146 @@ function createExternalsCollector(
             }
           });
         });
+
+        if (resolvedConfig.build?.experimental_autoDetectExternal) {
+          build.onResolve(
+            { filter: /.*/, namespace: "file" },
+            async (args: esbuild.OnResolveArgs): Promise<esbuild.OnResolveResult | undefined> => {
+              if (!isBareModuleImport(args.path)) {
+                // Not an npm package
+                return;
+              }
+
+              if (isBuiltinModule(args.path)) {
+                // Builtin module
+                return;
+              }
+
+              if (args.path === "_sentry-debug-id-injection-stub") {
+                // Ignore sentry stub
+                return;
+              }
+
+              // Try to resolve the actual file path
+              const [resolveError, resolvedPath] = await tryCatch(
+                resolveModule(args.path, args.resolveDir)
+              );
+
+              if (resolveError) {
+                logger.debug("[externals][auto] Resolve module error", {
+                  path: args.path,
+                  resolveError,
+                });
+                return;
+              }
+
+              // Find nearest package.json
+              const packageJsonPath = await findNearestPackageJson(resolvedPath, packageJsonCache);
+
+              if (!packageJsonPath) {
+                logger.debug("[externals][auto] Failed to resolve package.json path", {
+                  path: args.path,
+                  resolvedPath,
+                });
+                return;
+              }
+
+              const packageRoot = dirname(packageJsonPath);
+
+              // Check cache first
+              if (isExternalCache.has(packageRoot)) {
+                const isExternal = isExternalCache.get(packageRoot);
+
+                if (isExternal) {
+                  return { path: args.path, external: true };
+                }
+
+                return;
+              }
+
+              const [readError, packageJson] = await tryCatch(readPackageJSON(packageRoot));
+
+              if (readError) {
+                logger.debug("[externals][auto] Unable to read package.json", {
+                  error: readError,
+                  packageRoot,
+                });
+
+                isExternalCache.set(packageRoot, false);
+                return;
+              }
+
+              const packageName = packageJson.name;
+              const packageVersion = packageJson.version;
+
+              if (!packageName || !packageVersion) {
+                logger.debug("[externals][auto] No package name or version found in package.json", {
+                  packageRoot,
+                  packageJson,
+                });
+
+                return;
+              }
+
+              const markExternal = (reason: string): esbuild.OnResolveResult => {
+                const detectedPackage = {
+                  name: packageName,
+                  path: packageRoot,
+                  version: packageVersion,
+                } satisfies CollectedExternal;
+
+                logger.debug(`[externals][auto] Marking as external - ${reason}`, {
+                  detectedPackage,
+                });
+
+                externals.push(detectedPackage);
+
+                // Cache the result
+                isExternalCache.set(packageRoot, true);
+
+                return { path: args.path, external: true };
+              };
+
+              // If the path ends with .wasm or .node, we should mark it as external
+              if (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node")) {
+                return markExternal("path ends with .wasm or .node");
+              }
+
+              // Check files, main, module fields for native files
+              const files = Array.isArray(packageJson.files) ? packageJson.files : [];
+              const fields = [packageJson.main, packageJson.module, packageJson.browser].filter(
+                (f): f is string => typeof f === "string"
+              );
+              const allFiles = files.concat(fields);
+
+              // We need to expand any braces in the files array, e.g. ["{js,ts}"] -> ["js", "ts"]
+              const allFilesExpanded = braces(allFiles, { expand: true });
+
+              // Use a regexp to match native-related extensions
+              const nativeExtRegexp = /\.(wasm|node|gyp|c|cc|cpp|cxx|h|hpp|hxx)$/;
+              const hasNativeFile = allFilesExpanded.some((file) => nativeExtRegexp.test(file));
+
+              if (hasNativeFile) {
+                return markExternal("has native file");
+              }
+
+              // Check if binding.gyp exists (native addon)
+              const bindingGypPath = join(packageRoot, "binding.gyp");
+
+              // If access succeeds, binding.gyp exists
+              const [accessError] = await tryCatch(access(bindingGypPath));
+
+              if (!accessError) {
+                return markExternal("binding.gyp exists");
+              }
+
+              // Cache the negative result
+              isExternalCache.set(packageRoot, false);
+
+              return undefined;
+            }
+          );
+        }
       },
     },
   };
@@ -410,3 +566,87 @@ function resolveSync(id: string, resolveDir: string) {
     return esmResolveSync(id, { url: resolveDir });
   }
 }
+
+function isBareModuleImport(path: string): boolean {
+  const excludes = [".", "/", "~", "file:", "data:"];
+  return !excludes.some((exclude) => path.startsWith(exclude));
+}
+
+function isBuiltinModule(path: string): boolean {
+  return builtinModules.includes(path.replace("node:", ""));
+}
+
+async function isMainPackageJson(filePath: string): Promise<boolean> {
+  try {
+    const packageJson = await readPackageJSON(filePath);
+
+    // Allowlist of non-informative fields that can appear with 'type: module | commonjs' in marker package.json files
+    const markerFields = new Set([
+      "type",
+      "sideEffects",
+      "browser",
+      "main",
+      "module",
+      "react-native",
+      "name",
+    ]);
+
+    if (!packageJson.type) {
+      return true;
+    }
+
+    const keys = Object.keys(packageJson);
+    if (keys.every((k) => markerFields.has(k))) {
+      return false; // type marker
+    }
+
+    return true;
+  } catch (error) {
+    if (!(error instanceof Error)) {
+      logger.debug("[externals][containsEsmTypeMarkers] Unknown error", {
+        error,
+      });
+
+      return false;
+    }
+
+    if ("code" in error && error.code !== "ENOENT") {
+      logger.debug("[externals][containsEsmTypeMarkers] Error", {
+        error: error.message,
+      });
+    }
+
+    return false;
+  }
+}
+
+async function findNearestPackageJson(
+  basePath: string,
+  cache: Map<string, string | null>
+): Promise<string | null> {
+  const baseDir = dirname(basePath);
+
+  if (cache.has(baseDir)) {
+    const resolvedPath = cache.get(baseDir);
+
+    if (!resolvedPath) {
+      return null;
+    }
+
+    return resolvedPath;
+  }
+
+  const [error, packageJsonPath] = await tryCatch(
+    resolvePackageJSON(dirname(basePath), {
+      test: isMainPackageJson,
+    })
+  );
+
+  if (error) {
+    cache.set(baseDir, null);
+    return null;
+  }
+
+  cache.set(baseDir, packageJsonPath);
+  return packageJsonPath;
+}
diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts
index 416404df3fb..5355534a22a 100644
--- a/packages/cli-v3/src/commands/deploy.ts
+++ b/packages/cli-v3/src/commands/deploy.ts
@@ -1,5 +1,5 @@
 import { intro, log, outro } from "@clack/prompts";
-import { prepareDeploymentError } from "@trigger.dev/core/v3";
+import { prepareDeploymentError, tryCatch } from "@trigger.dev/core/v3";
 import { InitializeDeploymentResponseBody } from "@trigger.dev/core/v3/schemas";
 import { Command, Option as CommandOption } from "commander";
 import { resolve } from "node:path";
@@ -238,25 +238,32 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
 
   const { features } = resolvedConfig;
 
-  const buildManifest = await buildWorker({
-    target: "deploy",
-    environment: options.env,
-    destination: destination.path,
-    resolvedConfig,
-    rewritePaths: true,
-    envVars: serverEnvVars.success ? serverEnvVars.data.variables : {},
-    forcedExternals,
-    listener: {
-      onBundleStart() {
-        $buildSpinner.start("Building trigger code");
+  const [error, buildManifest] = await tryCatch(
+    buildWorker({
+      target: "deploy",
+      environment: options.env,
+      destination: destination.path,
+      resolvedConfig,
+      rewritePaths: true,
+      envVars: serverEnvVars.success ? serverEnvVars.data.variables : {},
+      forcedExternals,
+      listener: {
+        onBundleStart() {
+          $buildSpinner.start("Building trigger code");
+        },
+        onBundleComplete(result) {
+          $buildSpinner.stop("Successfully built code");
+
+          logger.debug("Bundle result", result);
+        },
       },
-      onBundleComplete(result) {
-        $buildSpinner.stop("Successfully built code");
+    })
+  );
 
-        logger.debug("Bundle result", result);
-      },
-    },
-  });
+  if (error) {
+    $buildSpinner.stop("Failed to build code");
+    throw error;
+  }
 
   logger.debug("Successfully built project to", destination.path);
 
diff --git a/packages/core/src/v3/config.ts b/packages/core/src/v3/config.ts
index a98b8f03d55..f4bcb677c07 100644
--- a/packages/core/src/v3/config.ts
+++ b/packages/core/src/v3/config.ts
@@ -170,6 +170,19 @@ export type TriggerConfig = {
      */
     external?: string[];
 
+    /**
+     * **WARNING: This is an experimental feature and might be removed in a future version.**
+     *
+     * Automatically detect dependencies that shouldn't be bundled and mark them as external. For example, native modules.
+     *
+     * Turning this on will not affect dependencies that were manually added to the `external` array.
+     *
+     * @default false
+     *
+     * @deprecated (experimental)
+     */
+    experimental_autoDetectExternal?: boolean;
+
     jsx?: {
       /**
        * @default "React.createElement"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 04ccc99c1ab..b783af32b1b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1232,6 +1232,9 @@ importers:
       ansi-escapes:
         specifier: ^7.0.0
         version: 7.0.0
+      braces:
+        specifier: ^3.0.3
+        version: 3.0.3
       c12:
         specifier: ^1.11.1
         version: 1.11.1(magicast@0.3.4)
@@ -2179,11 +2182,14 @@ importers:
 
   references/v3-catalog:
     dependencies:
+      '@1password/sdk':
+        specifier: ^0.3.0
+        version: 0.3.0
       '@effect/schema':
         specifier: ^0.75.5
         version: 0.75.5(effect@3.14.14)
       '@infisical/sdk':
-        specifier: ^2.1.9
+        specifier: ^2.3.5
         version: 2.3.5
       '@opentelemetry/api':
         specifier: 1.4.1
@@ -2227,6 +2233,12 @@ importers:
       arktype:
         specifier: 2.0.0-rc.17
         version: 2.0.0-rc.17
+      bcrypt:
+        specifier: ^6.0.0
+        version: 6.0.0
+      canvas:
+        specifier: ^3.1.0
+        version: 3.1.0
       dotenv:
         specifier: ^16.4.5
         version: 16.4.5
@@ -2248,6 +2260,9 @@ importers:
       msw:
         specifier: ^2.2.1
         version: 2.3.5(typescript@5.5.4)
+      mupdf:
+        specifier: ^1.3.6
+        version: 1.3.6
       openai:
         specifier: ^4.47.0
         version: 4.56.0(zod@3.23.8)
@@ -2275,6 +2290,12 @@ importers:
       server-only:
         specifier: ^0.0.1
         version: 0.0.1
+      sharp:
+        specifier: ^0.34.2
+        version: 0.34.2
+      sqlite3:
+        specifier: ^5.1.7
+        version: 5.1.7
       stripe:
         specifier: ^12.14.0
         version: 12.18.0
@@ -2283,7 +2304,7 @@ importers:
         version: 2.0.2
       typeorm:
         specifier: ^0.3.20
-        version: 0.3.20(pg@8.11.5)(ts-node@10.9.2)
+        version: 0.3.20(pg@8.11.5)(sqlite3@5.1.7)(ts-node@10.9.2)
       valibot:
         specifier: ^0.42.1
         version: 0.42.1(typescript@5.5.4)
@@ -2296,6 +2317,9 @@ importers:
       yup:
         specifier: ^1.4.0
         version: 1.4.0
+      zip-node-addon:
+        specifier: ^0.0.11
+        version: 0.0.11
       zod:
         specifier: 3.23.8
         version: 3.23.8
@@ -2348,6 +2372,9 @@ importers:
       '@trigger.dev/python':
         specifier: workspace:*
         version: link:../../packages/python
+      '@types/bcrypt':
+        specifier: ^5.0.2
+        version: 5.0.2
       '@types/email-reply-parser':
         specifier: ^1.4.2
         version: 1.4.2
@@ -2378,6 +2405,16 @@ importers:
 
 packages:
 
+  /@1password/sdk-core@0.3.0:
+    resolution: {integrity: sha512-2ZBshSguEe8TSwJrW7Ur1HMZhfE5UxV6dCzxzGfIHd1VO2ZbzadOFqubZwNQYAJJKR8lvZJJMTLvYiYkUa2hEw==}
+    dev: false
+
+  /@1password/sdk@0.3.0:
+    resolution: {integrity: sha512-JIvZ8F3MCVW4ebxIdw2KjzeoAtQw6387zDNFhWQpZJBLR6LoJwsG5fXEHFF5FjLitRgIua9qtq9Bj59QkU1k/A==}
+    dependencies:
+      '@1password/sdk-core': 0.3.0
+    dev: false
+
   /@adobe/css-tools@4.4.0:
     resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==}
     dev: false
@@ -7080,6 +7117,12 @@ packages:
       purgecss: 2.3.0
     dev: false
 
+  /@gar/promisify@1.1.3:
+    resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@google-cloud/precise-date@4.0.0:
     resolution: {integrity: sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==}
     engines: {node: '>=14.0.0'}
@@ -7238,6 +7281,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-darwin-arm64@0.34.2:
+    resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-darwin-arm64': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-darwin-x64@0.33.5:
     resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7249,6 +7303,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-darwin-x64@0.34.2:
+    resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-darwin-x64': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-darwin-arm64@1.0.4:
     resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
     cpu: [arm64]
@@ -7257,6 +7322,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-darwin-arm64@1.1.0:
+    resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-darwin-x64@1.0.4:
     resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
     cpu: [x64]
@@ -7265,6 +7338,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-darwin-x64@1.1.0:
+    resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-linux-arm64@1.0.4:
     resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
     cpu: [arm64]
@@ -7273,6 +7354,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-linux-arm64@1.1.0:
+    resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-linux-arm@1.0.5:
     resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
     cpu: [arm]
@@ -7281,6 +7370,22 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-linux-arm@1.1.0:
+    resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linux-ppc64@1.1.0:
+    resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
+    cpu: [ppc64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-linux-s390x@1.0.4:
     resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
     cpu: [s390x]
@@ -7289,6 +7394,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-linux-s390x@1.1.0:
+    resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-linux-x64@1.0.4:
     resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
     cpu: [x64]
@@ -7297,6 +7410,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-linux-x64@1.1.0:
+    resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-linuxmusl-arm64@1.0.4:
     resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
     cpu: [arm64]
@@ -7305,6 +7426,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-linuxmusl-arm64@1.1.0:
+    resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-libvips-linuxmusl-x64@1.0.4:
     resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
     cpu: [x64]
@@ -7313,6 +7442,14 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-libvips-linuxmusl-x64@1.1.0:
+    resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-linux-arm64@0.33.5:
     resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7324,6 +7461,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-linux-arm64@0.34.2:
+    resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-arm64': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-linux-arm@0.33.5:
     resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7335,6 +7483,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-linux-arm@0.34.2:
+    resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-arm': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-linux-s390x@0.33.5:
     resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7346,6 +7505,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-linux-s390x@0.34.2:
+    resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-s390x': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-linux-x64@0.33.5:
     resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7357,6 +7527,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-linux-x64@0.34.2:
+    resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-x64': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-linuxmusl-arm64@0.33.5:
     resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7368,6 +7549,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-linuxmusl-arm64@0.34.2:
+    resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linuxmusl-arm64': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-linuxmusl-x64@0.33.5:
     resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7379,6 +7571,17 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-linuxmusl-x64@0.34.2:
+    resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linuxmusl-x64': 1.1.0
+    dev: false
+    optional: true
+
   /@img/sharp-wasm32@0.33.5:
     resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7389,6 +7592,25 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-wasm32@0.34.2:
+    resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [wasm32]
+    requiresBuild: true
+    dependencies:
+      '@emnapi/runtime': 1.4.3
+    dev: false
+    optional: true
+
+  /@img/sharp-win32-arm64@0.34.2:
+    resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-win32-ia32@0.33.5:
     resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7398,6 +7620,15 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-win32-ia32@0.34.2:
+    resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@img/sharp-win32-x64@0.33.5:
     resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
     engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -7407,6 +7638,15 @@ packages:
     dev: false
     optional: true
 
+  /@img/sharp-win32-x64@0.34.2:
+    resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@infisical/sdk-android-arm-eabi@2.3.5:
     resolution: {integrity: sha512-7KGWJ5X/RV6sIHWt3mjNg+zykzYvbHIKmNKBHTTAOuULKpTIAoQyFD1q3XMylP2AaVh8BJtgVOODO4/Uxexlug==}
     engines: {node: '>= 10'}
@@ -8338,6 +8578,15 @@ packages:
       - supports-color
     dev: false
 
+  /@npmcli/fs@1.1.1:
+    resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
+    requiresBuild: true
+    dependencies:
+      '@gar/promisify': 1.1.3
+      semver: 7.6.3
+    dev: false
+    optional: true
+
   /@npmcli/fs@3.1.0:
     resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -8360,6 +8609,17 @@ packages:
       - bluebird
     dev: true
 
+  /@npmcli/move-file@1.1.2:
+    resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
+    engines: {node: '>=10'}
+    deprecated: This functionality has been moved to @npmcli/fs
+    requiresBuild: true
+    dependencies:
+      mkdirp: 1.0.4
+      rimraf: 3.0.2
+    dev: false
+    optional: true
+
   /@npmcli/package-json@4.0.1:
     resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -16906,6 +17166,13 @@ packages:
       redent: 3.0.0
     dev: false
 
+  /@tootallnate/once@1.1.2:
+    resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
+    engines: {node: '>= 6'}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@tootallnate/quickjs-emscripten@0.23.0:
     resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
     dev: false
@@ -16974,6 +17241,12 @@ packages:
     resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==}
     dev: true
 
+  /@types/bcrypt@5.0.2:
+    resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==}
+    dependencies:
+      '@types/node': 20.14.14
+    dev: true
+
   /@types/bcryptjs@2.4.2:
     resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==}
     dev: true
@@ -18603,6 +18876,12 @@ packages:
     requiresBuild: true
     optional: true
 
+  /abbrev@1.1.1:
+    resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /abbrev@2.0.0:
     resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -19009,6 +19288,12 @@ packages:
     engines: {node: '>= 6.0.0'}
     dev: false
 
+  /aproba@2.0.0:
+    resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /archiver-utils@2.1.0:
     resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==}
     engines: {node: '>= 6'}
@@ -19080,6 +19365,17 @@ packages:
       zip-stream: 6.0.1
     dev: true
 
+  /are-we-there-yet@3.0.1:
+    resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
+    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+    deprecated: This package is no longer supported.
+    requiresBuild: true
+    dependencies:
+      delegates: 1.0.0
+      readable-stream: 3.6.0
+    dev: false
+    optional: true
+
   /arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
@@ -19541,6 +19837,15 @@ packages:
     dependencies:
       tweetnacl: 0.14.5
 
+  /bcrypt@6.0.0:
+    resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
+    engines: {node: '>= 18'}
+    requiresBuild: true
+    dependencies:
+      node-addon-api: 8.3.1
+      node-gyp-build: 4.8.4
+    dev: false
+
   /better-path-resolve@1.0.0:
     resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
     engines: {node: '>=4'}
@@ -19556,6 +19861,12 @@ packages:
     resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
     engines: {node: '>=8'}
 
+  /bindings@1.5.0:
+    resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+    dependencies:
+      file-uri-to-path: 1.0.0
+    dev: false
+
   /bintrees@1.0.2:
     resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
     dev: false
@@ -19622,12 +19933,6 @@ packages:
     dependencies:
       balanced-match: 1.0.2
 
-  /braces@3.0.2:
-    resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
-    engines: {node: '>=8'}
-    dependencies:
-      fill-range: 7.0.1
-
   /braces@3.0.3:
     resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
     engines: {node: '>=8'}
@@ -19795,6 +20100,34 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /cacache@15.3.0:
+    resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==}
+    engines: {node: '>= 10'}
+    requiresBuild: true
+    dependencies:
+      '@npmcli/fs': 1.1.1
+      '@npmcli/move-file': 1.1.2
+      chownr: 2.0.0
+      fs-minipass: 2.1.0
+      glob: 7.2.3
+      infer-owner: 1.0.4
+      lru-cache: 6.0.0
+      minipass: 3.3.6
+      minipass-collect: 1.0.2
+      minipass-flush: 1.0.5
+      minipass-pipeline: 1.2.4
+      mkdirp: 1.0.4
+      p-map: 4.0.0
+      promise-inflight: 1.0.1
+      rimraf: 3.0.2
+      ssri: 8.0.1
+      tar: 6.2.1
+      unique-filename: 1.1.1
+    transitivePeerDependencies:
+      - bluebird
+    dev: false
+    optional: true
+
   /cacache@17.1.4:
     resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -19908,6 +20241,15 @@ packages:
   /caniuse-lite@1.0.30001707:
     resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==}
 
+  /canvas@3.1.0:
+    resolution: {integrity: sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==}
+    engines: {node: ^18.12.0 || >= 20.9.0}
+    requiresBuild: true
+    dependencies:
+      node-addon-api: 7.1.1
+      prebuild-install: 7.1.3
+    dev: false
+
   /capnp-ts@0.7.0:
     resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==}
     dependencies:
@@ -20085,7 +20427,7 @@ packages:
     engines: {node: '>= 8.10.0'}
     dependencies:
       anymatch: 3.1.3
-      braces: 3.0.2
+      braces: 3.0.3
       glob-parent: 5.1.2
       is-binary-path: 2.1.0
       is-glob: 4.0.3
@@ -20339,6 +20681,13 @@ packages:
       simple-swizzle: 0.2.2
     dev: false
 
+  /color-support@1.1.3:
+    resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
+    hasBin: true
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /color@3.2.1:
     resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
     dependencies:
@@ -20354,7 +20703,6 @@ packages:
       color-convert: 2.0.1
       color-string: 1.9.1
     dev: false
-    optional: true
 
   /combined-stream@1.0.8:
     resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
@@ -20475,6 +20823,12 @@ packages:
     resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
     engines: {node: ^14.18.0 || >=16.10.0}
 
+  /console-control-strings@1.1.0:
+    resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /content-disposition@0.5.4:
     resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
     engines: {node: '>= 0.6'}
@@ -21057,6 +21411,13 @@ packages:
       mimic-response: 1.0.1
     dev: true
 
+  /decompress-response@6.0.0:
+    resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+    engines: {node: '>=10'}
+    dependencies:
+      mimic-response: 3.1.0
+    dev: false
+
   /deep-eql@4.1.3:
     resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
     engines: {node: '>=6'}
@@ -21072,7 +21433,6 @@ packages:
   /deep-extend@0.6.0:
     resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
     engines: {node: '>=4.0.0'}
-    dev: true
 
   /deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -21175,6 +21535,12 @@ packages:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
     engines: {node: '>=0.4.0'}
 
+  /delegates@1.0.0:
+    resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /denque@2.1.0:
     resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
     engines: {node: '>=0.10'}
@@ -21211,6 +21577,11 @@ packages:
     engines: {node: '>=8'}
     requiresBuild: true
 
+  /detect-libc@2.0.4:
+    resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
+    engines: {node: '>=8'}
+    dev: false
+
   /detect-node-es@1.1.0:
     resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
     dev: false
@@ -22885,6 +23256,11 @@ packages:
     resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
     engines: {node: '>=6'}
 
+  /expand-template@2.0.3:
+    resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
+    engines: {node: '>=6'}
+    dev: false
+
   /expect-type@1.2.0:
     resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==}
     engines: {node: '>=12.0.0'}
@@ -23177,11 +23553,9 @@ packages:
       tslib: 2.8.1
     dev: false
 
-  /fill-range@7.0.1:
-    resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
-    engines: {node: '>=8'}
-    dependencies:
-      to-regex-range: 5.0.1
+  /file-uri-to-path@1.0.0:
+    resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+    dev: false
 
   /fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
@@ -23522,6 +23896,23 @@ packages:
   /functions-have-names@1.2.3:
     resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
 
+  /gauge@4.0.4:
+    resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
+    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+    deprecated: This package is no longer supported.
+    requiresBuild: true
+    dependencies:
+      aproba: 2.0.0
+      color-support: 1.1.3
+      console-control-strings: 1.1.0
+      has-unicode: 2.0.1
+      signal-exit: 3.0.7
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wide-align: 1.1.5
+    dev: false
+    optional: true
+
   /generative-bayesian-network@2.1.55:
     resolution: {integrity: sha512-igqQZTtS4GFwkDWA5gFWQye9Lmkx184Y17+x9flFq8HC68RVuuQGPeQtBFdMMlnac7/2Bq1n+1rkp4S8ZAu7kA==}
     dependencies:
@@ -23683,6 +24074,10 @@ packages:
       tar: 6.2.1
     dev: false
 
+  /github-from-package@0.0.0:
+    resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
+    dev: false
+
   /glob-parent@5.1.2:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
@@ -24000,6 +24395,12 @@ packages:
       has-symbols: 1.1.0
     dev: true
 
+  /has-unicode@2.0.1:
+    resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /has@1.0.3:
     resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
     engines: {node: '>= 0.4.0'}
@@ -24161,6 +24562,19 @@ packages:
       statuses: 2.0.1
       toidentifier: 1.0.1
 
+  /http-proxy-agent@4.0.1:
+    resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
+    engines: {node: '>= 6'}
+    requiresBuild: true
+    dependencies:
+      '@tootallnate/once': 1.1.2
+      agent-base: 6.0.2
+      debug: 4.4.0(supports-color@10.0.0)
+    transitivePeerDependencies:
+      - supports-color
+    dev: false
+    optional: true
+
   /http-proxy-agent@7.0.0:
     resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==}
     engines: {node: '>= 14'}
@@ -24340,6 +24754,12 @@ packages:
     engines: {node: '>=12'}
     dev: true
 
+  /infer-owner@1.0.4:
+    resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -25790,6 +26210,33 @@ packages:
       - supports-color
     dev: false
 
+  /make-fetch-happen@9.1.0:
+    resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==}
+    engines: {node: '>= 10'}
+    requiresBuild: true
+    dependencies:
+      agentkeepalive: 4.5.0
+      cacache: 15.3.0
+      http-cache-semantics: 4.1.1
+      http-proxy-agent: 4.0.1
+      https-proxy-agent: 5.0.1
+      is-lambda: 1.0.1
+      lru-cache: 6.0.0
+      minipass: 3.3.6
+      minipass-collect: 1.0.2
+      minipass-fetch: 1.4.1
+      minipass-flush: 1.0.5
+      minipass-pipeline: 1.2.4
+      negotiator: 0.6.3
+      promise-retry: 2.0.1
+      socks-proxy-agent: 6.2.1
+      ssri: 8.0.1
+    transitivePeerDependencies:
+      - bluebird
+      - supports-color
+    dev: false
+    optional: true
+
   /map-obj@1.0.1:
     resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
     engines: {node: '>=0.10.0'}
@@ -26670,6 +27117,11 @@ packages:
     engines: {node: '>=4'}
     dev: true
 
+  /mimic-response@3.1.0:
+    resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+    engines: {node: '>=10'}
+    dev: false
+
   /min-indent@1.0.1:
     resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
     engines: {node: '>=4'}
@@ -26772,7 +27224,6 @@ packages:
     engines: {node: '>= 8'}
     dependencies:
       minipass: 3.3.6
-    dev: true
 
   /minipass-collect@2.0.1:
     resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==}
@@ -26781,6 +27232,19 @@ packages:
       minipass: 7.1.2
     dev: false
 
+  /minipass-fetch@1.4.1:
+    resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==}
+    engines: {node: '>=8'}
+    requiresBuild: true
+    dependencies:
+      minipass: 3.3.6
+      minipass-sized: 1.0.3
+      minizlib: 2.1.2
+    optionalDependencies:
+      encoding: 0.1.13
+    dev: false
+    optional: true
+
   /minipass-fetch@3.0.5:
     resolution: {integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -26974,6 +27438,10 @@ packages:
     resolution: {integrity: sha512-c8eMDb1WwZcE02WVjHoOmUVk7fnKU/RmUcosHACglrWAuPQsEJv+E8430sXj6jNc1jHw0zrS16aCjQh4BcEb4A==}
     dev: false
 
+  /mupdf@1.3.6:
+    resolution: {integrity: sha512-HRKjlFiD1aN9u2iX7JCIr6qU6jMnihsQY2GnNVcrXXM1BBh3es1166mjMVsdjnhUfnA2NQr4pJBmUjFCxQ/N4A==}
+    dev: false
+
   /mustache@4.2.0:
     resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
     hasBin: true
@@ -27047,6 +27515,17 @@ packages:
     hasBin: true
     dev: false
 
+  /napi-build-utils@2.0.0:
+    resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
+    dev: false
+
+  /napi_thread_safe_promise@1.2.6:
+    resolution: {integrity: sha512-Magj7jWzspQiYlqBt4sieiOvWmNJgL/hCWeQaKFpFam82QZJ/etvOfAxlqhPtuUspMABTEgikULHaJA3aDVTNQ==}
+    requiresBuild: true
+    dependencies:
+      node-addon-api: 3.2.1
+    dev: false
+
   /natural-compare-lite@1.4.0:
     resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
     dev: true
@@ -27258,10 +27737,34 @@ packages:
     resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
     dev: true
 
+  /node-abi@3.75.0:
+    resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==}
+    engines: {node: '>=10'}
+    dependencies:
+      semver: 7.6.3
+    dev: false
+
   /node-abort-controller@3.1.1:
     resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
     dev: false
 
+  /node-addon-api@3.2.1:
+    resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
+    dev: false
+
+  /node-addon-api@4.3.0:
+    resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
+    dev: false
+
+  /node-addon-api@7.1.1:
+    resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+    dev: false
+
+  /node-addon-api@8.3.1:
+    resolution: {integrity: sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==}
+    engines: {node: ^18 || ^20 || >= 21}
+    dev: false
+
   /node-domexception@1.0.0:
     resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
     engines: {node: '>=10.5.0'}
@@ -27354,6 +27857,28 @@ packages:
       - supports-color
     dev: false
 
+  /node-gyp@8.4.1:
+    resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==}
+    engines: {node: '>= 10.12.0'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      env-paths: 2.2.1
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      make-fetch-happen: 9.1.0
+      nopt: 5.0.0
+      npmlog: 6.0.2
+      rimraf: 3.0.2
+      semver: 7.6.3
+      tar: 6.2.1
+      which: 2.0.2
+    transitivePeerDependencies:
+      - bluebird
+      - supports-color
+    dev: false
+    optional: true
+
   /node-releases@2.0.12:
     resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==}
     dev: true
@@ -27374,6 +27899,16 @@ packages:
     resolution: {integrity: sha512-oGCfo7Ub5bJmCOAt4CMAsCrVal/JXkQtxgBcpXzdU3lQib9c5hHdr62I8vI6TtdlsLcaYAPD/HRfZNdBUobwFg==}
     dev: false
 
+  /nopt@5.0.0:
+    resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
+    engines: {node: '>=6'}
+    hasBin: true
+    requiresBuild: true
+    dependencies:
+      abbrev: 1.1.1
+    dev: false
+    optional: true
+
   /nopt@7.2.0:
     resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -27505,6 +28040,19 @@ packages:
     dependencies:
       path-key: 4.0.0
 
+  /npmlog@6.0.2:
+    resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
+    engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+    deprecated: This package is no longer supported.
+    requiresBuild: true
+    dependencies:
+      are-we-there-yet: 3.0.1
+      console-control-strings: 1.1.0
+      gauge: 4.0.4
+      set-blocking: 2.0.0
+    dev: false
+    optional: true
+
   /num2fraction@1.2.2:
     resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==}
     dev: false
@@ -28921,6 +29469,25 @@ packages:
       - debug
     dev: false
 
+  /prebuild-install@7.1.3:
+    resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      detect-libc: 2.0.3
+      expand-template: 2.0.3
+      github-from-package: 0.0.0
+      minimist: 1.2.7
+      mkdirp-classic: 0.5.3
+      napi-build-utils: 2.0.0
+      node-abi: 3.75.0
+      pump: 3.0.0
+      rc: 1.2.8
+      simple-get: 4.0.1
+      tar-fs: 2.1.2
+      tunnel-agent: 0.6.0
+    dev: false
+
   /preferred-pm@3.0.3:
     resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==}
     engines: {node: '>=10'}
@@ -29140,7 +29707,6 @@ packages:
     peerDependenciesMeta:
       bluebird:
         optional: true
-    dev: true
 
   /promise-retry@2.0.1:
     resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
@@ -29413,7 +29979,6 @@ packages:
       ini: 1.3.8
       minimist: 1.2.7
       strip-json-comments: 2.0.1
-    dev: true
 
   /re2@1.21.3:
     resolution: {integrity: sha512-GI+KoGkHT4kxTaX+9p0FgNB1XUnCndO9slG5qqeEoZ7kbf6Dk6ohQVpmwKVeSp7LPLn+g6Q3BaCopz4oHuBDuQ==}
@@ -30856,6 +31421,12 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
+  /semver@7.7.2:
+    resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dev: false
+
   /send@0.18.0:
     resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
     engines: {node: '>= 0.8.0'}
@@ -31030,6 +31601,38 @@ packages:
     dev: false
     optional: true
 
+  /sharp@0.34.2:
+    resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+    requiresBuild: true
+    dependencies:
+      color: 4.2.3
+      detect-libc: 2.0.4
+      semver: 7.7.2
+    optionalDependencies:
+      '@img/sharp-darwin-arm64': 0.34.2
+      '@img/sharp-darwin-x64': 0.34.2
+      '@img/sharp-libvips-darwin-arm64': 1.1.0
+      '@img/sharp-libvips-darwin-x64': 1.1.0
+      '@img/sharp-libvips-linux-arm': 1.1.0
+      '@img/sharp-libvips-linux-arm64': 1.1.0
+      '@img/sharp-libvips-linux-ppc64': 1.1.0
+      '@img/sharp-libvips-linux-s390x': 1.1.0
+      '@img/sharp-libvips-linux-x64': 1.1.0
+      '@img/sharp-libvips-linuxmusl-arm64': 1.1.0
+      '@img/sharp-libvips-linuxmusl-x64': 1.1.0
+      '@img/sharp-linux-arm': 0.34.2
+      '@img/sharp-linux-arm64': 0.34.2
+      '@img/sharp-linux-s390x': 0.34.2
+      '@img/sharp-linux-x64': 0.34.2
+      '@img/sharp-linuxmusl-arm64': 0.34.2
+      '@img/sharp-linuxmusl-x64': 0.34.2
+      '@img/sharp-wasm32': 0.34.2
+      '@img/sharp-win32-arm64': 0.34.2
+      '@img/sharp-win32-ia32': 0.34.2
+      '@img/sharp-win32-x64': 0.34.2
+    dev: false
+
   /shebang-command@1.2.0:
     resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
     engines: {node: '>=0.10.0'}
@@ -31114,6 +31717,18 @@ packages:
     resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
     engines: {node: '>=14'}
 
+  /simple-concat@1.0.1:
+    resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
+    dev: false
+
+  /simple-get@4.0.1:
+    resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
+    dependencies:
+      decompress-response: 6.0.0
+      once: 1.4.0
+      simple-concat: 1.0.1
+    dev: false
+
   /simple-oauth2@5.0.0:
     resolution: {integrity: sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ==}
     dependencies:
@@ -31303,6 +31918,19 @@ packages:
       - utf-8-validate
     dev: false
 
+  /socks-proxy-agent@6.2.1:
+    resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==}
+    engines: {node: '>= 10'}
+    requiresBuild: true
+    dependencies:
+      agent-base: 6.0.2
+      debug: 4.4.0(supports-color@10.0.0)
+      socks: 2.8.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: false
+    optional: true
+
   /socks-proxy-agent@8.0.4:
     resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==}
     engines: {node: '>= 14'}
@@ -31432,6 +32060,21 @@ packages:
     resolution: {integrity: sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==}
     dev: false
 
+  /sqlite3@5.1.7:
+    resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==}
+    requiresBuild: true
+    dependencies:
+      bindings: 1.5.0
+      node-addon-api: 7.1.1
+      prebuild-install: 7.1.3
+      tar: 6.2.1
+    optionalDependencies:
+      node-gyp: 8.4.1
+    transitivePeerDependencies:
+      - bluebird
+      - supports-color
+    dev: false
+
   /sqs-consumer@7.5.0(@aws-sdk/client-sqs@3.454.0):
     resolution: {integrity: sha512-aY3akgMjuK1aj4E7ZVAURUUnC8aNgUBES+b4SN+6ccMmJhi37MamWl7g1JbPow8sjIp1fBPz1bXCCDJmtjOTAg==}
     engines: {node: '>=18.0.0'}
@@ -31484,6 +32127,15 @@ packages:
     dependencies:
       minipass: 7.1.2
 
+  /ssri@8.0.1:
+    resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
+    engines: {node: '>= 8'}
+    requiresBuild: true
+    dependencies:
+      minipass: 3.3.6
+    dev: false
+    optional: true
+
   /sswr@2.1.0(svelte@5.28.2):
     resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==}
     peerDependencies:
@@ -31740,7 +32392,6 @@ packages:
   /strip-json-comments@2.0.1:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /strip-json-comments@3.1.1:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
@@ -32220,7 +32871,6 @@ packages:
       mkdirp-classic: 0.5.3
       pump: 3.0.0
       tar-stream: 2.2.0
-    dev: true
 
   /tar-fs@3.0.6:
     resolution: {integrity: sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==}
@@ -33229,7 +33879,7 @@ packages:
     resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==}
     dev: false
 
-  /typeorm@0.3.20(pg@8.11.5)(ts-node@10.9.2):
+  /typeorm@0.3.20(pg@8.11.5)(sqlite3@5.1.7)(ts-node@10.9.2):
     resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==}
     engines: {node: '>=16.13.0'}
     hasBin: true
@@ -33300,6 +33950,7 @@ packages:
       pg: 8.11.5
       reflect-metadata: 0.2.2
       sha.js: 2.4.11
+      sqlite3: 5.1.7
       ts-node: 10.9.2(@types/node@20.14.14)(typescript@5.5.4)
       tslib: 2.6.2
       uuid: 9.0.0
@@ -33434,12 +34085,28 @@ packages:
       vfile: 6.0.3
     dev: false
 
+  /unique-filename@1.1.1:
+    resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
+    requiresBuild: true
+    dependencies:
+      unique-slug: 2.0.2
+    dev: false
+    optional: true
+
   /unique-filename@3.0.0:
     resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
     dependencies:
       unique-slug: 4.0.0
 
+  /unique-slug@2.0.2:
+    resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==}
+    requiresBuild: true
+    dependencies:
+      imurmurhash: 0.1.4
+    dev: false
+    optional: true
+
   /unique-slug@4.0.0:
     resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -34722,6 +35389,14 @@ packages:
       stackback: 0.0.2
     dev: true
 
+  /wide-align@1.1.5:
+    resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+    requiresBuild: true
+    dependencies:
+      string-width: 4.2.3
+    dev: false
+    optional: true
+
   /word-wrap@1.2.3:
     resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
     engines: {node: '>=0.10.0'}
@@ -35035,6 +35710,15 @@ packages:
   /zimmerframe@1.1.2:
     resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
 
+  /zip-node-addon@0.0.11:
+    resolution: {integrity: sha512-Kr5qN0UdXn/JpMyA4wri5nzugPzGRV2MA49JW4ijr1h2aPdUp1wEDl7eqmk6FzlhDE6JlSEfey3gajVcpWsqbQ==}
+    requiresBuild: true
+    dependencies:
+      bindings: 1.5.0
+      napi_thread_safe_promise: 1.2.6
+      node-addon-api: 4.3.0
+    dev: false
+
   /zip-stream@4.1.1:
     resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
     engines: {node: '>= 10'}
diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json
index 3de6f793962..5b2315fe67b 100644
--- a/references/v3-catalog/package.json
+++ b/references/v3-catalog/package.json
@@ -16,8 +16,9 @@
     "generate:prisma": "prisma generate --sql"
   },
   "dependencies": {
+    "@1password/sdk": "^0.3.0",
     "@effect/schema": "^0.75.5",
-    "@infisical/sdk": "^2.1.9",
+    "@infisical/sdk": "^2.3.5",
     "@opentelemetry/api": "1.4.1",
     "@prisma/client": "5.19.0",
     "@react-email/components": "0.0.24",
@@ -32,6 +33,8 @@
     "@typeschema/typebox": "^0.14.0",
     "ai": "^3.3.24",
     "arktype": "2.0.0-rc.17",
+    "bcrypt": "^6.0.0",
+    "canvas": "^3.1.0",
     "dotenv": "^16.4.5",
     "email-reply-parser": "^1.8.0",
     "execa": "^8.0.1",
@@ -39,6 +42,7 @@
     "header-generator": "^2.1.55",
     "kysely": "^0.27.4",
     "msw": "^2.2.1",
+    "mupdf": "^1.3.6",
     "openai": "^4.47.0",
     "pg": "^8.11.5",
     "playwright": "^1.50.1",
@@ -48,6 +52,8 @@
     "reflect-metadata": "^0.1.13",
     "runtypes": "^6.7.0",
     "server-only": "^0.0.1",
+    "sharp": "^0.34.2",
+    "sqlite3": "^5.1.7",
     "stripe": "^12.14.0",
     "superstruct": "^2.0.2",
     "typeorm": "^0.3.20",
@@ -55,6 +61,7 @@
     "wrangler": "3.70.0",
     "yt-dlp-wrap": "^2.3.12",
     "yup": "^1.4.0",
+    "zip-node-addon": "^0.0.11",
     "zod": "3.23.8"
   },
   "devDependencies": {
@@ -75,6 +82,7 @@
     "@opentelemetry/semantic-conventions": "^1.22.0",
     "@trigger.dev/build": "workspace:*",
     "@trigger.dev/python": "workspace:*",
+    "@types/bcrypt": "^5.0.2",
     "@types/email-reply-parser": "^1.4.2",
     "@types/fluent-ffmpeg": "^2.1.26",
     "@types/react": "^18.3.1",
diff --git a/references/v3-catalog/src/trigger/binaries.ts b/references/v3-catalog/src/trigger/binaries.ts
index d7a6da8b517..482bf14cbdf 100644
--- a/references/v3-catalog/src/trigger/binaries.ts
+++ b/references/v3-catalog/src/trigger/binaries.ts
@@ -4,6 +4,87 @@ import * as path from "node:path";
 import { Readable } from "node:stream";
 import type { ReadableStream } from "node:stream/web";
 
+import bcrypt from "bcrypt";
+
+const saltRounds = 10;
+const myPlaintextPassword = "s0//P4$$w0rD";
+const someOtherPlaintextPassword = "not_bacon";
+
+bcrypt.genSalt(saltRounds, function (err, salt) {
+  bcrypt.hash(myPlaintextPassword, salt, function (err, hash) {
+    // Store hash in your password DB.
+  });
+});
+
+import { InfisicalClient } from "@infisical/sdk";
+
+const infisicalClient = new InfisicalClient({
+  siteUrl: "https://example.com",
+});
+
+import * as mupdf from "mupdf";
+
+// Helper function to load document from URL
+async function loadDocumentFromUrl(url: string): Promise<mupdf.Document> {
+  try {
+    const response = await fetch(url);
+    const buffer = await response.arrayBuffer();
+    return mupdf.Document.openDocument(buffer, "application/pdf");
+  } catch (error) {
+    throw new Error(`Failed to load document from URL: ${url}`);
+  }
+}
+
+import zip from "zip-node-addon";
+
+function unzip(inputPath: string, outputPath: string) {
+  zip.unzipFile(inputPath, outputPath);
+}
+
+import { createClient } from "@1password/sdk";
+
+// Creates an authenticated client.
+const client = await createClient({
+  auth: process.env.OP_SERVICE_ACCOUNT_TOKEN ?? "",
+  // Set the following to your own integration name and version.
+  integrationName: "My 1Password Integration",
+  integrationVersion: "v1.0.0",
+});
+
+// Fetches a secret.
+// const secret = await client.secrets.resolve("op://vault/item/field");
+
+import sharp from "sharp";
+import sqlite3 from "sqlite3";
+import { createCanvas } from "canvas";
+
+// Test sharp: create a 1x1 PNG buffer
+const sharpBufferPromise = sharp({
+  create: {
+    width: 1,
+    height: 1,
+    channels: 4,
+    background: { r: 0, g: 0, b: 0, alpha: 0 },
+  },
+})
+  .png()
+  .toBuffer();
+
+// Test sqlite3: open an in-memory database
+const sqliteDb = new sqlite3.Database(":memory:", (err) => {
+  if (err) {
+    console.error("sqlite3 error:", err);
+  } else {
+    console.log("sqlite3 in-memory database opened");
+  }
+});
+
+// Test canvas: create a 100x100 canvas and draw a rectangle
+const canvas = createCanvas(100, 100);
+const ctx = canvas.getContext("2d");
+ctx.fillStyle = "red";
+ctx.fillRect(10, 10, 80, 80);
+
 export const convertVideo = task({
   id: "convert-video",
   retry: {
diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts
index c27ce98dda2..259a33927fa 100644
--- a/references/v3-catalog/trigger.config.ts
+++ b/references/v3-catalog/trigger.config.ts
@@ -3,7 +3,7 @@ import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin";
 import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
 import { esbuildPlugin } from "@trigger.dev/build";
 import { audioWaveform } from "@trigger.dev/build/extensions/audioWaveform";
-import { ffmpeg, syncEnvVars } from "@trigger.dev/build/extensions/core";
+import { additionalFiles, ffmpeg, syncEnvVars } from "@trigger.dev/build/extensions/core";
 import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";
 import { playwright } from "@trigger.dev/build/extensions/playwright";
 import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
@@ -15,7 +15,6 @@ export default defineConfig({
   project: "yubjwjsfkxnylobaqvqz",
   machine: "medium-1x",
   instrumentations: [new OpenAIInstrumentation()],
-  additionalFiles: ["wrangler/wrangler.toml"],
   maxDuration: 3600,
   dirs: ["./src/trigger"],
   retries: {
@@ -32,7 +31,11 @@ export default defineConfig({
   logLevel: "info",
   build: {
     conditions: ["react-server"],
+    experimental_autoDetectExternal: true,
     extensions: [
+      additionalFiles({
+        files: ["./wrangler/wrangler.toml"],
+      }),
       ffmpeg(),
       emitDecoratorMetadata(),
       audioWaveform(),
@@ -48,6 +51,7 @@ export default defineConfig({
           org: "triggerdev",
           project: "taskhero-examples-basic",
           authToken: process.env.SENTRY_AUTH_TOKEN,
+          telemetry: false,
         }),
         { placement: "last", target: "deploy" }
       ),


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>packages/cli-v3/src/build/braces.d.ts (1)</summary><blockquote>

`84-88`: **Fix typo in JSDoc – `quanitifiers` ➜ `quantifiers`**

Small documentation typo – makes copy-paste searches harder and looks unpolished.

```diff
-   * In regular expressions, quanitifiers can be used to specify how many times a token can be repeated.
+   * In regular expressions, quantifiers can be used to specify how many times a token can be repeated.
```

</blockquote></details>
<details>
<summary>packages/cli-v3/src/build/externals.ts (2)</summary><blockquote>

`565-568`: **Broaden non-bare import detection**

`isBareModuleImport()` misses other common prefixes (`node:`, `https:`, `http:`, `npm:`, `#` for import maps).  
Including them avoids unnecessary resolution attempts and false positives.

```diff
-  const excludes = [".", "/", "~", "file:", "data:"];
+  const excludes = [
+    ".", "/", "~",
+    "file:", "data:", "http:", "https:",
+    "node:", "npm:", "#"
+  ];
```

---

`570-572`: **Handle sub-path built-in imports**

`builtinModules` contains plain module names (`'fs'`, `'url'`).  
Imports like `node:fs/promises` or `fs/promises` won’t match and will be mis-classified.

```diff
-function isBuiltinModule(path: string): boolean {
-  return builtinModules.includes(path.replace("node:", ""));
+function isBuiltinModule(path: string): boolean {
+  const cleaned = path.replace(/^node:/, "");
+  return builtinModules.some((m) => cleaned === m || cleaned.startsWith(`${m}/`));
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e08e493b3cf2b6073b7620185f0074d179b2670b and d546ab39a78323916c0412dc26529ac9b928ffc1.

</details>

<details>
<summary>⛔ Files ignored due to path filters (4)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`
* `references/v3-catalog/package.json` is excluded by `!references/**`
* `references/v3-catalog/src/trigger/binaries.ts` is excluded by `!references/**`
* `references/v3-catalog/trigger.config.ts` is excluded by `!references/**`

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `.changeset/grumpy-wasps-fold.md` (1 hunks)
* `packages/cli-v3/package.json` (1 hunks)
* `packages/cli-v3/src/build/braces.d.ts` (1 hunks)
* `packages/cli-v3/src/build/externals.ts` (5 hunks)
* `packages/cli-v3/src/commands/deploy.ts` (2 hunks)
* `packages/core/src/v3/config.ts` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
* GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
* GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
* GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
* GitHub Check: typecheck / typecheck
* GitHub Check: units / 🧪 Unit Tests
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>packages/core/src/v3/config.ts (1)</summary>

`173-184`: **Well-documented experimental feature.**

The configuration property is properly documented with clear warning notices, default value, and purpose explanation. The JSDoc comments help users understand when and why they should use this experimental feature.

</details>
<details>
<summary>packages/cli-v3/package.json (1)</summary>

`99-99`: <details>
<summary>✅ Verification successful</summary>

**Verify braces dependency version is appropriate.**

The `braces` package is added as a new dependency, which is necessary for the experimental external detection feature.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the braces package has any known security vulnerabilities and latest version

# Get the latest version
npm view braces version

# Check for any security advisories
npm audit --package braces@3.0.3 --json | jq '.metadata.vulnerabilities'
```

Length of output: 458

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for existing lockfiles to enable vulnerability auditing
find . -type f -name 'package-lock.json'
find . -type f -name 'yarn.lock'
```

Length of output: 711

---

🏁 Script executed:

```shell
#!/bin/bash
# Fetch npm security advisories for the braces package
curl -s "https://registry.npmjs.org/-/npm/v1/security/advisories?module=braces" \
  | jq '.objects[] | {id, title, vulnerable_versions, severity, url}'
```

Length of output: 207

---



</details>

**Dependency Validation: braces@3.0.3 is up-to-date and has no known advisories**  

I confirmed via `npm view braces version` that 3.0.3 is the latest release and found no security advisories for this package in the npm registry. The addition of `braces` for the experimental external detection feature can proceed as is.

</details>
<details>
<summary>.changeset/grumpy-wasps-fold.md (1)</summary>

`1-7`: **Appropriate changeset entry for experimental feature.**

The changeset correctly identifies this as a patch version update and concisely describes the new experimental feature. This aligns with semantic versioning principles since it's adding a new capability without breaking existing functionality.

</details>
<details>
<summary>packages/cli-v3/src/commands/deploy.ts (2)</summary>

`2-2`: **New import for improved error handling.**

The addition of `tryCatch` utility from the core package supports the improved error handling implementation.

---

`241-267`: **Improved error handling with explicit try-catch pattern.**

The refactoring from direct `await` to a structured error-handling approach using `tryCatch` is a significant improvement. This pattern:

1. Provides explicit error handling for the build process
2. Ensures the spinner is properly stopped with a clear failure message
3. Adds better visibility into build completion via the `onBundleComplete` callback

This change enhances both developer experience and code reliability.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T10:18:44Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>packages/cli-v3/src/build/externals.ts (1)</summary><blockquote>

`268-406`: **Well-structured implementation of the autoDetectExternal feature.**

The implementation properly:
1. Only runs when explicitly enabled via the autoDetectExternal config flag
2. Filters out non-package imports early
3. Uses caching to avoid redundant work
4. Has comprehensive detection logic for native modules

Since this is an experimental feature, consider adding a warning log when it's enabled to remind users about its status.


```diff
 if (resolvedConfig.build?.autoDetectExternal) {
+          logger.warn("[externals] Using experimental autoDetectExternal feature. This may cause unexpected behavior.");
           build.onResolve(
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d546ab39a78323916c0412dc26529ac9b928ffc1 and 9e122c8361419cd2606e78ea329451be795f64e7.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `packages/cli-v3/src/build/braces.d.ts` (1 hunks)
* `packages/cli-v3/src/build/externals.ts` (5 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* packages/cli-v3/src/build/braces.d.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
* GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
* GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
* GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
* GitHub Check: typecheck / typecheck
* GitHub Check: units / 🧪 Unit Tests
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>packages/cli-v3/src/build/externals.ts (6)</summary>

`147-150`: **Effective caching implementation for performance optimization.**

The caching strategy using two separate maps is a good approach to improve performance by avoiding repeated file system operations and package.json parsing. This is especially important for large projects with many dependencies.

---

`163-169`: **Enhanced logging will help with troubleshooting.**

The expanded logging now includes details about externals collection, cache sizes, and the auto-detection status, which will be valuable for diagnosing issues in this experimental feature.

---

`368-370`: **Correct implementation for native file extension detection.**

This implementation correctly uses `resolvedPath` instead of `args.path` when checking for native file extensions, addressing a previous issue identified in code review.

---

`383-388`: **Comprehensive detection of native files.**

The regex pattern covers common native file extensions, and the check against expanded file entries is a good approach to detect potential native dependencies.

---

`570-577`: **The module detection utility functions look correct.**

Both `isBareModuleImport` and `isBuiltinModule` functions are implemented correctly and serve their purpose well. The excludes list for bare module imports is comprehensive.

---

`623-652`: **Well-implemented caching for package.json resolution.**

The `findNearestPackageJson` function effectively uses the cache and properly handles errors during resolution. This will significantly improve performance when processing many modules from the same package.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:01:34Z
user: login: ericallam
body: Rename config property to prefix with `experimental_`
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T15:12:14Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>packages/cli-v3/src/build/externals.ts (1)</summary><blockquote>

`579-621`: **Add test coverage for `isMainPackageJson`**

No existing tests reference this function, so dedicated unit tests should be added to validate its behavior across edge cases.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>packages/cli-v3/src/build/externals.ts (5)</summary><blockquote>

`268-280`: **Consider adding error handling for configuration validation**

The experimental flag check is correctly implemented, but consider adding validation to ensure users understand the implications of enabling this experimental feature.

```diff
 if (resolvedConfig.build?.experimental_autoDetectExternal) {
+  logger.debug("[externals] Using experimental auto-detection of externals. This may cause build or runtime issues.");
   build.onResolve(
```

---

`288-298`: **Ensure module resolution errors are properly categorized**

The error handling for module resolution is good, but consider distinguishing between different types of resolution errors to provide more specific guidance to users.

```diff
 if (resolveError) {
   logger.debug("[externals][auto] Resolve module error", {
     path: args.path,
     resolveError,
+    resolveErrorCode: resolveError instanceof Error ? (resolveError as any).code : 'unknown',
+    resolveErrorMessage: resolveError instanceof Error ? resolveError.message : String(resolveError)
   });
   return;
 }
```

---

`579-621`: **Consider additional edge cases in isMainPackageJson**

The function distinguishes between main package.json files and type marker files using a hardcoded list. Consider adding handling for additional fields that may appear in marker files.

```diff
 const markerFields = new Set([
   "type",
   "sideEffects",
   "browser",
   "main",
   "module",
   "react-native",
   "name",
+  "exports",
+  "types",
+  "typings"
 ]);
```

---

`613-617`: **Improve error logging verbosity**

The error logging could be more informative by including the file path in the error message.

```diff
 if ("code" in error && error.code !== "ENOENT") {
   logger.debug("[externals][containsEsmTypeMarkers] Error", {
+    filePath,
     error: error.message,
   });
 }
```

---

`639-644`: **Consider refactoring to use async/await for clarity**

While `tryCatch` works correctly, consider using async/await with standard try/catch for consistency with other parts of the codebase and improved readability.

```diff
-  const [error, packageJsonPath] = await tryCatch(
-    resolvePackageJSON(dirname(basePath), {
-      test: isMainPackageJson,
-    })
-  );
-
-  if (error) {
-    cache.set(baseDir, null);
-    return null;
-  }
+  try {
+    const packageJsonPath = await resolvePackageJSON(dirname(basePath), {
+      test: isMainPackageJson,
+    });
+    
+    cache.set(baseDir, packageJsonPath);
+    return packageJsonPath;
+  } catch (error) {
+    cache.set(baseDir, null);
+    return null;
+  }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9e122c8361419cd2606e78ea329451be795f64e7 and 3ff83669759bf767406da610e7bdeb556978b649.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `references/v3-catalog/trigger.config.ts` is excluded by `!references/**`

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `.changeset/grumpy-wasps-fold.md` (1 hunks)
* `packages/cli-v3/src/build/externals.ts` (5 hunks)
* `packages/core/src/v3/config.ts` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* .changeset/grumpy-wasps-fold.md

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* packages/core/src/v3/config.ts

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (7)</summary>

* GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
* GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
* GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
* GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
* GitHub Check: units / 🧪 Unit Tests
* GitHub Check: typecheck / typecheck
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>packages/cli-v3/src/build/externals.ts (2)</summary>

`147-150`: **Good practice using caching for performance optimization**

The implementation uses two caching mechanisms to avoid redundant filesystem operations, which is a good performance optimization especially for the build process that might process thousands of modules.

---

`163-169`: **Improved debugging with comprehensive logging**

The enhanced logging will make it easier to diagnose issues with the automatic external detection feature. Including both the flag status and cache sizes provides valuable context.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:20:21Z

Review comments:
diff_hunk: @@ -0,0 +1,125 @@
+/*
+    MIT License
+
+    Copyright (c) Microsoft Corporation.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE
+ */
+
+type Transform = (str: string) => string;
+
+interface Options {
+  /**
+   * Limit the length of the input string. Useful when the input string is generated or your application allows
+   * users to pass a string, et cetera.
+   *
+   * @default 65536
+   * @example
+   * console.log(braces('a/{b,c}/d', { maxLength: 3 }));
+   * //=> throws an error
+   */
+  maxLength?: number | undefined;
+  /**
+   * Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method).
+   *
+   * @default undefined
+   * @example
+   * console.log(braces('a/{b,c}/d', { expand: true }));
+   * //=> [ 'a/b/d', 'a/c/d' ]
+   */
+  expand?: boolean | undefined;
+  /**
+   * Remove duplicates from the returned array.
+   *
+   * @default undefined
+   */
+  nodupes?: boolean | undefined;
+  /**
+   * To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()`
+   * is used or `options.expand` is true and the generated range will exceed the `rangeLimit`.
+   *
+   * You can customize `options.rangeLimit` or set it to `Infinity` to disable this altogether.
+   *
+   * @default 1000
+   * @example
+   * // pattern exceeds the "rangeLimit", so it's optimized automatically
+   * console.log(braces.expand('{1..1000}'));
+   * //=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
+   *
+   * // pattern does not exceed "rangeLimit", so it's NOT optimized
+   * console.log(braces.expand('{1..100}'));
+   * //=> ['1', '2', '3', '4', '5', …, '100']
+   */
+  rangeLimit?: number | undefined;
+  /**
+   * Customize range expansion.
+   *
+   * @default undefined
+   * @example
+   * const range = braces.expand('x{a..e}y', {
+   *   transform: (str) => `foo/${str}`
+   * });
+   *
+   * console.log(range);
+   * //=> [ 'xfooay', 'xfooby', 'xfoocy', 'xfoody', 'xfooey' ]
+   */
+  transform?: Transform | undefined;
+  /**
+   * In regular expressions, quanitifiers can be used to specify how many times a token can be repeated.
+   * For example, `a{1,3}` will match the letter `a` one to three times.
+   *
+   * Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#lists)
+   *
+   * The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers)
+   * are defined in the given pattern, and not to try to expand them as lists.
+   *
+   * @default undefined
+   * @example
+   * const braces = require('braces');
+   * console.log(braces('a/b{1,3}/{x,y,z}'));
+   * //=> [ 'a/b(1|3)/(x|y|z)' ]
+   * console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true}));
+   * //=> [ 'a/b{1,3}/(x|y|z)' ]
+   * console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true, expand: true}));
+   * //=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ]
+   */
+  quantifiers?: boolean | undefined;
+  /**
+   * Do not strip backslashes that were used for escaping from the result.
+   *
+   * @default undefined
+   */
+  keepEscaping?: boolean | undefined;
+  /**
+   * Do not strip quotes from the result.
+   *
+   * @default undefined
+   */
+  keepQuotes?: boolean | undefined;
+}
+
+// Ambient type override for braces to allow string or string[] as pattern
+declare module "braces" {
+  function braces(pattern: string | string[], options?: Options): string[];
+  namespace braces {
+    function expand(pattern: string | string[]): string[];
+  }
path: packages/cli-v3/src/build/braces.d.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`braces.expand` overload should accept the same `options` argument as the main function**

The real API is `braces.expand(pattern, options?)`.  
Omitting the second parameter breaks type-checking for legitimate calls (`braces.expand('{a,b}', {rangeLimit: 10})`).

```diff
-    function expand(pattern: string | string[]): string[];
+    function expand(pattern: string | string[], options?: Options): string[];
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  namespace braces {
-    function expand(pattern: string | string[]): string[];
+    function expand(pattern: string | string[], options?: Options): string[];
  }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/cli-v3/src/build/braces.d.ts at lines 120 to 122, the braces.expand
function declaration is missing the optional options parameter that the main
function accepts. Update the function signature to include the second optional
parameter for options, matching the main function's API, so calls like
braces.expand('{a,b}', {rangeLimit: 10}) type-check correctly.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T10:18:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -248,6 +264,141 @@ function createExternalsCollector(
             }
           });
         });
+
+        if (resolvedConfig.build?.autoDetectExternal) {
+          build.onResolve(
+            { filter: /.*/, namespace: "file" },
+            async (args: esbuild.OnResolveArgs): Promise<esbuild.OnResolveResult | undefined> => {
+              if (!isBareModuleImport(args.path)) {
+                // Not an npm package
+                return;
+              }
+
+              if (isBuiltinModule(args.path)) {
+                // Builtin module
+                return;
+              }
+
+              // Try to resolve the actual file path
+              const [resolveError, resolvedPath] = await tryCatch(
+                resolveModule(args.path, args.resolveDir)
+              );
+
+              if (resolveError) {
+                logger.debug("[externals][auto] Resolve module error", {
+                  path: args.path,
+                  resolveError,
+                });
+                return;
+              }
+
+              // Find nearest package.json
+              const packageJsonPath = await findNearestPackageJson(resolvedPath, packageJsonCache);
+
+              if (!packageJsonPath) {
+                logger.debug("[externals][auto] Failed to resolve package.json path", {
+                  path: args.path,
+                  resolvedPath,
+                });
+                return;
+              }
+
+              const packageRoot = dirname(packageJsonPath);
+
+              // Check cache first
+              if (isExternalCache.has(packageRoot)) {
+                const isExternal = isExternalCache.get(packageRoot);
+
+                if (isExternal) {
+                  return { path: args.path, external: true };
+                }
+
+                return;
+              }
+
+              const [readError, packageJson] = await tryCatch(readPackageJSON(packageRoot));
+
+              if (readError) {
+                logger.debug("[externals][auto] Unable to read package.json", {
+                  error: readError,
+                  packageRoot,
+                });
+
+                isExternalCache.set(packageRoot, false);
+                return;
+              }
+
+              const packageName = packageJson.name;
+              const packageVersion = packageJson.version;
+
+              if (!packageName || !packageVersion) {
+                logger.debug("[externals][auto] No package name or version found in package.json", {
+                  packageRoot,
+                  packageJson,
+                });
+
+                return;
+              }
+
+              const markExternal = (reason: string): esbuild.OnResolveResult => {
+                const detectedPackage = {
+                  name: packageName,
+                  path: packageRoot,
+                  version: packageVersion,
+                } satisfies CollectedExternal;
+
+                logger.debug(`[externals][auto] Marking as external - ${reason}`, {
+                  detectedPackage,
+                });
+
+                externals.push(detectedPackage);
+
+                // Cache the result
+                isExternalCache.set(packageRoot, true);
+
+                return { path: args.path, external: true };
+              };
+
+              // If the path ends with .wasm or .node, we should mark it as external
+              if (args.path.endsWith(".wasm") || args.path.endsWith(".node")) {
+                return markExternal("path ends with .wasm or .node");
+              }
+
path: packages/cli-v3/src/build/externals.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use `resolvedPath` when checking for native file extensions**

`args.path` is the *import specifier*, not the resolved filename.  
Packages that expose a bare import (e.g. `"bindings"` → `bindings.node`) will slip through because `args.path` lacks the `.node`/`.wasm` suffix.

```diff
-              if (args.path.endsWith(".wasm") || args.path.endsWith(".node")) {
+              if (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node")) {
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
              // If the path ends with .wasm or .node, we should mark it as external
-              if (args.path.endsWith(".wasm") || args.path.endsWith(".node")) {
+              if (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node")) {
                return markExternal("path ends with .wasm or .node");
              }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/cli-v3/src/build/externals.ts around lines 362 to 366, the code
checks args.path for .wasm or .node extensions, but args.path is the import
specifier, not the resolved filename. To fix this, replace args.path with
resolvedPath in the condition so that the check is performed on the actual
resolved filename, ensuring native file extensions are correctly detected and
marked as external.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T10:18:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -410,3 +566,87 @@ function resolveSync(id: string, resolveDir: string) {
     return esmResolveSync(id, { url: resolveDir });
   }
 }
+
+function isBareModuleImport(path: string): boolean {
+  const excludes = [".", "/", "~", "file:", "data:"];
+  return !excludes.some((exclude) => path.startsWith(exclude));
+}
+
+function isBuiltinModule(path: string): boolean {
+  return builtinModules.includes(path.replace("node:", ""));
+}
+
+async function isMainPackageJson(filePath: string): Promise<boolean> {
+  try {
+    const packageJson = await readPackageJSON(filePath);
+
+    // Allowlist of non-informative fields that can appear with 'type: module | commonjs' in marker package.json files
+    const markerFields = new Set([
+      "type",
+      "sideEffects",
+      "browser",
+      "main",
+      "module",
+      "react-native",
+      "name",
+    ]);
+
+    if (!packageJson.type) {
+      return true;
+    }
+
+    const keys = Object.keys(packageJson);
+    if (keys.every((k) => markerFields.has(k))) {
+      return false; // type marker
+    }
+
+    return true;
+  } catch (error) {
+    if (!(error instanceof Error)) {
+      logger.debug("[externals][containsEsmTypeMarkers] Unknown error", {
+        error,
+      });
+
+      return false;
+    }
+
+    if ("code" in error && error.code !== "ENOENT") {
+      logger.debug("[externals][containsEsmTypeMarkers] Error", {
+        error: error.message,
+      });
+    }
+
+    return false;
+  }
+}
path: packages/cli-v3/src/build/externals.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider adding more test cases for the isMainPackageJson function.**

The function distinguishes between main package.json files and type marker files, which is important for finding the actual package root. However, there might be edge cases with different fields combinations that could be better handled.

---

🏁 Script executed:

```shell
#!/bin/bash
# Let's find some examples of minimal package.json files in node_modules to verify our heuristic

echo "Looking for minimal package.json files that might be type markers:"
find . -path "*/node_modules/*" -name "package.json" -exec cat {} \; | grep -l "\"type\"" | head -n 10 | xargs cat
```

Length of output: 6139

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for existing test coverage of isMainPackageJson in the CLI v3 package

# Locate the function definition and any references
grep -R "isMainPackageJson" -n packages/cli-v3

# Look for test files and check if they reference the function
find packages/cli-v3 -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) -print0 \
  | xargs -0 grep -H "isMainPackageJson" || echo "No references to isMainPackageJson found in test files"
```

Length of output: 476

---



</details>

**Add test coverage for `isMainPackageJson` in `packages/cli-v3/src/build/externals.ts`**  

No existing tests reference this function, so we should add dedicated unit tests to validate its behavior across edge cases:  
- File with no `type` field should return `true`  
- File with only marker fields (e.g. `type`, `main`, `name`) should return `false`  
- File with `type` plus at least one non-marker field should return `true`  
- Non-existent or unreadable file path should return `false`  
- Invalid JSON or parsing errors should return `false`  

Target test file:  
- Create or update `packages/cli-v3/src/build/externals.spec.ts` (or `.test.ts`) with these scenarios.  

This will ensure our heuristic for detecting package roots remains robust.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/cli-v3/src/build/externals.ts around lines 579 to 621, the
isMainPackageJson function lacks test coverage. Add unit tests in
packages/cli-v3/src/build/externals.spec.ts (or .test.ts) to cover these cases:
a package.json with no "type" field returns true; a package.json containing only
marker fields returns false; a package.json with "type" plus other fields
returns true; a non-existent or unreadable file returns false; and invalid JSON
or parsing errors return false. This will validate the function's behavior
across edge cases and improve robustness.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T14:01:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -248,6 +264,146 @@ function createExternalsCollector(
             }
           });
         });
+
+        if (resolvedConfig.build?.experimental_autoDetectExternal) {
+          build.onResolve(
+            { filter: /.*/, namespace: "file" },
+            async (args: esbuild.OnResolveArgs): Promise<esbuild.OnResolveResult | undefined> => {
+              if (!isBareModuleImport(args.path)) {
+                // Not an npm package
+                return;
+              }
+
+              if (isBuiltinModule(args.path)) {
+                // Builtin module
+                return;
+              }
+
+              if (args.path === "_sentry-debug-id-injection-stub") {
+                // Ignore sentry stub
+                return;
+              }
+
+              // Try to resolve the actual file path
+              const [resolveError, resolvedPath] = await tryCatch(
+                resolveModule(args.path, args.resolveDir)
+              );
+
+              if (resolveError) {
+                logger.debug("[externals][auto] Resolve module error", {
+                  path: args.path,
+                  resolveError,
+                });
+                return;
+              }
+
+              // Find nearest package.json
+              const packageJsonPath = await findNearestPackageJson(resolvedPath, packageJsonCache);
+
+              if (!packageJsonPath) {
+                logger.debug("[externals][auto] Failed to resolve package.json path", {
+                  path: args.path,
+                  resolvedPath,
+                });
+                return;
+              }
+
+              const packageRoot = dirname(packageJsonPath);
+
+              // Check cache first
+              if (isExternalCache.has(packageRoot)) {
+                const isExternal = isExternalCache.get(packageRoot);
+
+                if (isExternal) {
+                  return { path: args.path, external: true };
+                }
+
+                return;
+              }
+
+              const [readError, packageJson] = await tryCatch(readPackageJSON(packageRoot));
+
+              if (readError) {
+                logger.debug("[externals][auto] Unable to read package.json", {
+                  error: readError,
+                  packageRoot,
+                });
+
+                isExternalCache.set(packageRoot, false);
+                return;
+              }
+
+              const packageName = packageJson.name;
+              const packageVersion = packageJson.version;
+
+              if (!packageName || !packageVersion) {
+                logger.debug("[externals][auto] No package name or version found in package.json", {
+                  packageRoot,
+                  packageJson,
+                });
+
+                return;
+              }
+
+              const markExternal = (reason: string): esbuild.OnResolveResult => {
+                const detectedPackage = {
+                  name: packageName,
+                  path: packageRoot,
+                  version: packageVersion,
+                } satisfies CollectedExternal;
+
+                logger.debug(`[externals][auto] Marking as external - ${reason}`, {
+                  detectedPackage,
+                });
+
+                externals.push(detectedPackage);
+
+                // Cache the result
+                isExternalCache.set(packageRoot, true);
+
+                return { path: args.path, external: true };
+              };
+
+              // If the path ends with .wasm or .node, we should mark it as external
+              if (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node")) {
+                return markExternal("path ends with .wasm or .node");
+              }
path: packages/cli-v3/src/build/externals.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add null check before using resolvedPath**

While you correctly use resolvedPath to check for native file extensions (addressing a previous review comment), ensure it's not null/undefined before calling `.endsWith()`.

```diff
-if (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node")) {
+if (resolvedPath && (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node"))) {
  return markExternal("path ends with .wasm or .node");
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
              if (resolvedPath && (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node"))) {
                return markExternal("path ends with .wasm or .node");
              }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/cli-v3/src/build/externals.ts around lines 368 to 370, resolvedPath
is used without checking if it is null or undefined before calling .endsWith(),
which can cause runtime errors. Add a null check to ensure resolvedPath is
defined before performing the .endsWith() checks for ".wasm" or ".node"
extensions.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T15:20:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -248,6 +264,146 @@ function createExternalsCollector(
             }
           });
         });
+
+        if (resolvedConfig.build?.experimental_autoDetectExternal) {
+          build.onResolve(
+            { filter: /.*/, namespace: "file" },
+            async (args: esbuild.OnResolveArgs): Promise<esbuild.OnResolveResult | undefined> => {
+              if (!isBareModuleImport(args.path)) {
+                // Not an npm package
+                return;
+              }
+
+              if (isBuiltinModule(args.path)) {
+                // Builtin module
+                return;
+              }
+
+              if (args.path === "_sentry-debug-id-injection-stub") {
+                // Ignore sentry stub
+                return;
+              }
+
+              // Try to resolve the actual file path
+              const [resolveError, resolvedPath] = await tryCatch(
+                resolveModule(args.path, args.resolveDir)
+              );
+
+              if (resolveError) {
+                logger.debug("[externals][auto] Resolve module error", {
+                  path: args.path,
+                  resolveError,
+                });
+                return;
+              }
+
+              // Find nearest package.json
+              const packageJsonPath = await findNearestPackageJson(resolvedPath, packageJsonCache);
+
+              if (!packageJsonPath) {
+                logger.debug("[externals][auto] Failed to resolve package.json path", {
+                  path: args.path,
+                  resolvedPath,
+                });
+                return;
+              }
+
+              const packageRoot = dirname(packageJsonPath);
+
+              // Check cache first
+              if (isExternalCache.has(packageRoot)) {
+                const isExternal = isExternalCache.get(packageRoot);
+
+                if (isExternal) {
+                  return { path: args.path, external: true };
+                }
+
+                return;
+              }
+
+              const [readError, packageJson] = await tryCatch(readPackageJSON(packageRoot));
+
+              if (readError) {
+                logger.debug("[externals][auto] Unable to read package.json", {
+                  error: readError,
+                  packageRoot,
+                });
+
+                isExternalCache.set(packageRoot, false);
+                return;
+              }
+
+              const packageName = packageJson.name;
+              const packageVersion = packageJson.version;
+
+              if (!packageName || !packageVersion) {
+                logger.debug("[externals][auto] No package name or version found in package.json", {
+                  packageRoot,
+                  packageJson,
+                });
+
+                return;
+              }
+
+              const markExternal = (reason: string): esbuild.OnResolveResult => {
+                const detectedPackage = {
+                  name: packageName,
+                  path: packageRoot,
+                  version: packageVersion,
+                } satisfies CollectedExternal;
+
+                logger.debug(`[externals][auto] Marking as external - ${reason}`, {
+                  detectedPackage,
+                });
+
+                externals.push(detectedPackage);
+
+                // Cache the result
+                isExternalCache.set(packageRoot, true);
+
+                return { path: args.path, external: true };
+              };
+
+              // If the path ends with .wasm or .node, we should mark it as external
+              if (resolvedPath.endsWith(".wasm") || resolvedPath.endsWith(".node")) {
+                return markExternal("path ends with .wasm or .node");
+              }
+
+              // Check files, main, module fields for native files
+              const files = Array.isArray(packageJson.files) ? packageJson.files : [];
+              const fields = [packageJson.main, packageJson.module, packageJson.browser].filter(
+                (f): f is string => typeof f === "string"
+              );
+              const allFiles = files.concat(fields);
+
+              // We need to expand any braces in the files array, e.g. ["{js,ts}"] -> ["js", "ts"]
+              const allFilesExpanded = braces(allFiles, { expand: true });
+
+              // Use a regexp to match native-related extensions
+              const nativeExtRegexp = /\.(wasm|node|gyp|c|cc|cpp|cxx|h|hpp|hxx)$/;
+              const hasNativeFile = allFilesExpanded.some((file) => nativeExtRegexp.test(file));
+
+              if (hasNativeFile) {
+                return markExternal("has native file");
+              }
path: packages/cli-v3/src/build/externals.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add error handling for braces expansion**

The braces library may throw errors for malformed patterns. Consider adding a try/catch block when expanding file patterns.

```diff
-const allFilesExpanded = braces(allFiles, { expand: true });
+let allFilesExpanded: string[] = [];
+try {
+  allFilesExpanded = braces(allFiles, { expand: true });
+} catch (error) {
+  logger.debug("[externals][auto] Error expanding brace patterns", {
+    error,
+    allFiles,
+  });
+  // Fall back to using the original files array
+  allFilesExpanded = allFiles;
+}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
              const files = Array.isArray(packageJson.files) ? packageJson.files : [];
              const fields = [packageJson.main, packageJson.module, packageJson.browser].filter(
                (f): f is string => typeof f === "string"
              );
              const allFiles = files.concat(fields);

              // We need to expand any braces in the files array, e.g. ["{js,ts}"] -> ["js", "ts"]
              let allFilesExpanded: string[] = [];
              try {
                allFilesExpanded = braces(allFiles, { expand: true });
              } catch (error) {
                logger.debug("[externals][auto] Error expanding brace patterns", {
                  error,
                  allFiles,
                });
                // Fall back to using the original files array
                allFilesExpanded = allFiles;
              }

              // Use a regexp to match native-related extensions
              const nativeExtRegexp = /\.(wasm|node|gyp|c|cc|cpp|cxx|h|hpp|hxx)$/;
              const hasNativeFile = allFilesExpanded.some((file) => nativeExtRegexp.test(file));

              if (hasNativeFile) {
                return markExternal("has native file");
              }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In packages/cli-v3/src/build/externals.ts around lines 373 to 388, the braces
function call that expands file patterns can throw errors if the patterns are
malformed. To fix this, wrap the braces(allFiles, { expand: true }) call in a
try/catch block to handle any exceptions gracefully. In the catch block, log or
handle the error appropriately and ensure the code can continue or fail safely
without crashing.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-21T15:20:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-21T10:13:59Z
body: ###  🦋  Changeset detected

Latest commit: 3ff83669759bf767406da610e7bdeb556978b649

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



Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/triggerdotdev/trigger.dev/new/feat/auto-externals?filename=.changeset/many-zoos-marry.md&value=---%0A%22%40fake-scope%2Ffake-pkg%22%3A%20patch%0A---%0A%0AAdd%20experimental%20externals%20detection%0A)


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-21T10:14:02Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change introduces an experimental configuration option, `experimental_autoDetectExternal`, to the trigger system's build configuration within both the `trigger.dev` and `@trigger.dev/core` packages. When enabled, this option activates an automatic detection mechanism in the build process to identify and mark native or binary dependencies (such as `.wasm` or `.node` files) as external, preventing them from being bundled. The implementation adds caching for package metadata and external status, updates logging for diagnostics, and includes new helper functions for module and package analysis. Additionally, the `braces` package is added as a runtime dependency with accompanying TypeScript type declarations. The deployment command's build process is refactored to use a `tryCatch` utility for explicit error handling and improved build event logging.

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!NOTE]
> <details>
> <summary>⚡️ AI Code Reviews for VS Code, Cursor, Windsurf</summary>
> 
> CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
> Learn more [here](http://coderabbit.ai/ide).
> 
> </details>

---

> [!NOTE]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=triggerdotdev/trigger.dev&utm_content=2083):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR