Skip to content

Commit d7cb889

Browse files
committed
feat(agents): add explicit support for yarn-berry projects
1 parent ea30ed2 commit d7cb889

File tree

4 files changed

+268
-6
lines changed

4 files changed

+268
-6
lines changed

pkg/agentfs/docker.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ Please ensure your project has the appropriate dependency file, or create a Dock
7878
case ProjectTypeNodeYarn:
7979
fmt.Printf("✔ Detected Node.js project with Yarn Classic package manager\n")
8080
fmt.Printf(" Using template [%s] with Yarn v1 support\n", util.Accented("node.yarn"))
81+
case ProjectTypeNodeYarnBerry:
82+
fmt.Printf("✔ Detected Node.js project with Yarn Berry package manager\n")
83+
fmt.Printf(" Using template [%s] with Yarn v2+ and PnP support\n", util.Accented("node.yarn-berry"))
8184
case ProjectTypePythonUV:
8285
fmt.Printf("✔ Detected Python project with UV package manager\n")
8386
fmt.Printf(" Using template [%s] for faster builds\n", util.Accented("python.uv"))
@@ -123,6 +126,9 @@ Please ensure your project has the appropriate dependency file, or create a Dock
123126
} else if projectType == ProjectTypeNodeYarn {
124127
// Validate yarn project setup
125128
validateYarnProject(dir, silent)
129+
} else if projectType == ProjectTypeNodeYarnBerry {
130+
// Validate yarn berry project setup
131+
validateYarnBerryProject(dir, silent)
126132
}
127133

128134
var dockerfileContent []byte
@@ -262,6 +268,27 @@ func validateYarnProject(dir string, silent bool) {
262268
}
263269
}
264270

