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
}