diff --git a/change/change-e975674f-161c-4841-9fec-9751de3638dd.json b/change/change-e975674f-161c-4841-9fec-9751de3638dd.json new file mode 100644 index 000000000..4476bc8d1 --- /dev/null +++ b/change/change-e975674f-161c-4841-9fec-9751de3638dd.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "type": "minor", + "comment": "Support referencing files in a specific package or at the workspace level in inputs", + "packageName": "@lage-run/hasher", + "email": "dobes@formative.com", + "dependentChangeType": "patch" + } + ] +} \ No newline at end of file diff --git a/packages/hasher/src/PackageTree.ts b/packages/hasher/src/PackageTree.ts index e770093bc..b96156710 100644 --- a/packages/hasher/src/PackageTree.ts +++ b/packages/hasher/src/PackageTree.ts @@ -110,7 +110,11 @@ export class PackageTree { getPackageFiles(packageName: string, patterns: string[]) { const { root, packageInfos } = this.options; - const packagePath = path.relative(root, path.dirname(packageInfos[packageName].packageJsonPath)).replace(/\\/g, "/"); + // Look up the directory of the specified package. If packageName is "", that means they want to find a file + // relative to the root workspace. + const packagePath = packageName + ? path.relative(root, path.dirname(packageInfos[packageName].packageJsonPath)).replace(/\\/g, "/") + : ""; const packageFiles = this.#packageFiles[packagePath]; diff --git a/packages/hasher/src/__tests__/TargetHasher.test.ts b/packages/hasher/src/__tests__/TargetHasher.test.ts index f8411792d..d0bbdec49 100644 --- a/packages/hasher/src/__tests__/TargetHasher.test.ts +++ b/packages/hasher/src/__tests__/TargetHasher.test.ts @@ -126,6 +126,52 @@ describe("The main Hasher class", () => { monorepo2.cleanup(); }); + it("creates different hashes when a src file has changed for a specific package", async () => { + const monorepo1 = await setupFixture("monorepo"); + const hasher = new TargetHasher({ root: monorepo1.root, environmentGlob: [] }); + const target = createTarget(monorepo1.root, "package-a", "build"); + target.inputs = ["**/*", "package-b#src/index.ts"]; + + const hash = await getHash(hasher, target); + + const monorepo2 = await setupFixture("monorepo"); + await monorepo2.commitFiles({ "packages/package-b/src/index.ts": "console.log('hello world');" }); + + const hasher2 = new TargetHasher({ root: monorepo2.root, environmentGlob: [] }); + const target2 = createTarget(monorepo2.root, "package-a", "build"); + target2.inputs = ["**/*", "package-b#src/index.ts"]; + + const hash2 = await getHash(hasher2, target2); + + expect(hash).not.toEqual(hash2); + + monorepo1.cleanup(); + monorepo2.cleanup(); + }); + + it("creates different hashes when a src file has changed in the root package", async () => { + const monorepo1 = await setupFixture("monorepo"); + const hasher = new TargetHasher({ root: monorepo1.root, environmentGlob: [] }); + const target = createTarget(monorepo1.root, "package-a", "build"); + target.inputs = ["**/*", "#config.txt"]; + + const hash = await getHash(hasher, target); + + const monorepo2 = await setupFixture("monorepo"); + await monorepo2.commitFiles({ "config.txt": "hello" }); + + const hasher2 = new TargetHasher({ root: monorepo2.root, environmentGlob: [] }); + const target2 = createTarget(monorepo2.root, "package-a", "build"); + target2.inputs = ["**/*", "#config.txt"]; + + const hash2 = await getHash(hasher2, target2); + + expect(hash).not.toEqual(hash2); + + monorepo1.cleanup(); + monorepo2.cleanup(); + }); + it("creates different hashes when the target has a different env glob", async () => { const monorepo1 = await setupFixture("monorepo-with-global-files"); const hasher = new TargetHasher({ root: monorepo1.root, environmentGlob: [] }); diff --git a/packages/hasher/src/expandInputPatterns.ts b/packages/hasher/src/expandInputPatterns.ts index 775938956..acc25c54e 100644 --- a/packages/hasher/src/expandInputPatterns.ts +++ b/packages/hasher/src/expandInputPatterns.ts @@ -1,7 +1,7 @@ import { type Target } from "@lage-run/target-graph"; import { type DependencyMap } from "workspace-tools"; -export function expandInputPatterns(patterns: string[], target: Target, dependencyMap: DependencyMap) { +export function expandInputPatterns(patterns: string[], target: Target, dependencyMap: DependencyMap): Record { const expandedPatterns: Record = {}; for (const pattern of patterns) { @@ -34,6 +34,13 @@ export function expandInputPatterns(patterns: string[], target: Target, dependen } } } + } else if (pattern.includes("#")) { + // In this case they specified a specific package which an input file will be pulled from + // Note that if the path starts with '#' the pkg is caluclated as "" and the file is resolved + // relative to the root workspace + const [pkg, matchPattern] = pattern.split("#"); + expandedPatterns[pkg] = expandedPatterns[pkg] ?? []; + expandedPatterns[pkg].push(matchPattern); } else { const pkg = target.packageName!; expandedPatterns[pkg] = expandedPatterns[pkg] ?? [];