diff --git a/server/src/__tests__/cloud-bundled-package-digest.test.ts b/server/src/__tests__/cloud-bundled-package-digest.test.ts index 67491571762..7978c46795e 100644 --- a/server/src/__tests__/cloud-bundled-package-digest.test.ts +++ b/server/src/__tests__/cloud-bundled-package-digest.test.ts @@ -63,6 +63,14 @@ describe("bounded current-image package dependency inventory", () => { expect(cloudBundledPackageDigest(image.provider, { catalogRoot: image.catalogRoot })).toBe(first); }); + it.each(["", ".pnpm", ".pnpm/node_modules"])("rejects aggregate image dependency targets: %s", (aggregate) => { + const image = fixture(); + const target = path.join(image.root, "node_modules", aggregate); + fs.mkdirSync(target, { recursive: true }); + link(target, path.join(image.sdk, "node_modules", "aggregate")); + expect(() => cloudBundledPackageDigest(image.provider, { catalogRoot: image.catalogRoot })).toThrow("select an individual package"); + }); + it("accepts the shipped provider's observed entry class", () => { const image = fixture(); const directory = path.join(image.provider, "many"); fs.mkdirSync(directory); diff --git a/server/src/services/cloud-bundled-package-digest.ts b/server/src/services/cloud-bundled-package-digest.ts index 47d3f209353..40cacb3fa11 100644 --- a/server/src/services/cloud-bundled-package-digest.ts +++ b/server/src/services/cloud-bundled-package-digest.ts @@ -41,6 +41,10 @@ export function cloudBundledPackageDigest(packageRoot: string, options: { catalo logical(target); if (imageNodeModules) { const relative = path.relative(imageNodeModules, target).split(path.sep); + if ((relative.length === 1 && relative[0] === "") + || (relative[0] === ".pnpm" && (relative.length === 1 || (relative.length === 2 && relative[1] === "node_modules")))) { + throw new Error("Image dependency must select an individual package"); + } if (relative[0] === ".pnpm" && relative.length > 2 && relative[1] && relative[1] !== "node_modules") { walk(path.join(imageNodeModules, ".pnpm", relative[1])); return;