func getAppFile(dir string) (appFile, error) {
	var v appFile
	ok, err := hasAppFile(dir)
	if err != nil {
		return v, err
	}
	if !ok {
		return v, nil
	}
	f, err := os.Open(filepath.Join(dir, appJSON))
	if err != nil {
		return v, fmt.Errorf("error opening app.json file: %v", err)
	}
	defer f.Close()
	af, err := parseAppFile(f)
	if err != nil {
		return v, fmt.Errorf("failed to parse app.json file: %v", err)
	}
	return *af, nil
}
getAppFile returns the parsed app.json in the directory if it exists,
otherwise returns a zero appFile.
package main
import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"github.com/fatih/color"
	"github.com/AlecAivazis/survey/v2"
)
type env struct {
	Description string `json:"description"`
	Value       string `json:"value"`
	Required    *bool  `json:"required"`
	Generator   string `json:"generator"`
	Order       *int   `json:"order"`
}
type options struct {
	AllowUnauthenticated *bool  `json:"allow-unauthenticated"`
	Memory               string `json:"memory"`
	CPU                  string `json:"cpu"`
	Port                 int    `json:"port"`
	HTTP2                *bool  `json:"http2"`
}
type hook struct {
	Commands []string `json:"commands"`
}
type buildpacks struct {
	Builder string `json:"builder"`
}
type build struct {
	Skip       *bool      `json:"skip"`
	Buildpacks buildpacks `json:"buildpacks"`
}
type hooks struct {
	PreCreate  hook `json:"precreate"`
	PostCreate hook `json:"postcreate"`
	PreBuild   hook `json:"prebuild"`
	PostBuild  hook `json:"postbuild"`
}
type appFile struct {
	Name    string         `json:"name"`
	Env     map[string]env `json:"env"`
	Options options        `json:"options"`
	Build   build          `json:"build"`
	Hooks   hooks          `json:"hooks"`
	IgnoredDescription string      `json:"description"`
	IgnoredKeywords    []string    `json:"keywords"`
	IgnoredLogo        string      `json:"logo"`
	IgnoredRepository  string      `json:"repository"`
	IgnoredWebsite     string      `json:"website"`
	IgnoredStack       string      `json:"stack"`
	IgnoredFormation   interface{} `json:"formation"`
}
const appJSON = `app.json`
func hasAppFile(dir string) (bool, error) {
	path := filepath.Join(dir, appJSON)
	fi, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	return fi.Mode().IsRegular(), nil
}
func parseAppFile(r io.Reader) (*appFile, error) {
	var v appFile
	d := json.NewDecoder(r)
	d.DisallowUnknownFields()
	if err := d.Decode(&v); err != nil {
		return nil, fmt.Errorf("failed to parse app.json: %+v", err)
	}
	for k, env := range v.Env {
		if env.Required == nil {
			v := true
			env.Required = &v
		}
		v.Env[k] = env
	}
	for k, env := range v.Env {
		if env.Generator == "secret" && env.Value != "" {
			return nil, fmt.Errorf("env var %q can't have both a value and use the secret generator", k)
		}
	}
	return &v, nil
}