func buildFn()

in cmd/nodejs/functions_framework/main.go [64:207]


func buildFn(ctx *gcp.Context) error {
	if _, ok := os.LookupEnv(env.FunctionSource); ok {
		return gcp.UserErrorf("%s is not currently supported for Node.js buildpacks", env.FunctionSource)
	}

	indexJSExists, err := ctx.FileExists("index.js")
	if err != nil {
		return err
	}
	// Function source code should be defined in the "main" field in package.json, index.js or function.js.
	// https://cloud.google.com/functions/docs/writing#structuring_source_code
	fnFile := "function.js"
	if indexJSExists {
		fnFile = "index.js"
	}

	// Determine if the function has dependency on functions-framework.
	hasFrameworkDependency := false
	pjs, err := nodejs.ReadPackageJSONIfExists(ctx.ApplicationRoot())
	if err != nil {
		return fmt.Errorf("reading package.json: %w", err)
	}
	if pjs != nil {
		_, hasFrameworkDependency = pjs.Dependencies[functionsFrameworkPackage]
		if pjs.Main != "" {
			fnFile = pjs.Main
		}
	}

	fnFileExists, err := ctx.FileExists(fnFile)
	if err != nil {
		return err
	}
	if !fnFileExists {
		return gcp.UserErrorf("%s does not exist", fnFile)
	}

	yarnPnP, err := usingYarnModuleResolution(ctx)
	if err != nil {
		return err
	}

	if yarnPnP && !hasFrameworkDependency {
		return gcp.UserErrorf("This project is using Yarn Plug'n'Play but you have not included the Functions Framework in your dependencies. Please add it by running: 'yarn add @google-cloud/functions-framework'.")
	}

	pnpmLockExists, err := ctx.FileExists(nodejs.PNPMLock)
	if err != nil {
		return err
	}
	if pnpmLockExists && !hasFrameworkDependency {
		return gcp.UserErrorf("This project is using pnpm but you have not included the Functions Framework in your dependencies. Please add it by running: 'pnpm add @google-cloud/functions-framework'.")
	}

	// TODO(mattrobertson) remove this check once Nodejs has backported the fix to v16. More info here:
	// https://github.com/GoogleCloudPlatform/functions-framework-nodejs/issues/407
	if skip, err := nodejs.SkipSyntaxCheck(ctx, fnFile, pjs); err != nil {
		return err
	} else if !skip {
		// Syntax check the function code without executing to prevent run-time errors.
		if yarnPnP {
			if _, err := ctx.Exec([]string{"yarn", "node", "--check", fnFile}, gcp.WithUserAttribution); err != nil {
				return err
			}
		} else {
			if _, err := ctx.Exec([]string{"node", "--check", fnFile}, gcp.WithUserAttribution); err != nil {
				return err
			}
		}
	}

	l, err := ctx.Layer(layerName, gcp.BuildLayer, gcp.CacheLayer, gcp.LaunchLayer)
	if err != nil {
		return fmt.Errorf("creating %v layer: %w", layerName, err)
	}
	// We use the absolute path to the functions-framework executable in order to
	// avoid having to add its parent directory to PATH which could cause
	// conflicts with user-specified dependencies in the case where the framework
	// is not an explicit dependency.
	//
	// If the function specifies a framework dependency, the executable will be
	// in node_modules, where it would have been installed by the preceding
	// npm/yarn buildpack. Otherwise, it will be in the layer's node_modules,
	// installed below.
	ff := filepath.Join(".bin", "functions-framework")

	if yarnPnP {
		// In order for node module resolution to work in Yarn Plug'n'Play mode, we must invoke yarn to
		// start the Functions Framework.
		ff = "yarn functions-framework"
		cloudfunctions.AddFrameworkVersionLabel(ctx, &cloudfunctions.FrameworkVersionInfo{
			Runtime: "nodejs",
			Version: "yarn",
		})
	} else if hasFrameworkDependency {
		ctx.Logf("Handling functions with dependency on functions-framework.")
		if err := ctx.ClearLayer(l); err != nil {
			return fmt.Errorf("clearing layer %q: %w", l.Name, err)
		}
		ff = filepath.Join("node_modules", ff)
		addFrameworkVersionLabel(ctx, functionsFrameworkNodeModulePath, false)
	} else {
		ctx.Logf("Handling functions without dependency on functions-framework.")
		if err := cloudfunctions.AssertFrameworkInjectionAllowed(); err != nil {
			return err
		}

		if err := installFunctionsFramework(ctx, l); err != nil {
			vendorError := ""
			if nodejs.IsUsingVendoredDependencies() {
				vendorError = "Vendored dependencies detected, please make sure you have functions-framework installed locally to avoid the installation error by following: https://github.com/GoogleCloudPlatform/functions-framework-nodejs#installation."
			}

			return fmt.Errorf("%s installing functions-framework: %w", vendorError, err)
		}

		ff = filepath.Join(l.Path, "node_modules", ff)
		addFrameworkVersionLabel(ctx, filepath.Join(l.Path, functionsFrameworkNodeModulePath), true)

		nm := filepath.Join(ctx.ApplicationRoot(), "node_modules")
		nmExists, err := ctx.FileExists(nm)
		if err != nil {
			return err
		}
		// Add user's node_modules to NODE_PATH so functions-framework can always find user's packages.
		if nmExists {
			l.LaunchEnvironment.Prepend("NODE_PATH", string(os.PathListSeparator), nm)
		}
	}

	// Get and set the valid value for --max-old-space-size node_options.
	// Keep the existing behaviour if the value is not provided or invalid
	if size, err := getMaxOldSpaceSize(); err != nil {
		return err
	} else if size > 0 {
		l.LaunchEnvironment.Prepend("NODE_OPTIONS", " ", fmt.Sprintf("--max-old-space-size=%d", size))
	}

	if err := ctx.SetFunctionsEnvVars(l); err != nil {
		return err
	}
	ctx.AddWebProcess([]string{"/bin/bash", "-c", ff})
	return nil
}