in cmd/nodejs/yarn/main.go [110:226]
func yarn1InstallModules(ctx *gcp.Context, pjs *nodejs.PackageJSON) error {
freezeLockfile, err := nodejs.UseFrozenLockfile(ctx)
if err != nil {
return err
}
ml, err := ctx.Layer("yarn_modules", gcp.BuildLayer, gcp.CacheLayer, gcp.LaunchLayer)
if err != nil {
return fmt.Errorf("creating layer: %w", err)
}
if err := ar.GenerateNPMConfig(ctx); err != nil {
return fmt.Errorf("generating Artifact Registry credentials: %w", err)
}
_, err = nodejs.CheckOrClearCache(ctx, ml, cache.WithFiles("package.json", nodejs.YarnLock))
if err != nil {
return fmt.Errorf("checking cache: %w", err)
}
// Use Yarn's --modules-folder flag to install directly into the layer and then symlink them into
// the app dir.
layerModules := filepath.Join(ml.Path, "node_modules")
appModules := filepath.Join(ctx.ApplicationRoot(), "node_modules")
if err := ctx.MkdirAll(layerModules, 0755); err != nil {
return err
}
if err := ctx.RemoveAll(appModules); err != nil {
return err
}
if err := ctx.Symlink(layerModules, appModules); err != nil {
return err
}
locationFlag := fmt.Sprintf("--modules-folder=%s", layerModules)
runtimeconfigJSONExists, err := ctx.FileExists(".runtimeconfig.json")
if err != nil {
return err
}
// This is a hack to fix a bug in an old version of Firebase that loaded a config using a path
// relative to node_modules: https://github.com/firebase/firebase-functions/issues/630.
if runtimeconfigJSONExists {
layerConfig := filepath.Join(ml.Path, ".runtimeconfig.json")
if err := ctx.RemoveAll(layerConfig); err != nil {
return err
}
if err := ctx.Symlink(filepath.Join(ctx.ApplicationRoot(), ".runtimeconfig.json"), layerConfig); err != nil {
return err
}
}
// Always run yarn install to execute customer's lifecycle hooks.
cmd := []string{"yarn", "install", "--non-interactive", "--prefer-offline", locationFlag}
// HACK: For backwards compatibility on App Engine Node.js 10 and older, skip using `--frozen-lockfile`.
if freezeLockfile {
cmd = append(cmd, "--frozen-lockfile")
}
gcpBuild := nodejs.HasGCPBuild(pjs)
appHostingBuildEnv, appHostingBuildEnvPresent := os.LookupEnv(nodejs.AppHostingBuildEnv)
if gcpBuild || appHostingBuildEnvPresent {
// Setting --production=false causes the devDependencies to be installed regardless of the
// NODE_ENV value. The allows the customer's lifecycle hooks to access to them. We purge the
// devDependencies from the final app.
cmd = append(cmd, "--production=false")
}
// Add the layer's node_modules/.bin to the path so it is available in postinstall scripts.
nodeBin := filepath.Join(layerModules, ".bin")
if _, err := ctx.Exec(cmd, gcp.WithUserAttribution, gcp.WithEnv(fmt.Sprintf("PATH=%s:%s", os.Getenv("PATH"), nodeBin))); err != nil {
return err
}
pjs, err = nodejs.OverrideAppHostingBuildScript(ctx, nodejs.ApphostingPreprocessedPathForPack)
if err != nil {
return err
}
appHostingBuildScriptPresent := nodejs.HasApphostingPackageBuild(pjs)
if gcpBuild || appHostingBuildEnvPresent || appHostingBuildScriptPresent {
if appHostingBuildScriptPresent {
if _, err := ctx.Exec([]string{"yarn", "run", "apphosting:build"}, gcp.WithUserAttribution); err != nil {
return gcp.UserErrorf("%w", faherror.FailedFrameworkBuildError(pjs.Scripts[nodejs.ScriptApphostingBuild], err))
}
} else if appHostingBuildEnvPresent {
if _, err := ctx.Exec(strings.Split(appHostingBuildEnv, " "), gcp.WithUserAttribution); err != nil {
return gcp.UserErrorf("%w", faherror.FailedFrameworkBuildError(appHostingBuildEnv, err))
}
} else {
if _, err := ctx.Exec([]string{"yarn", "run", "gcp-build"}, gcp.WithUserAttribution); err != nil {
return err
}
}
// If there was a gcp-build script we installed all the devDependencies above. We should try to
// prune them from the final app image.
nodeEnv := nodejs.NodeEnv()
if nodejs.NodeEnv() != nodejs.EnvProduction {
ctx.Logf("Retaining devDependencies because NODE_ENV=%q", nodeEnv)
} else {
if env.IsFAH() {
// We don't prune if the user is using App Hosting since App Hosting builds don't
// rely on the node_modules folder at this point.
return nil
}
// For Yarn1, setting `--production=true` causes all `devDependencies` to be deleted.
ctx.Logf("Pruning devDependencies")
cmd := []string{"yarn", "install", "--ignore-scripts", "--prefer-offline", "--production=true", locationFlag}
if freezeLockfile {
cmd = append(cmd, "--frozen-lockfile")
}
if _, err := ctx.Exec(cmd, gcp.WithUserAttribution); err != nil {
return err
}
}
}
return nil
}