-
-
Notifications
You must be signed in to change notification settings - Fork 991
fix(cli-v3): detect package manager (yarn/pnpm/npm) and lockfile for correct deploy builds #2988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@trigger.dev/cli-v3": patch | ||
| --- | ||
|
|
||
| Fix `trigger deploy` to detect and use the correct package manager (Yarn, pnpm, npm) and lockfile for builds. This fixes issues with Yarn Workspaces and ensures reproducible builds. (#2914) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
|
|
||
| import { describe, it, expect } from "vitest"; | ||
| import { generateContainerfile, GenerateContainerfileOptions } from "./buildImage.js"; | ||
|
|
||
| describe("generateContainerfile", () => { | ||
| const defaultOptions: GenerateContainerfileOptions = { | ||
| runtime: "node", | ||
| build: { | ||
| env: {}, | ||
| }, | ||
| image: {}, | ||
| indexScript: "index.js", | ||
| entrypoint: "entrypoint.js", | ||
| }; | ||
|
|
||
| it("should generate npm install command by default", async () => { | ||
| const dockerfile = await generateContainerfile(defaultOptions); | ||
| expect(dockerfile).toContain("COPY --chown=node:node package.json ./"); | ||
| expect(dockerfile).toContain("RUN npm i --no-audit --no-fund --no-save --no-package-lock"); | ||
| }); | ||
|
|
||
| it("should generate yarn install command when packageManager is yarn", async () => { | ||
| const options: GenerateContainerfileOptions = { | ||
| ...defaultOptions, | ||
| packageManager: { name: "yarn", command: "yarn", version: "1.22.19" }, | ||
| }; | ||
| const dockerfile = await generateContainerfile(options); | ||
| expect(dockerfile).toContain("RUN yarn install"); | ||
| }); | ||
|
|
||
| it("should generate pnpm install command when packageManager is pnpm", async () => { | ||
| const options: GenerateContainerfileOptions = { | ||
| ...defaultOptions, | ||
| packageManager: { name: "pnpm", command: "pnpm", version: "8.6.0" }, | ||
| }; | ||
| const dockerfile = await generateContainerfile(options); | ||
| expect(dockerfile).toContain("RUN corepack enable"); | ||
| expect(dockerfile).toContain("RUN pnpm install"); | ||
| }); | ||
|
|
||
| it("should copy lockfile if provided", async () => { | ||
| const options: GenerateContainerfileOptions = { | ||
| ...defaultOptions, | ||
| packageManager: { name: "yarn", command: "yarn", version: "1.22.19" }, | ||
| lockfilePath: "yarn.lock", | ||
| }; | ||
| const dockerfile = await generateContainerfile(options); | ||
| expect(dockerfile).toContain("COPY --chown=node:node yarn.lock ./"); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { logger } from "../utilities/logger.js"; | ||
| import { PackageManager } from "nypm"; | ||
| import { depot } from "@depot/cli"; | ||
| import { x } from "tinyexec"; | ||
| import { BuildManifest, BuildRuntime } from "@trigger.dev/core/v3/schemas"; | ||
|
|
@@ -550,13 +551,12 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu | |
| options.noCache ? "--no-cache" : undefined, | ||
| ...(useRegistryCache | ||
| ? [ | ||
| "--cache-to", | ||
| `type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${ | ||
| cacheCompression === "zstd" ? ",compression=zstd" : "" | ||
| }`, | ||
| "--cache-from", | ||
| `type=registry,ref=${projectCacheRef}`, | ||
| ] | ||
| "--cache-to", | ||
| `type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${cacheCompression === "zstd" ? ",compression=zstd" : "" | ||
| }`, | ||
| "--cache-from", | ||
| `type=registry,ref=${projectCacheRef}`, | ||
| ] | ||
| : []), | ||
| "--output", | ||
| outputOptions.join(","), | ||
|
|
@@ -683,6 +683,8 @@ export type GenerateContainerfileOptions = { | |
| image: BuildManifest["image"]; | ||
| indexScript: string; | ||
| entrypoint: string; | ||
| packageManager?: PackageManager | null; | ||
| lockfilePath?: string; | ||
| }; | ||
|
|
||
| const BASE_IMAGE: Record<BuildRuntime, string> = { | ||
|
|
@@ -869,7 +871,11 @@ ENV NODE_ENV=production | |
| ENV NPM_CONFIG_UPDATE_NOTIFIER=false | ||
|
|
||
| COPY --chown=node:node package.json ./ | ||
| RUN npm i --no-audit --no-fund --no-save --no-package-lock | ||
| ${options.lockfilePath | ||
| ? `COPY --chown=node:node ${options.lockfilePath} ./` | ||
| : "# No lockfile path provided" | ||
| } | ||
| ${getInstallCommand(options.packageManager)} | ||
|
|
||
| # Now copy all the files | ||
| # IMPORTANT: Do this after running npm install because npm i will wipe out the node_modules directory | ||
|
|
@@ -1161,3 +1167,21 @@ function getOutputOptions({ | |
|
|
||
| return outputOptions; | ||
| } | ||
|
|
||
| function getInstallCommand(packageManager?: PackageManager | null) { | ||
| switch (packageManager?.name) { | ||
| case "yarn": { | ||
| return "RUN yarn install"; | ||
| } | ||
| case "pnpm": { | ||
| return ` | ||
| RUN corepack enable | ||
| RUN pnpm install | ||
| `; | ||
| } | ||
| case "npm": | ||
| default: { | ||
| return "RUN npm i --no-audit --no-fund --no-save --no-package-lock"; | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+1171
to
+1187
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Goal: “fail the build if the lockfile would change”Use the package manager’s CI / immutable install mode so dependency resolution is taken from the lockfile and the install aborts if the lockfile and manifests are out of sync. npm (package-lock.json / npm-shrinkwrap.json)
YarnYarn 1 (Classic)
Yarn 2+ (Berry / modern Yarn)
pnpm (pnpm-lock.yaml)
Reproducible-build best practices (works across npm/Yarn/pnpm)
Quick “CI install” command cheat-sheet# npm
npm ci
# Yarn 1
yarn install --frozen-lockfile
# Yarn 2+
yarn install --immutable
# pnpm
pnpm install --frozen-lockfileSources: npm 🏁 Script executed: # Search for issue `#2914` reference in the repo
rg "2914" --type ts --type md --type json
# Look for context in buildImage.ts around the install command generation
fd buildImage.ts -x cat -n {} | head -100Repository: triggerdotdev/trigger.dev Length of output: 3348 🏁 Script executed: # Check if there are any GitHub issue links or comments in the relevant files
rg "frozen-lockfile|npm ci|reproducible.*build" packages/cli-v3/src/deploy/ -A 2 -B 2Repository: triggerdotdev/trigger.dev Length of output: 51 🏁 Script executed: # Find and examine buildImage.ts more carefully
fd buildImage.ts -x wc -l {}Repository: triggerdotdev/trigger.dev Length of output: 116 🏁 Script executed: # Look at lines around 1171-1187 in buildImage.ts
fd buildImage.ts -x sed -n '1150,1200p' {}Repository: triggerdotdev/trigger.dev Length of output: 892 🏁 Script executed: # Search for any issue/PR references related to reproducible builds or frozen-lockfile
rg "issue|PR|frozen.*lock|reproducible" packages/cli-v3/src/deploy/buildImage.ts -B 2 -A 2Repository: triggerdotdev/trigger.dev Length of output: 3565 🏁 Script executed: # Check if there's a .yarn-version or .node-version or similar to understand which Yarn version is used
fd -e "versions" -e "npmrc" -e "yarnrc" -e "pnpmfile" packages/cli-v3/Repository: triggerdotdev/trigger.dev Length of output: 438 🏁 Script executed: # Check package.json for packageManager field to see which version is pinned
rg '"packageManager"' packages/cli-v3/package.jsonRepository: triggerdotdev/trigger.dev Length of output: 51 🏁 Script executed: # Check root package.json for packageManager field
cat package.json | grep -A 2 -B 2 "packageManager"Repository: triggerdotdev/trigger.dev Length of output: 184 🏁 Script executed: # Check what lockfiles exist in repo to determine package manager versions
git ls-files | rg -E "yarn\.lock|pnpm-lock\.yaml|package-lock\.json" | head -5Repository: triggerdotdev/trigger.dev Length of output: 465 🏁 Script executed: # Check for lockfiles to understand which package managers are used
git ls-files | grep -E "yarn\.lock|pnpm-lock\.yaml|package-lock\.json"Repository: triggerdotdev/trigger.dev Length of output: 956 🏁 Script executed: # Check if there's any yarn version specification
cat .yarnrc.yml 2>/dev/null || cat .yarnrc 2>/dev/null || echo "No yarn config found"Repository: triggerdotdev/trigger.dev Length of output: 89 🏁 Script executed: # Look at the context around lockfile copying in buildImage.ts to understand the full picture
sed -n '860,900p' packages/cli-v3/src/deploy/buildImage.tsRepository: triggerdotdev/trigger.dev Length of output: 1012 Install commands don't ensure reproducible builds. The implementation should use CI/immutable install modes to fail if lockfiles are out of sync: 🔧 Proposed fix function getInstallCommand(packageManager?: PackageManager | null) {
switch (packageManager?.name) {
case "yarn": {
- return "RUN yarn install";
+ return "RUN yarn install --frozen-lockfile";
}
case "pnpm": {
- return `
- RUN corepack enable
- RUN pnpm install
- `;
+ return "RUN corepack enable && pnpm install --frozen-lockfile";
}
case "npm":
default: {
- return "RUN npm i --no-audit --no-fund --no-save --no-package-lock";
+ return "RUN npm ci";
}
}
}🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 pnpm install command generates inconsistent Dockerfile formatting with extra whitespace
The
getInstallCommandfunction for pnpm returns a template literal with leading newlines and 6 spaces of indentation before eachRUNcommand.Click to expand
Issue
The pnpm case at
buildImage.ts:1176-1180returns:This produces output with leading/trailing newlines and inconsistent indentation:
Comparison
The npm and yarn cases return clean single-line strings without extra whitespace:
"RUN yarn install"(line 1174)"RUN npm i --no-audit --no-fund --no-save --no-package-lock"(line 1184)Impact
While Docker technically accepts indented
RUNcommands, this produces messy Dockerfile output that is inconsistent with the rest of the generated file. The extra blank lines and indentation make the Dockerfile harder to read and debug.Recommendation: Use consistent formatting without extra whitespace:
Was this helpful? React with 👍 or 👎 to provide feedback.