271+
func validateYarnBerryProject(dir string, silent bool) {
272+
yarnLockPath := filepath.Join(dir, "yarn.lock")
273+
yarnrcPath := filepath.Join(dir, ".yarnrc.yml")
274+
275+
if _, err := os.Stat(yarnLockPath); err != nil {
276+
if !silent {
277+
fmt.Printf("! Warning: Yarn Berry project detected but %s file not found\n", util.Accented("yarn.lock"))
278+
fmt.Printf(" Consider running %s to generate %s for reproducible builds\n", util.Accented("yarn install"), util.Accented("yarn.lock"))
279+
fmt.Printf(" This ensures consistent dependency versions across environments\n\n")
280+
}
281+
}
282+
283+
if _, err := os.Stat(yarnrcPath); err != nil {
284+
if !silent {
285+
fmt.Printf("! Warning: Yarn Berry project detected but %s file not found\n", util.Accented(".yarnrc.yml"))
286+
fmt.Printf(" This file contains Yarn Berry configuration and is required for proper builds\n")
287+
fmt.Printf(" Consider running %s to set up Yarn Berry\n\n", util.Accented("yarn set version berry"))
288+
}
289+
}
290+
}
291+
265292
func validateEntrypoint(dir string, dockerfileContent []byte, dockerignoreContent []byte, projectType ProjectType, settingsMap map[string]string, silent bool) ([]byte, error) {
266293
valFile := func(fileName string) (string, error) {
267294
// Parse dockerignore patterns to filter out files that won't be in build context
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# This Dockerfile creates a production-ready container for a LiveKit Node.js agent using Yarn Berry (v2+)
2+
# It uses a multi-stage build to minimize the final image size
3+
# syntax=docker/dockerfile:1
4+
5+
# === MULTI-STAGE BUILD STRUCTURE ===
6+
# Stage 1 (base): Sets up Node.js environment with Yarn Berry
7+
# Stage 2 (build): Installs dependencies and builds the application
8+
# Stage 3 (final): Copies only necessary files for runtime
9+
#
10+
# Benefits: Smaller final image without build tools and source files
11+
# Final image contains only: compiled JS, node_modules (or PnP), and runtime dependencies
12+
13+
FROM node:20-slim AS base
14+
15+
# Define the program entrypoint file where your agent is started.
16+
ARG PROGRAM_MAIN="{{.ProgramMain}}"
17+
18+
# Set the working directory where our application will live
19+
WORKDIR /app
20+
21+
# Enable Corepack to use Yarn Berry
22+
# Corepack is Node.js's official package manager manager
23+
RUN corepack enable
24+
25+
# === BUILD STAGE ===
26+
# This stage is discarded after building, keeping the final image small
27+
FROM base AS build
28+
29+
# Install CA certificates for HTTPS connections during package installation
30+
# --no-install-recommends keeps the image smaller by avoiding suggested packages
31+
RUN apt-get update -qq && apt-get install --no-install-recommends -y ca-certificates
32+
33+
# Copy Yarn Berry configuration files first
34+
COPY .yarnrc.yml ./
35+
COPY .yarn ./.yarn
36+
37+
# Copy package.json and yarn.lock for better layer caching
38+
COPY package.json yarn.lock ./
39+
40+
# Install dependencies using Yarn Berry
41+
# --immutable ensures exact versions from yarn.lock are used
42+
# --immutable-cache ensures the cache is not modified
43+
RUN yarn install --immutable
44+
45+
# Copy all application files into the build container
46+
COPY . .
47+
48+
# Build the TypeScript application
49+
# This compiles TypeScript to JavaScript and prepares for production
50+
RUN yarn run build
51+
52+
# === FINAL PRODUCTION STAGE ===
53+
# Start from the base image without build tools
54+
FROM base
55+
56+
# Copy the built application from the build stage
57+
# This includes dependencies and compiled JavaScript files
58+
COPY --from=build /app /app
59+
60+
# Copy SSL certificates for HTTPS connections at runtime
61+
COPY --from=build /etc/ssl/certs /etc/ssl/certs
62+
63+
# Expose the healthcheck port
64+
# This allows Docker and orchestration systems to check if the container is healthy
65+
EXPOSE 8081
66+
67+
# Run the application
68+
# The "start" command tells the agent to connect to LiveKit and begin waiting for jobs
69+
CMD [ "node", "{{.ProgramMain}}", "start" ]
70+
71+
# === COMMON CUSTOMIZATIONS ===
72+
#
73+
# 1. Zero-Install (PnP - Plug'n'Play) mode:
74+
# If using Yarn Berry's PnP mode, ensure .pnp.cjs is copied:
75+
# COPY --from=build /app/.pnp.* ./
76+
# And update CMD to use yarn node:
77+
# CMD ["yarn", "node", "{{.ProgramMain}}", "start"]
78+
#
79+
# 2. Installing system dependencies for native modules:
80+
# Some Node.js packages require system libraries. Add before COPY in build stage:
81+
#
82+
# # For packages with native C++ addons:
83+
# RUN apt-get update -qq && apt-get install --no-install-recommends -y \
84+
# ca-certificates \
85+
# python3 \
86+
# make \
87+
# g++ \
88+
# && rm -rf /var/lib/apt/lists/*
89+
#
90+
# 3. Different entry point locations:
91+
# - If using src/index.js: CMD ["node", "./src/index.js", "start"]
92+
# - If using dist/main.js: CMD ["node", "./dist/main.js", "start"]
93+
# - For development: CMD ["yarn", "run", "dev"]
94+
#
95+
# 4. Environment variables:
96+
# Set Node.js environment for production:
97+
# ENV NODE_ENV=production
98+
#
99+
# 5. Running as non-root user (recommended for security):
100+
# Add before the final CMD:
101+
# RUN adduser --disabled-password --gecos "" --uid 10001 appuser
102+
# USER appuser
103+
#
104+
# === TROUBLESHOOTING YARN BERRY-SPECIFIC ISSUES ===
105+
#
106+
# 1. "yarn.lock not found":
107+
# - Run `yarn install` locally to generate yarn.lock
108+
# - Commit yarn.lock to version control
109+
# - --immutable requires lock file for reproducible builds
110+
#
111+
# 2. ".yarnrc.yml not found":
112+
# - Ensure .yarnrc.yml is committed to version control
113+
# - This file contains Yarn Berry configuration
114+
# - Run `yarn set version berry` locally to migrate from Yarn Classic
115+
#
116+
# 3. PnP (Plug'n'Play) issues:
117+
# - If using PnP, ensure .pnp.cjs and .pnp.loader.mjs are copied
118+
# - Use `yarn node` instead of `node` to run with PnP
119+
# - Some packages may need to be unplugged: yarn unplug <package>
120+
#
121+
# 4. "Module not found" errors:
122+
# - Check if using PnP or node_modules (nodeLinker setting)
123+
# - For PnP: ensure .pnp.* files are copied
124+
# - For node_modules: ensure they're copied correctly
125+
# - Check for hoisting issues with nmMode setting
126+
#
127+
# 5. Zero-Install not working:
128+
# - Ensure .yarn/cache is committed (for Zero-Install)
129+
# - Or add .yarn/cache to .gitignore (for traditional install)
130+
# - Check compressionLevel setting in .yarnrc.yml
131+
#
132+
# 6. Native module compilation issues:
133+
# - Install build tools in the build stage (see customization #2)
134+
# - For node-gyp: apt-get install python3 make g++
135+
# - Consider supportedArchitectures in .yarnrc.yml
136+
#
137+
# 7. Large image sizes:
138+
# - Use node:20-alpine instead of node:20-slim for smaller base
139+
# - If using Zero-Install, consider excluding .yarn/cache from Docker
140+
# - Use nmMode: hardlinks-local for smaller node_modules
141+
#
142+
# 8. Workspace issues:
143+
# - Ensure all workspace packages are included
144+
# - Use `yarn workspaces focus` for single workspace deployment
145+
# - Check nmHoistingLimits setting for workspace hoisting
146+
#
147+
# 9. Plugin issues:
148+
# - Ensure .yarn/plugins directory is copied if using plugins
149+
# - Plugins must be compatible with production environment
150+
# - Consider disabling unnecessary plugins for production
151+
#
152+
# For more help: https://yarnpkg.com/migration/guide
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Node.js artifacts
2+
node_modules/
3+
npm-debug.log*
4+
yarn-debug.log*
5+
yarn-error.log*
6+
pnpm-debug.log*
7+
lerna-debug.log*
8+
9+
# Yarn Berry specific (exclude some, but not all)
10+
# Keep .yarn/releases for the Yarn version
11+
# Keep .yarn/plugins if using plugins
12+
# Exclude cache if not using Zero-Install
13+
.yarn/cache/
14+
.yarn/unplugged/
15+
.yarn/build-state.yml
16+
.yarn/install-state.gz
17+
18+
# PnP artifacts (keep .pnp.cjs if using PnP)
19+
# .pnp.*
20+
21+
# Build artifacts
22+
dist/
23+
build/
24+
*.tsbuildinfo
25+
26+
# Testing
27+
coverage/
28+
.nyc_output/
29+
30+
# IDE & Editor files
31+
.vscode/
32+
.idea/
33+
*.swp
34+
*.swo
35+
*~
36+
.DS_Store
37+
38+
# Yarn Berry IDE files
39+
.yarn/sdks/
40+
41+
# Environment files
42+
.env
43+
.env.local
44+
.env.*.local
45+
46+
# Docker artifacts
47+
Dockerfile*
48+
.dockerignore
49+
50+
# Git files
51+
.git/
52+
.gitignore
53+
54+
# Documentation
55+
README.md
56+
docs/
57+
58+
# CI/CD
59+
.github/
60+
.gitlab-ci.yml
61+
.travis.yml
62+
63+
# Logs
64+
logs/
65+
*.log
66+
67+
# Temporary files
68+
.tmp/
69+
.temp/
70+
tmp/
71+
temp/

pkg/agentfs/utils.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,19 @@ const (
3535
ProjectTypePythonHatch ProjectType = "python.hatch"
3636
ProjectTypePythonPDM ProjectType = "python.pdm"
3737
ProjectTypePythonPipenv ProjectType = "python.pipenv"
38-
ProjectTypeNodeNPM ProjectType = "node.npm"
39-
ProjectTypeNodePNPM ProjectType = "node.pnpm"
40-
ProjectTypeNodeYarn ProjectType = "node.yarn"
41-
ProjectTypeUnknown ProjectType = "unknown"
38+
ProjectTypeNodeNPM ProjectType = "node.npm"
39+
ProjectTypeNodePNPM ProjectType = "node.pnpm"
40+
ProjectTypeNodeYarn ProjectType = "node.yarn"
41+
ProjectTypeNodeYarnBerry ProjectType = "node.yarn-berry"
42+
ProjectTypeUnknown ProjectType = "unknown"
4243
)
4344

4445
func (p ProjectType) IsPython() bool {
4546
return p == ProjectTypePythonPip || p == ProjectTypePythonUV || p == ProjectTypePythonPoetry || p == ProjectTypePythonHatch || p == ProjectTypePythonPDM || p == ProjectTypePythonPipenv
4647
}
4748

4849
func (p ProjectType) IsNode() bool {
49-
return p == ProjectTypeNodeNPM || p == ProjectTypeNodePNPM || p == ProjectTypeNodeYarn
50+
return p == ProjectTypeNodeNPM || p == ProjectTypeNodePNPM || p == ProjectTypeNodeYarn || p == ProjectTypeNodeYarnBerry
5051
}
5152

5253
func (p ProjectType) Lang() string {
@@ -125,6 +126,12 @@ func LocateLockfile(dir string, p ProjectType) (bool, string) {
125126
"yarn.lock", // Yarn lock file (highest priority)
126127
"package.json", // Package manifest (fallback)
127128
}
129+
case ProjectTypeNodeYarnBerry:
130+
filesToCheck = []string{
131+
"yarn.lock", // Yarn lock file (highest priority)
132+
".yarnrc.yml", // Yarn Berry configuration
133+
"package.json", // Package manifest (fallback)
134+
}
128135
default:
129136
return false, ""
130137
}
@@ -147,8 +154,13 @@ func DetectProjectType(dir string) (ProjectType, error) {
147154
return ProjectTypeNodePNPM, nil
148155
}
149156

157+
// Check for Yarn Berry (yarn.lock WITH .yarnrc.yml means Yarn v2+)
158+
if util.FileExists(dir, "yarn.lock") && util.FileExists(dir, ".yarnrc.yml") {
159+
return ProjectTypeNodeYarnBerry, nil
160+
}
161+
150162
// Check for Yarn Classic (yarn.lock without .yarnrc.yml means Yarn v1)
151-
if util.FileExists(dir, "yarn.lock") && !util.FileExists(dir, ".yarnrc.yml") {
163+
if util.FileExists(dir, "yarn.lock") {
152164
return ProjectTypeNodeYarn, nil
153165
}
154166

0 commit comments

Comments
 (0)