func buildFn()

in cmd/nodejs/npm/main.go [53:210]


func buildFn(ctx *gcp.Context) error {
	ml, err := ctx.Layer("npm_modules", gcp.BuildLayer, gcp.CacheLayer)
	if err != nil {
		return fmt.Errorf("creating layer: %w", err)
	}
	nm := filepath.Join(ml.Path, "node_modules")
	if nmExists, _ := ctx.FileExists("node_modules"); nmExists {
		buildermetrics.GlobalBuilderMetrics().GetCounter(buildermetrics.NpmNodeModulesCounterID).Increment(1)

	}
	vendorNpmDeps := nodejs.IsUsingVendoredDependencies()
	if !vendorNpmDeps {
		if err := ctx.RemoveAll("node_modules"); err != nil {
			return err
		}
	}
	if err := ar.GenerateNPMConfig(ctx); err != nil {
		return fmt.Errorf("generating Artifact Registry credentials: %w", err)
	}

	pjs, err := nodejs.ReadPackageJSONIfExists(ctx.ApplicationRoot())
	if err != nil {
		return err
	}
	if err := upgradeNPM(ctx, pjs); err != nil {
		vendorError := ""
		if vendorNpmDeps {
			vendorError = "Vendored dependencies detected, please remove the npm version from your package.json to avoid installing npm and instead use the bundled npm"
		}
		return fmt.Errorf("%s Error: %w", vendorError, err)
	}

	lockfile, err := nodejs.EnsureLockfile(ctx)
	if err != nil {
		return err
	}
	pjs, err = nodejs.OverrideAppHostingBuildScript(ctx, nodejs.ApphostingPreprocessedPathForPack)
	if err != nil {
		return err
	}
	buildCmds, isCustomBuild := nodejs.DetermineBuildCommands(pjs, "npm")
	// Respect the user's NODE_ENV value if it's set
	buildNodeEnv, nodeEnvPresent := os.LookupEnv(nodejs.EnvNodeEnv)
	if !nodeEnvPresent {
		if len(buildCmds) > 0 {
			// Assume that dev dependencies are required to run build scripts to
			// support the most use cases possible.
			buildNodeEnv = nodejs.EnvDevelopment
		} else {
			buildNodeEnv = nodejs.EnvProduction
		}
	}

	if vendorNpmDeps {
		buildermetrics.GlobalBuilderMetrics().GetCounter(buildermetrics.NpmVendorDependenciesCounterID).Increment(1)
		if _, err := ctx.Exec([]string{"npm", "rebuild"}, gcp.WithEnv("NODE_ENV="+buildNodeEnv), gcp.WithUserAttribution); err != nil {
			return err
		}
	} else {
		cached, err := nodejs.CheckOrClearCache(ctx, ml, cache.WithStrings(buildNodeEnv), cache.WithFiles("package.json", lockfile))
		if err != nil {
			return fmt.Errorf("checking cache: %w", err)
		}
		if cached {
			// Restore cached node_modules.
			if _, err := ctx.Exec([]string{"cp", "--archive", nm, "node_modules"}, gcp.WithUserTimingAttribution); err != nil {
				return err
			}

			// Always run npm install to run preinstall/postinstall scripts.
			// Otherwise it should be a no-op because the lockfile is unchanged.
			if _, err := ctx.Exec([]string{"npm", "install", "--quiet"}, gcp.WithEnv("NODE_ENV="+buildNodeEnv), gcp.WithUserAttribution); err != nil {
				return err
			}
		} else {
			ctx.Logf("Installing application dependencies.")
			installCmd, err := nodejs.NPMInstallCommand(ctx)
			if err != nil {
				return err
			}

			if _, err := ctx.Exec([]string{"npm", installCmd, "--quiet", "--no-fund", "--no-audit"}, gcp.WithEnv("NODE_ENV="+buildNodeEnv), gcp.WithUserAttribution); err != nil {
				return err
			}
			// Ensure node_modules exists even if no dependencies were installed.
			if err := ctx.MkdirAll("node_modules", 0755); err != nil {
				return err
			}
			if _, err := ctx.Exec([]string{"cp", "--archive", "node_modules", nm}, gcp.WithUserTimingAttribution); err != nil {
				return err
			}
		}
	}

	if len(buildCmds) > 0 {
		// If there are multiple build scripts to run, run them one-by-one so the logs are
		// easier to understand.
		for _, cmd := range buildCmds {
			execOpts := []gcp.ExecOption{gcp.WithUserAttribution}
			if nodejs.DetectSvelteKitAutoAdapter(pjs) {
				execOpts = append(execOpts, gcp.WithEnv(nodejs.SvelteAdapterEnv))
			}
			split := strings.Split(cmd, " ")
			if _, err := ctx.Exec(split, execOpts...); err != nil {
				if !isCustomBuild {
					return fmt.Errorf(`%w
NOTE: Running the default build script can be skipped by passing the empty environment variable "%s=" to the build`, err, nodejs.GoogleNodeRunScriptsEnv)
				}
				if fahCmd, fahCmdPresent := os.LookupEnv(nodejs.AppHostingBuildEnv); fahCmdPresent {
					return gcp.UserErrorf("%w", faherror.FailedFrameworkBuildError(fahCmd, err))
				}
				if nodejs.HasApphostingPackageBuild(pjs) {
					return gcp.UserErrorf("%w", faherror.FailedFrameworkBuildError(pjs.Scripts[nodejs.ScriptApphostingBuild], err))
				}
				return err
			}
		}

		shouldPrune, err := shouldPrune(ctx, pjs)
		if err != nil {
			return err
		}
		if shouldPrune {
			// npm prune deletes devDependencies from node_modules
			if _, err := ctx.Exec([]string{"npm", "prune", "--production"}, gcp.WithUserAttribution); err != nil {
				return err
			}
		}
	}

	el, err := ctx.Layer("env", gcp.BuildLayer, gcp.LaunchLayer)
	if err != nil {
		return fmt.Errorf("creating layer: %w", err)
	}
	el.SharedEnvironment.Prepend("PATH", string(os.PathListSeparator), filepath.Join(ctx.ApplicationRoot(), "node_modules", ".bin"))
	el.SharedEnvironment.Default("NODE_ENV", nodejs.NodeEnv())

	// Configure the entrypoint for production.
	cmd, err := nodejs.DefaultStartCommand(ctx, pjs)
	if err != nil {
		return fmt.Errorf("detecting start command: %w", err)
	}

	if !devmode.Enabled(ctx) {
		ctx.AddWebProcess(cmd)
		return nil
	}

	// Configure the entrypoint and metadata for dev mode.
	if err := devmode.AddFileWatcherProcess(ctx, devmode.Config{
		RunCmd: cmd,
		Ext:    devmode.NodeWatchedExtensions,
	}); err != nil {
		return fmt.Errorf("adding devmode file watcher: %w", err)
	}

	return nil
}