func yarn1InstallModules()

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
}