diff --git a/packages/build/package.json b/packages/build/package.json index d5ec39213..da42a211f 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -74,7 +74,9 @@ "dev": "tshy --watch", "typecheck": "tsc --noEmit -p tsconfig.src.json", "update-version": "tsx ../../scripts/updateVersion.ts", - "check-exports": "attw --pack ." + "check-exports": "attw --pack .", + "test": "vitest run", + "test:dev": "vitest" }, "dependencies": { "@prisma/config": "^6.10.0", @@ -91,7 +93,8 @@ "esbuild": "^0.23.0", "rimraf": "6.0.1", "tshy": "^3.0.2", - "tsx": "4.17.0" + "tsx": "4.17.0", + "vitest": "^2.0.0" }, "engines": { "node": ">=18.20.0" diff --git a/packages/build/src/internal/additionalFiles.ts b/packages/build/src/internal/additionalFiles.ts index 9bde93f46..c92b848dc 100644 --- a/packages/build/src/internal/additionalFiles.ts +++ b/packages/build/src/internal/additionalFiles.ts @@ -1,7 +1,7 @@ import { BuildManifest } from "@trigger.dev/core/v3"; import { BuildContext } from "@trigger.dev/core/v3/build"; import { copyFile, mkdir } from "node:fs/promises"; -import { dirname, isAbsolute, join, posix, relative, resolve } from "node:path"; +import { dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path"; import { glob } from "tinyglobby"; export type AdditionalFilesOptions = { @@ -76,7 +76,8 @@ async function findStaticAssetFiles( // Extracts the base directory from a glob pattern (the non-wildcard prefix). // For example: "../shared/**" -> "../shared", "./assets/*.txt" -> "./assets" // For specific files without globs: "./config/settings.json" -> "./config" (parent dir) -function getGlobBase(pattern: string): string { +// For single-part patterns: "file.txt" -> "." (current dir) +export function getGlobBase(pattern: string): string { const parts = pattern.split(/[/\\]/); const baseParts: string[] = []; let hasGlobCharacters = false; @@ -92,8 +93,9 @@ function getGlobBase(pattern: string): string { // If no glob characters were found, the pattern is a specific file path. // Return the parent directory so that relative() preserves the filename. - if (!hasGlobCharacters && baseParts.length > 1) { - baseParts.pop(); // Remove the filename, keep the directory + // For single-part patterns (just a filename), return "." to indicate current directory. + if (!hasGlobCharacters) { + baseParts.pop(); // Remove the filename, keep the directory (or empty for single-part) } return baseParts.length > 0 ? baseParts.join(posix.sep) : "."; @@ -129,8 +131,9 @@ async function findStaticAssetsForMatcher( pathInsideDestinationDir = join(options.destination, relativeToGlobBase); } else { // Default behavior: compute relative path from cwd and strip ".." segments + // Use platform-specific separator for splitting since path.relative() returns platform separators pathInsideDestinationDir = relative(cwd, file) - .split(posix.sep) + .split(sep) .filter((p) => p !== "..") .join(posix.sep); } diff --git a/packages/build/test/additionalFiles.test.ts b/packages/build/test/additionalFiles.test.ts new file mode 100644 index 000000000..b8fea20d9 --- /dev/null +++ b/packages/build/test/additionalFiles.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect } from "vitest"; +import { getGlobBase } from "../src/internal/additionalFiles.js"; + +describe("getGlobBase", () => { + describe("glob patterns with wildcards", () => { + it("extracts base from parent directory glob pattern", () => { + expect(getGlobBase("../shared/**")).toBe("../shared"); + }); + + it("extracts base from relative directory glob pattern", () => { + expect(getGlobBase("./assets/*.txt")).toBe("./assets"); + }); + + it("extracts base from nested directory glob pattern", () => { + expect(getGlobBase("files/nested/**/*.js")).toBe("files/nested"); + }); + + it("returns current directory for top-level glob", () => { + expect(getGlobBase("**/*.js")).toBe("."); + }); + + it("returns current directory for star pattern", () => { + expect(getGlobBase("*.js")).toBe("."); + }); + + it("handles question mark wildcard", () => { + expect(getGlobBase("./src/?/*.ts")).toBe("./src"); + }); + + it("handles bracket patterns", () => { + expect(getGlobBase("./src/[abc]/*.ts")).toBe("./src"); + }); + + it("handles brace expansion patterns", () => { + expect(getGlobBase("./src/{a,b}/*.ts")).toBe("./src"); + }); + + it("handles deeply nested patterns", () => { + expect(getGlobBase("a/b/c/d/**")).toBe("a/b/c/d"); + }); + }); + + describe("specific file paths without globs", () => { + it("returns parent directory for file in subdirectory", () => { + expect(getGlobBase("./config/settings.json")).toBe("./config"); + }); + + it("returns parent directory for file in nested subdirectory", () => { + expect(getGlobBase("../shared/utils/helpers.ts")).toBe("../shared/utils"); + }); + + it("returns current directory for single-part filename", () => { + expect(getGlobBase("file.txt")).toBe("."); + }); + + it("returns current directory for filename starting with dot", () => { + expect(getGlobBase(".env")).toBe("."); + }); + + it("returns parent directory for explicit relative path to file", () => { + expect(getGlobBase("./file.txt")).toBe("."); + }); + + it("returns parent directories for parent reference to file", () => { + expect(getGlobBase("../file.txt")).toBe(".."); + }); + + it("handles multiple parent references", () => { + expect(getGlobBase("../../config/app.json")).toBe("../../config"); + }); + }); + + describe("edge cases", () => { + it("returns current directory for empty string", () => { + expect(getGlobBase("")).toBe("."); + }); + + it("handles Windows-style backslashes", () => { + expect(getGlobBase("..\\shared\\**")).toBe("../shared"); + }); + + it("handles mixed forward and back slashes", () => { + expect(getGlobBase("../shared\\nested/**")).toBe("../shared/nested"); + }); + + it("handles patterns with only dots", () => { + expect(getGlobBase("./")).toBe("."); + }); + + it("handles parent directory reference only", () => { + expect(getGlobBase("../")).toBe(".."); + }); + }); +}); diff --git a/packages/build/vitest.config.ts b/packages/build/vitest.config.ts new file mode 100644 index 000000000..7f850a544 --- /dev/null +++ b/packages/build/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts", "src/**/*.test.ts"], + globals: true, + }, +});