mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
* feat: automatically install packages required for playwright, and playwright browser This reverts commit 2a5567ff09fa044cf391b1d79ecf1fe7846e8828. * docs: update environment variable test configuration property name Generated-by: aiautocommit * docs(node): add production deployment guide link to start command help Generated-by: aiautocommit * feat(python-playwright): install playwright only when requested via env - Enable Playwright browser installation conditionally using `RAILPACK_PYTHON_PLAYWRIGHT_INSTALL`. - Update python-playwright example to test against example.com. Generated-by: aiautocommit * feat(python): make Playwright browser installation opt-in - Require `RAILPACK_PYTHON_PLAYWRIGHT_INSTALL=1` to install Playwright browsers and their runtime dependencies automatically. - Prevent unintended deployment size increases and breaking changes for existing users since automatic installation can cause issues. - Add test coverage verifying opt-in behavior and production dependency resolution. Generated-by: aiautocommit * docs(python): document playwright install configuration variable Generated-by: aiautocommit * feat(node): add support for checking production-only dependencies Generated-by: aiautocommit * feat(node): make Playwright browser installation opt-in - Restrict automatic Playwright detection to production dependencies and require an explicit environment variable to install browsers, preventing unexpected side effects for existing users. - Add tests to verify Playwright installation behavior based on environment configuration. Generated-by: aiautocommit * docs: add unit testing guideline against testing log output Generated-by: aiautocommit * chore(deps): update playwright dependency to v1.60 in node-playwright example Generated-by: aiautocommit * feat(node,python): update Playwright runtime dependencies for Debian 12 - Align Playwright runtime dependencies with the official nativeDeps.ts list for Debian 12 Chromium. - Ensure production runtime images include all required system libraries for Playwright without relying on builder-only installation flags. Generated-by: aiautocommit * feat(examples): navigate to example.com in playwright node example Generated-by: aiautocommit * docs(node): document opt-in Playwright browser installation Generated-by: aiautocommit * test(core): update test snapshots for playwright production-only dependencies Generated-by: aiautocommit * docs: add instruction regarding snapshot updates to PR template Generated-by: aiautocommit * feat(node): remove libatomic1 requirement for Node.js 25+ Generated-by: aiautocommit * test(core): update playwright snapshots for mise version and apt caches Generated-by: aiautocommit
375 lines
8.8 KiB
Go
375 lines
8.8 KiB
Go
package node
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/railwayapp/railpack/core/app"
|
|
"github.com/railwayapp/railpack/core/generate"
|
|
"github.com/railwayapp/railpack/core/plan"
|
|
testingUtils "github.com/railwayapp/railpack/core/testing"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNode(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
detected bool
|
|
packageManager PackageManager
|
|
nodeVersion string
|
|
pnpmVersion string
|
|
envVars map[string]string
|
|
}{
|
|
{
|
|
name: "npm",
|
|
path: "../../../examples/node-npm",
|
|
detected: true,
|
|
packageManager: PackageManagerNpm,
|
|
nodeVersion: "23.5.0",
|
|
},
|
|
{
|
|
name: "bun",
|
|
path: "../../../examples/node-bun",
|
|
detected: true,
|
|
packageManager: PackageManagerBun,
|
|
},
|
|
{
|
|
name: "pnpm",
|
|
path: "../../../examples/node-corepack",
|
|
detected: true,
|
|
packageManager: PackageManagerPnpm,
|
|
nodeVersion: "20",
|
|
pnpmVersion: "10.4.1",
|
|
},
|
|
{
|
|
name: "pnpm",
|
|
path: "../../../examples/node-pnpm-workspaces",
|
|
detected: true,
|
|
packageManager: PackageManagerPnpm,
|
|
nodeVersion: "22.2.0",
|
|
},
|
|
{
|
|
name: "pnpm from mise.toml",
|
|
path: "../../../examples/node-latest-pnpm-mise-native-deps",
|
|
detected: true,
|
|
packageManager: PackageManagerPnpm,
|
|
nodeVersion: "latest",
|
|
pnpmVersion: "latest",
|
|
},
|
|
{
|
|
name: "pnpm",
|
|
path: "../../../examples/node-astro",
|
|
detected: true,
|
|
packageManager: PackageManagerNpm,
|
|
},
|
|
{
|
|
name: "railpack node version overrides engines",
|
|
path: "../../../examples/node-version-precedence",
|
|
detected: true,
|
|
packageManager: PackageManagerNpm,
|
|
nodeVersion: "22",
|
|
envVars: map[string]string{"RAILPACK_NODE_VERSION": "22"},
|
|
},
|
|
{
|
|
name: "golang",
|
|
path: "../../../examples/go-mod",
|
|
detected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := testingUtils.CreateGenerateContext(t, tt.path)
|
|
if tt.envVars != nil {
|
|
envVars := tt.envVars
|
|
ctx.Env = app.NewEnvironment(&envVars)
|
|
}
|
|
provider := NodeProvider{}
|
|
detected, err := provider.Detect(ctx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.detected, detected)
|
|
|
|
if detected {
|
|
err = provider.Initialize(ctx)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tt.packageManager, provider.packageManager)
|
|
|
|
err = provider.Plan(ctx)
|
|
require.NoError(t, err)
|
|
|
|
if tt.nodeVersion != "" {
|
|
nodeVersion := ctx.Resolver.Get("node")
|
|
if tt.nodeVersion == "latest" {
|
|
require.NotEmpty(t, nodeVersion.Version)
|
|
} else {
|
|
require.Equal(t, tt.nodeVersion, nodeVersion.Version)
|
|
}
|
|
}
|
|
|
|
if tt.pnpmVersion != "" {
|
|
pnpmVersion := ctx.Resolver.Get("pnpm")
|
|
require.NotNil(t, pnpmVersion)
|
|
|
|
if tt.pnpmVersion == "latest" {
|
|
require.NotEmpty(t, pnpmVersion.Version)
|
|
} else {
|
|
require.Equal(t, tt.pnpmVersion, pnpmVersion.Version)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNodeCorepack(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
wantCorepack bool
|
|
}{
|
|
{
|
|
name: "corepack project",
|
|
path: "../../../examples/node-corepack",
|
|
wantCorepack: true,
|
|
},
|
|
{
|
|
name: "bun project",
|
|
path: "../../../examples/node-bun",
|
|
wantCorepack: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := testingUtils.CreateGenerateContext(t, tt.path)
|
|
provider := NodeProvider{}
|
|
err := provider.Initialize(ctx)
|
|
require.NoError(t, err)
|
|
|
|
usesCorepack := provider.usesCorepack()
|
|
require.Equal(t, tt.wantCorepack, usesCorepack)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNextApps(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "npm project",
|
|
path: "../../../examples/node-npm",
|
|
want: []string{},
|
|
},
|
|
{
|
|
name: "bun project",
|
|
path: "../../../examples/node-next",
|
|
want: []string{""},
|
|
},
|
|
{
|
|
name: "turbo with 2 next apps",
|
|
path: "../../../examples/node-turborepo",
|
|
want: []string{"apps/web"},
|
|
},
|
|
{
|
|
name: "nx next workspace",
|
|
path: "../../../examples/node-nx-next",
|
|
want: []string{"apps/web"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := testingUtils.CreateGenerateContext(t, tt.path)
|
|
provider := NodeProvider{}
|
|
err := provider.Initialize(ctx)
|
|
require.NoError(t, err)
|
|
|
|
nextPackages, err := provider.getPackagesWithFramework(ctx, func(pkg *WorkspacePackage, ctx *generate.GenerateContext) bool {
|
|
if pkg.PackageJson.BuildScriptContains("next build") {
|
|
return true
|
|
}
|
|
return provider.isNextAppPackage(pkg, ctx)
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
nextApps := make([]string, len(nextPackages))
|
|
for i, pkg := range nextPackages {
|
|
nextApps[i] = pkg.Path
|
|
}
|
|
require.Equal(t, tt.want, nextApps)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPackageJsonRequiresBun(t *testing.T) {
|
|
// Special cases
|
|
t.Run("nil package.json", func(t *testing.T) {
|
|
got := packageJsonRequiresBun(nil)
|
|
require.False(t, got)
|
|
})
|
|
|
|
t.Run("no scripts", func(t *testing.T) {
|
|
got := packageJsonRequiresBun(&PackageJson{})
|
|
require.False(t, got)
|
|
})
|
|
|
|
// Scripts that should trigger bun detection
|
|
bunScripts := []string{
|
|
"bun run server.js",
|
|
"bunx nodemon index.js",
|
|
"bun test",
|
|
"npm run clean && bun build.js",
|
|
"echo 'Running tests' | bun test",
|
|
"npm run build; bun run server.js",
|
|
"cd src && bun install",
|
|
"bun --version",
|
|
"bunx prisma migrate",
|
|
}
|
|
|
|
t.Run("scripts requiring bun", func(t *testing.T) {
|
|
packageJson := &PackageJson{
|
|
Scripts: make(map[string]string),
|
|
}
|
|
for i, script := range bunScripts {
|
|
packageJson.Scripts[fmt.Sprintf("script%d", i)] = script
|
|
}
|
|
got := packageJsonRequiresBun(packageJson)
|
|
require.True(t, got)
|
|
})
|
|
|
|
// Scripts that should NOT trigger bun detection
|
|
nonBunScripts := []string{
|
|
"esbuild dev.ts ./src --bundle --outdir=dist --packages=external --platform=node --sourcemap --watch",
|
|
"webpack --config webpack.bundle.config.js",
|
|
"node src/bundle-manager.js",
|
|
"jest --bundle-reporter",
|
|
"eslint src/bundles/",
|
|
"sh deploy-bundle.sh",
|
|
"npm run bundle:production",
|
|
"yarn bundle",
|
|
"pnpm run unbundle",
|
|
}
|
|
|
|
t.Run("scripts not requiring bun", func(t *testing.T) {
|
|
packageJson := &PackageJson{
|
|
Scripts: make(map[string]string),
|
|
}
|
|
for i, script := range nonBunScripts {
|
|
packageJson.Scripts[fmt.Sprintf("script%d", i)] = script
|
|
}
|
|
got := packageJsonRequiresBun(packageJson)
|
|
require.False(t, got)
|
|
})
|
|
}
|
|
|
|
func TestUsesPnpmBinSubdir(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
version string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "latest uses bin subdir",
|
|
version: "latest",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "major 11 uses bin subdir",
|
|
version: "11",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "pnpm 11 uses bin subdir",
|
|
version: "11.0.0",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "pnpm 10 does not use bin subdir",
|
|
version: "10.9.0",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "empty version does not use bin subdir",
|
|
version: "",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "invalid version does not use bin subdir",
|
|
version: "workspace:^",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "resolved mise version",
|
|
version: "11.5.1",
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require.Equal(t, tt.want, usesPnpmBinSubdir(tt.version))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPlaywrightInstallationIsOptIn(t *testing.T) {
|
|
t.Run("does not install by default", func(t *testing.T) {
|
|
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-playwright")
|
|
provider := NodeProvider{}
|
|
|
|
require.NoError(t, provider.Initialize(ctx))
|
|
require.NoError(t, provider.Plan(ctx))
|
|
require.False(t, nodeStepHasExecCommand(
|
|
ctx,
|
|
"install",
|
|
"pnpm exec playwright install --only-shell",
|
|
))
|
|
require.NotContains(t, ctx.Deploy.AptPackages, "libnss3")
|
|
})
|
|
|
|
t.Run("installs when enabled", func(t *testing.T) {
|
|
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/node-playwright")
|
|
ctx.Env.Variables["RAILPACK_NODE_PLAYWRIGHT_INSTALL"] = "1"
|
|
provider := NodeProvider{}
|
|
|
|
require.NoError(t, provider.Initialize(ctx))
|
|
require.NoError(t, provider.Plan(ctx))
|
|
require.True(t, nodeStepHasExecCommand(
|
|
ctx,
|
|
"install",
|
|
"pnpm exec playwright install --only-shell",
|
|
))
|
|
require.Contains(t, ctx.Deploy.AptPackages, "libnss3")
|
|
})
|
|
}
|
|
|
|
func nodeStepHasExecCommand(
|
|
ctx *generate.GenerateContext,
|
|
stepName string,
|
|
command string,
|
|
) bool {
|
|
step := ctx.GetStepByName(stepName)
|
|
if step == nil {
|
|
return false
|
|
}
|
|
|
|
commandStep, ok := (*step).(*generate.CommandStepBuilder)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
for _, candidate := range commandStep.Commands {
|
|
execCommand, ok := candidate.(plan.ExecCommand)
|
|
if ok && execCommand.Cmd == command {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|