Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-62993

Auto-detect NodeJS-Tool based on package.json engines.node version

    XMLWordPrintable

    Details

    • Similar Issues:

      Description

      As described in nodejs docs] you can specify the nodejs version suitable for your project in package.json.

      I think it would be nice if one could write something like this in pipeline:

      nodejsAutodetect {
          // your node and npm scripts
      }

      and then nodejs-plugin would scan all installed versions of node and check whether the version fits or not. If it fits it would use that version for the block.

      I have created a groovy script in a shared library for my current project which does that. It is far from perfect, but i think a good proof of concept:

      import jenkins.plugins.nodejs.NodeJSUtils;
      
      @Grab('com.vdurmont:semver4j:3.1.0')
      import com.vdurmont.semver4j.Semver
      
      @NonCPS
      def determineInstalledNodeJSVersions() {
        def installedNodeJSVersions = [:]
        NodeJSUtils.getInstallations().each { entry ->
          def name = entry.getName()
          def version = entry.getProperties()[0].installers[0].id
          installedNodeJSVersions[name] = version
        }
        return installedNodeJSVersions.sort({it1, it2 -> it2.value <=> it1.value})
      }
      
      def nodejsAutodetect(Closure body) {
      
        def availableVersions = determineInstalledNodeJSVersions()
        // get lastest version
        def latestNodeJS = map.find { true }
        def nodeJSToUse = latestNodeJS.key
      
        if(!env.DETECTED_NODEJS_VERSION) {
          def packageJson = readJSON file: 'package.json'
          if(packageJson['engines'] && packageJson['engines']['node']) {
            versionString = packageJson['engines']['node']
            
            availableVersions.find { entry ->
              Semver semNPM = new Semver(entry.value, Semver.SemverType.NPM)    
              if(semNPM.satisfies(versionString)) {
                nodeJSToUse = entry.key
                env.DETECTED_NODEJS_VERSION = nodeJSToUse
                return true
              } else {
                return false
              }
            }
          } else {
            println "Did not find 'node' entry in package.json, so using latest installed nodejs."
          }
        } else {
          nodeJSToUse = env.DETECTED_NODEJS_VERSION
        }
      
        nodejs(nodeJSToUse) {
          body()
        }
      }
      

      I think it would be a nice improvement for your library.

      I am exited to hear what you think about it.

        Attachments

          Activity

          andi_bade Andi Bade created issue -
          andi_bade Andi Bade made changes -
          Field Original Value New Value
          Description As described [in nodejs docs|#engines]] you can specify the nodejs version suitable for your project in package.json.

          I think it would be nice if one could write something like this in pipeline:
          {code}
          nodejsAutodetect {
              // your node and npm scripts
          }{code}
          and then nodejs-plugin would scan all installed versions of node and check whether the version fits or not. If it fits it would use that version for the block.

          I have created a groovy script in a shared library for my current project which does that. It is far from perfect, but i think a good proof of concept:
          {code}
          import jenkins.plugins.nodejs.NodeJSUtils;
          @NonCPS
          def determineInstalledNodeJSVersions() {
            def installedNodeJSVersions = [:]
            NodeJSUtils.getInstallations().each { entry ->
              def name = entry.getName()
              def version = entry.getProperties()[0].installers[0].id
              installedNodeJSVersions[name] = version
            }
            return installedNodeJSVersions.sort({it1, it2 -> it2.value <=> it1.value})
          }

          def nodejsAutodetect(Closure body) {

            def availableVersions = determineInstalledNodeJSVersions()
            // get lastest version
            def latestNodeJS = map.find { true }
            def nodeJSToUse = latestNodeJS.key

            if(!env.DETECTED_NODEJS_VERSION) {
              def packageJson = readJSON file: 'package.json'
              if(packageJson['engines'] && packageJson['engines']['node']) {
                versionString = packageJson['engines']['node']
                dir('detect-node-tmp') {
                  nodejs(latestNodeJS.key) {
                    sh "npm install semver"
                    availableVersions.find { entry ->
                      def satisfies = sh(returnStdout: true,
                        script: "node -e \"console.log(require('semver').satisfies('${entry.value}', '${versionString}') ? 'true' : 'false')\"",
                        label: "Test Node Version").trim()

                      if(satisfies == 'true') {
                        nodeJSToUse = entry.key
                        env.DETECTED_NODEJS_VERSION = nodeJSToUse
                        return true
                      } else {
                        return false
                      }
                    }
                  }
                }
              } else {
                println "Did not find 'node' entry in package.json, so using latest installed nodejs."
              }
            } else {
              nodeJSToUse = env.DETECTED_NODEJS_VERSION
            }

            nodejs(nodeJSToUse) {
              body()
            }
          }
          {code}
          I think it would be a nice improvement for your library.

          I am exited to hear what you think about it.
          As described [in nodejs docs|#engines]] you can specify the nodejs version suitable for your project in package.json.

          I think it would be nice if one could write something like this in pipeline:
          {code:java}
          nodejsAutodetect {
              // your node and npm scripts
          }{code}
          and then nodejs-plugin would scan all installed versions of node and check whether the version fits or not. If it fits it would use that version for the block.

          I have created a groovy script in a shared library for my current project which does that. It is far from perfect, but i think a good proof of concept:
          {code:java}
          import jenkins.plugins.nodejs.NodeJSUtils;

          @Grab('com.vdurmont:semver4j:3.1.0')
          import com.vdurmont.semver4j.Semver

          @NonCPS
          def determineInstalledNodeJSVersions() {
            def installedNodeJSVersions = [:]
            NodeJSUtils.getInstallations().each { entry ->
              def name = entry.getName()
              def version = entry.getProperties()[0].installers[0].id
              installedNodeJSVersions[name] = version
            }
            return installedNodeJSVersions.sort({it1, it2 -> it2.value <=> it1.value})
          }

          def nodejsAutodetect(Closure body) {

            def availableVersions = determineInstalledNodeJSVersions()
            // get lastest version
            def latestNodeJS = map.find { true }
            def nodeJSToUse = latestNodeJS.key

            if(!env.DETECTED_NODEJS_VERSION) {
              def packageJson = readJSON file: 'package.json'
              if(packageJson['engines'] && packageJson['engines']['node']) {
                versionString = packageJson['engines']['node']
                
                availableVersions.find { entry ->
                  Semver semNPM = new Semver(entry.value, Semver.SemverType.NPM)
                  if(semNPM.satisfies(versionString)) {
                    nodeJSToUse = entry.key
                    env.DETECTED_NODEJS_VERSION = nodeJSToUse
                    return true
                  } else {
                    return false
                  }
                }
              } else {
                println "Did not find 'node' entry in package.json, so using latest installed nodejs."
              }
            } else {
              nodeJSToUse = env.DETECTED_NODEJS_VERSION
            }

            nodejs(nodeJSToUse) {
              body()
            }
          }
          {code}
          I think it would be a nice improvement for your library.

          I am exited to hear what you think about it.
          Hide
          nfalco Nikolas Falco added a comment - - edited

          I do not agree about this approace because it relies too on package.json and the engine does not force a specific version of NodeJS and npm version to use. No way to specify the other installer options.

          This changes also requires the repository content was already checkout. It's like the withMaven step auto install the version of maven and JDK based on what is described in the pom.xml

           

          Futhermore the installer (tool "xyz") in invoked before the build step by the workflow step and the installer is in sync with a semaphore per each node.

          Show
          nfalco Nikolas Falco added a comment - - edited I do not agree about this approace because it relies too on package.json and the engine does not force a specific version of NodeJS and npm version to use. No way to specify the other installer options. This changes also requires the repository content was already checkout. It's like the withMaven step auto install the version of maven and JDK based on what is described in the pom.xml   Futhermore the installer (tool "xyz") in invoked before the build step by the workflow step and the installer is in sync with a semaphore per each node.
          nfalco Nikolas Falco made changes -
          Resolution Won't Do [ 10001 ]
          Status Open [ 1 ] Closed [ 6 ]
          Hide
          nfalco Nikolas Falco added a comment - - edited

          In you pipeline you can

          def packageJson = readJSON package.json
          def version = packageJson?.engines?.node
          withEnv(["PATH+NODEJS=${tool "NodeJS " + version}"]) {
             ...
          }
          

          if that version is already installed on the node nothing happens

          Show
          nfalco Nikolas Falco added a comment - - edited In you pipeline you can def packageJson = readJSON package.json def version = packageJson?.engines?.node withEnv(["PATH+NODEJS=${tool "NodeJS " + version}"]) { ... } if that version is already installed on the node nothing happens
          Hide
          andi_bade Andi Bade added a comment -

          In package.json it typically would look like this:

          {
            "name": "project-name",
            "version": "1.12.0",
            "engines": {
              "node": ">=10.4 <11.0"
            },
            ...
          }

          So you would not specify a precise nodejs version but a range. So your withEnv approach would almost never work. That is why i check every version with Semver.satisfies. 

          And i am not sure if you got me right. I would not auto install a nodejs Version. I just try to find the best version among the already installed versions which fits the project.

          Show
          andi_bade Andi Bade added a comment - In package.json it typically would look like this: { "name" : "project-name" , "version" : "1.12.0" , "engines" : { "node" : ">=10.4 <11.0" }, ... } So you would not specify a precise nodejs version but a range. So your withEnv approach would almost never work. That is why i check every version with Semver.satisfies.  And i am not sure if you got me right. I would not auto install a nodejs Version. I just try to find the best version among the already installed versions which fits the project.

            People

            Assignee:
            nfalco Nikolas Falco
            Reporter:
            andi_bade Andi Bade
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Dates

              Created:
              Updated:
              Resolved: