| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- package buildpacks
- import (
- "context"
- "encoding/json"
- "fmt"
- "strings"
- "sync"
- "github.com/Masterminds/semver/v3"
- "github.com/google/go-github/v41/github"
- "github.com/xanzy/go-gitlab"
- )
- var lts = map[string]int{
- "argon": 4,
- "boron": 6,
- "carbon": 8,
- "dubnium": 10,
- }
- type nodejsRuntime struct {
- wg sync.WaitGroup
- }
- func NewNodeRuntime() Runtime {
- return &nodejsRuntime{}
- }
- func (runtime *nodejsRuntime) detectYarnGithub(results chan struct {
- string
- bool
- }, directoryContent []*github.RepositoryContent,
- ) {
- yarnLockFound := false
- packageJSONFound := false
- for i := 0; i < len(directoryContent); i++ {
- name := directoryContent[i].GetName()
- if name == "yarn.lock" {
- yarnLockFound = true
- } else if name == "package.json" {
- packageJSONFound = true
- }
- if yarnLockFound && packageJSONFound {
- break
- }
- }
- if yarnLockFound && packageJSONFound {
- results <- struct {
- string
- bool
- }{yarn, true}
- }
- runtime.wg.Done()
- }
- func (runtime *nodejsRuntime) detectYarnGitlab(results chan struct {
- string
- bool
- }, tree []*gitlab.TreeNode,
- ) {
- yarnLockFound := false
- packageJSONFound := false
- for i := 0; i < len(tree); i++ {
- name := tree[i].Name
- if name == "yarn.lock" {
- yarnLockFound = true
- } else if name == "package.json" {
- packageJSONFound = true
- }
- if yarnLockFound && packageJSONFound {
- break
- }
- }
- if yarnLockFound && packageJSONFound {
- results <- struct {
- string
- bool
- }{yarn, true}
- }
- runtime.wg.Done()
- }
- func (runtime *nodejsRuntime) detectNPMGithub(results chan struct {
- string
- bool
- }, directoryContent []*github.RepositoryContent,
- ) {
- packageJSONFound := false
- for i := 0; i < len(directoryContent); i++ {
- name := directoryContent[i].GetName()
- if name == "package.json" {
- packageJSONFound = true
- break
- }
- }
- if packageJSONFound {
- results <- struct {
- string
- bool
- }{npm, true}
- }
- runtime.wg.Done()
- }
- func (runtime *nodejsRuntime) detectNPMGitlab(results chan struct {
- string
- bool
- }, tree []*gitlab.TreeNode,
- ) {
- packageJSONFound := false
- for i := 0; i < len(tree); i++ {
- name := tree[i].Name
- if name == "package.json" {
- packageJSONFound = true
- break
- }
- }
- if packageJSONFound {
- results <- struct {
- string
- bool
- }{npm, true}
- }
- runtime.wg.Done()
- }
- func (runtime *nodejsRuntime) detectStandaloneGithub(results chan struct {
- string
- bool
- }, directoryContent []*github.RepositoryContent,
- ) {
- jsFileFound := false
- for i := 0; i < len(directoryContent); i++ {
- name := directoryContent[i].GetName()
- if name == "server.js" || name == "app.js" || name == "main.js" || name == "index.js" {
- jsFileFound = true
- break
- }
- }
- if jsFileFound {
- results <- struct {
- string
- bool
- }{standalone, true}
- }
- runtime.wg.Done()
- }
- func (runtime *nodejsRuntime) detectStandaloneGitlab(results chan struct {
- string
- bool
- }, tree []*gitlab.TreeNode,
- ) {
- jsFileFound := false
- for i := 0; i < len(tree); i++ {
- name := tree[i].Name
- if name == "server.js" || name == "app.js" || name == "main.js" || name == "index.js" {
- jsFileFound = true
- break
- }
- }
- if jsFileFound {
- results <- struct {
- string
- bool
- }{standalone, true}
- }
- runtime.wg.Done()
- }
- // copied directly from https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
- func validateNvmrc(content string) (string, error) {
- content = strings.TrimSpace(strings.ToLower(content))
- if content == "lts/*" || content == "node" {
- return content, nil
- }
- for key := range lts {
- if content == strings.ToLower("lts/"+key) {
- return content, nil
- }
- }
- content = strings.TrimPrefix(content, "v")
- if _, err := semver.NewConstraint(content); err != nil {
- return "", fmt.Errorf("invalid version constraint specified in .nvmrc: %q", content)
- }
- return content, nil
- }
- // copied directly from https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
- func formatNvmrcContent(version string) string {
- if version == "node" {
- return "*"
- }
- if strings.HasPrefix(version, "lts") {
- ltsName := strings.SplitN(version, "/", 2)[1]
- if ltsName == "*" {
- var maxVersion int
- for _, versionValue := range lts {
- if maxVersion < versionValue {
- maxVersion = versionValue
- }
- }
- return fmt.Sprintf("%d.*", maxVersion)
- }
- return fmt.Sprintf("%d.*", lts[ltsName])
- }
- return version
- }
- // copied directly from https://github.com/paketo-buildpacks/node-engine/blob/main/node_version_parser.go
- func validateNodeVersion(content string) (string, error) {
- content = strings.TrimSpace(strings.ToLower(content))
- content = strings.TrimPrefix(content, "v")
- if _, err := semver.NewConstraint(content); err != nil {
- return "", fmt.Errorf("invalid version constraint specified in .node-version: %q", content)
- }
- return content, nil
- }
- func (runtime *nodejsRuntime) DetectGithub(
- client *github.Client,
- directoryContent []*github.RepositoryContent,
- owner, name, path string,
- repoContentOptions github.RepositoryContentGetOptions,
- paketo, heroku *BuilderInfo,
- ) error {
- results := make(chan struct {
- string
- bool
- }, 3)
- runtime.wg.Add(3)
- go runtime.detectYarnGithub(results, directoryContent)
- go runtime.detectNPMGithub(results, directoryContent)
- go runtime.detectStandaloneGithub(results, directoryContent)
- runtime.wg.Wait()
- close(results)
- paketoBuildpackInfo := BuildpackInfo{
- Name: "NodeJS",
- Buildpack: "gcr.io/paketo-buildpacks/nodejs",
- }
- herokuBuildpackInfo := BuildpackInfo{
- Name: "NodeJS",
- Buildpack: "heroku/nodejs",
- }
- if len(results) == 0 {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return nil
- }
- foundYarn := false
- foundNPM := false
- foundStandalone := false
- for result := range results {
- if result.string == yarn {
- foundYarn = true
- } else if result.string == npm {
- foundNPM = true
- } else if result.string == standalone {
- foundStandalone = true
- }
- }
- if foundYarn || foundNPM {
- // it is safe to assume that the project contains a package.json
- fileContent, _, _, err := client.Repositories.GetContents(
- context.Background(),
- owner,
- name,
- fmt.Sprintf("%s/package.json", path),
- &repoContentOptions,
- )
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error fetching contents of package.json: %v", err)
- }
- var packageJSON struct {
- Scripts map[string]string `json:"scripts"`
- Engines struct {
- Node string `json:"node"`
- } `json:"engines"`
- }
- data, err := fileContent.GetContent()
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error calling GetContent() on package.json: %v", err)
- }
- err = json.NewDecoder(strings.NewReader(data)).Decode(&packageJSON)
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error decoding package.json contents to struct: %v", err)
- }
- if packageJSON.Engines.Node == "" {
- // we should now check for the node engine version in .nvmrc and then .node-version
- nvmrcFound := false
- nodeVersionFound := false
- for i := 0; i < len(directoryContent); i++ {
- name := directoryContent[i].GetName()
- if name == ".nvmrc" {
- nvmrcFound = true
- } else if name == ".node-version" {
- nodeVersionFound = true
- }
- }
- if nvmrcFound {
- // copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
- fileContent, _, _, err = client.Repositories.GetContents(
- context.Background(),
- owner,
- name,
- fmt.Sprintf("%s/.nvmrc", path),
- &repoContentOptions,
- )
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error fetching contents of .nvmrc: %v", err)
- }
- data, err = fileContent.GetContent()
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error calling GetContent() on .nvmrc: %v", err)
- }
- nvmrcVersion, err := validateNvmrc(data)
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error validating .nvmrc: %v", err)
- }
- nvmrcVersion = formatNvmrcContent(nvmrcVersion)
- if nvmrcVersion != "*" {
- packageJSON.Engines.Node = data
- }
- }
- if packageJSON.Engines.Node == "" && nodeVersionFound {
- // copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/node_version_parser.go
- fileContent, _, _, err = client.Repositories.GetContents(
- context.Background(),
- owner,
- name,
- fmt.Sprintf("%s/.node-version", path),
- &repoContentOptions,
- )
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error fetching contents of .node-version: %v", err)
- }
- data, err = fileContent.GetContent()
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error calling GetContent() on .node-version: %v", err)
- }
- nodeVersion, err := validateNodeVersion(data)
- if err != nil {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return fmt.Errorf("error validating .node-version: %v", err)
- }
- if nodeVersion != "" {
- packageJSON.Engines.Node = nodeVersion
- }
- }
- }
- if packageJSON.Engines.Node == "" {
- // use the default node engine version from https://github.com/paketo-buildpacks/node-engine/blob/main/buildpack.toml
- packageJSON.Engines.Node = "16.*.*"
- }
- paketoBuildpackInfo.Config = make(map[string]interface{})
- paketoBuildpackInfo.Config["scripts"] = packageJSON.Scripts
- paketoBuildpackInfo.Config["node_engine"] = packageJSON.Engines.Node
- paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
- herokuBuildpackInfo.Config = make(map[string]interface{})
- herokuBuildpackInfo.Config["scripts"] = packageJSON.Scripts
- herokuBuildpackInfo.Config["node_engine"] = packageJSON.Engines.Node
- heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
- } else if foundStandalone {
- paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
- heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
- }
- return nil
- }
- func (runtime *nodejsRuntime) DetectGitlab(
- client *gitlab.Client,
- tree []*gitlab.TreeNode,
- repoPath, path, ref string,
- paketo, heroku *BuilderInfo,
- ) error {
- results := make(chan struct {
- string
- bool
- }, 3)
- runtime.wg.Add(3)
- go runtime.detectYarnGitlab(results, tree)
- go runtime.detectNPMGitlab(results, tree)
- go runtime.detectStandaloneGitlab(results, tree)
- runtime.wg.Wait()
- close(results)
- paketoBuildpackInfo := BuildpackInfo{
- Name: "NodeJS",
- Buildpack: "gcr.io/paketo-buildpacks/nodejs",
- }
- herokuBuildpackInfo := BuildpackInfo{
- Name: "NodeJS",
- Buildpack: "heroku/nodejs",
- }
- if len(results) == 0 {
- paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- return nil
- }
- // foundYarn := false
- // foundNPM := false
- // foundStandalone := false
- // for result := range results {
- // if result.string == yarn {
- // foundYarn = true
- // } else if result.string == npm {
- // foundNPM = true
- // } else if result.string == standalone {
- // foundStandalone = true
- // }
- // }
- paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
- heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
- // if foundYarn || foundNPM {
- // // it is safe to assume that the project contains a package.json
- // fileContent, _, err := client.RepositoryFiles.GetRawFile(
- // fmt.Sprintf("%s/%s", owner, name), fmt.Sprintf("%s/package.json", path),
- // &gitlab.GetRawFileOptions{
- // Ref: gitlab.String(ref),
- // },
- // )
- // if err != nil {
- // paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- // heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- // return fmt.Errorf("error fetching contents of package.json: %v", err)
- // }
- // var packageJSON struct {
- // Scripts map[string]string `json:"scripts"`
- // Engines struct {
- // Node string `json:"node"`
- // } `json:"engines"`
- // }
- // data := string(fileContent)
- // err = json.NewDecoder(strings.NewReader(data)).Decode(&packageJSON)
- // if err != nil {
- // paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- // heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- // return fmt.Errorf("error decoding package.json contents to struct: %v", err)
- // }
- // if packageJSON.Engines.Node == "" {
- // // we should now check for the node engine version in .nvmrc and then .node-version
- // nvmrcFound := false
- // nodeVersionFound := false
- // for i := 0; i < len(tree); i++ {
- // name := tree[i].Name
- // if name == ".nvmrc" {
- // nvmrcFound = true
- // } else if name == ".node-version" {
- // nodeVersionFound = true
- // }
- // }
- // if nvmrcFound {
- // // copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/nvmrc_parser.go
- // fileContent, _, err = client.RepositoryFiles.GetRawFile(
- // fmt.Sprintf("%s/%s", owner, name), fmt.Sprintf("%s/.nvmrc", path),
- // &gitlab.GetRawFileOptions{
- // Ref: gitlab.String(ref),
- // },
- // )
- // if err != nil {
- // paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- // heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- // return fmt.Errorf("error fetching contents of .nvmrc: %v", err)
- // }
- // data = string(fileContent)
- // nvmrcVersion, err := validateNvmrc(data)
- // if err != nil {
- // paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- // heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- // return fmt.Errorf("error validating .nvmrc: %v", err)
- // }
- // nvmrcVersion = formatNvmrcContent(nvmrcVersion)
- // if nvmrcVersion != "*" {
- // packageJSON.Engines.Node = data
- // }
- // }
- // if packageJSON.Engines.Node == "" && nodeVersionFound {
- // // copy exact behavior of https://github.com/paketo-buildpacks/node-engine/blob/main/node_version_parser.go
- // fileContent, _, err = client.RepositoryFiles.GetRawFile(
- // fmt.Sprintf("%s/%s", owner, name), fmt.Sprintf("%s/.node-version", path),
- // &gitlab.GetRawFileOptions{
- // Ref: gitlab.String(ref),
- // },
- // )
- // if err != nil {
- // paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- // heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- // return fmt.Errorf("error fetching contents of .node-version: %v", err)
- // }
- // data = string(fileContent)
- // nodeVersion, err := validateNodeVersion(data)
- // if err != nil {
- // paketo.Others = append(paketo.Others, paketoBuildpackInfo)
- // heroku.Others = append(heroku.Others, herokuBuildpackInfo)
- // return fmt.Errorf("error validating .node-version: %v", err)
- // }
- // if nodeVersion != "" {
- // packageJSON.Engines.Node = nodeVersion
- // }
- // }
- // }
- // if packageJSON.Engines.Node == "" {
- // // use the default node engine version from https://github.com/paketo-buildpacks/node-engine/blob/main/buildpack.toml
- // packageJSON.Engines.Node = "16.*.*"
- // }
- // paketoBuildpackInfo.Config = make(map[string]interface{})
- // paketoBuildpackInfo.Config["scripts"] = packageJSON.Scripts
- // paketoBuildpackInfo.Config["node_engine"] = packageJSON.Engines.Node
- // paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
- // herokuBuildpackInfo.Config = make(map[string]interface{})
- // herokuBuildpackInfo.Config["scripts"] = packageJSON.Scripts
- // herokuBuildpackInfo.Config["node_engine"] = packageJSON.Engines.Node
- // heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
- // } else if foundStandalone {
- // paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
- // heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
- // }
- return nil
- }
|