fix(build): address review comments and add unit tests for additionalFiles

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

Co-authored-by: Eric Allam <ericallam@users.noreply.github.com>
This commit is contained in:
claude[bot]
2026-01-09 10:04:51 +00:00
parent 55aba75c47
commit 345722e1c7
4 changed files with 115 additions and 7 deletions
+5 -2
View File
@@ -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"
@@ -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);
}
@@ -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("..");
});
});
});
+8
View File
@@ -0,0 +1,8 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["test/**/*.test.ts", "src/**/*.test.ts"],
globals: true,
},
});