diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ffd2707c..870a29d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,8 @@ on: tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-*' + workflow_dispatch: + jobs: run-build: @@ -23,7 +25,14 @@ jobs: run: Copy-Item (Get-Command node.exe | Select-Object -ExpandProperty Definition) . - run: npm test - run: npm run build + - name: Archive build artifact + if: github.ref_type == 'branch' + uses: actions/upload-artifact@v3 + with: + name: Installer + path: "build/out/NodistSetup-*.exe" - name: Create release draft + if: github.ref_type == 'tag' uses: ncipollo/release-action@v1 with: artifacts: "build/out/NodistSetup-*.exe" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e337933c..8310c42f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,11 +1,6 @@ name: run-tests on: - push: - branches: - - '**' - tags-ignore: - - '**' pull_request: jobs: diff --git a/README.md b/README.md index da073bc3..40db4bca 100644 --- a/README.md +++ b/README.md @@ -291,8 +291,9 @@ MIT License ## Changelog -v0.10.1 +v0.10.2 * Fix building shims (for newer go versions) by using go modules +* Fix npm shim to use correct node version * Add npx shim (works only for npm versions that ship with npx) * Fix getting latest npm version * Use last available x86 version for building (first node 18 versions are not available for x86) diff --git a/build/build.js b/build/build.js index 238577f1..f7390eaf 100644 --- a/build/build.js +++ b/build/build.js @@ -5,6 +5,7 @@ var fs = require('fs'); var mkdirp = require('mkdirp'); var ncp = require('ncp'); var path = require('path'); +var semver = require('semver'); var recursiveReaddir = require('recursive-readdir'); var request = require('request'); var rimraf = require('rimraf'); @@ -83,6 +84,7 @@ var npm = new (require('../lib/npm'))({nodistDir: stagingDir}); //default npm version to the latest at the time of writing var npmVersion = '6.14.16'; var nodeVersion = '16.15.0'; +var maxNodeMainVersion = '^20'; var versionPathx86 = ''; var versionPathx64 = ''; @@ -102,31 +104,14 @@ console.log('Welcome to the Nodist Builder'); console.log(' before going further we need to prep our staging folder'); //defining helper functions -function getLatestNodeVersionFor(nodeVersions, fileType) { +function getLatestUsableNodeVersionFor(nodeVersions, fileType) { for (var key in nodeVersions) { - if (nodeVersions[key].files.includes(fileType)) { - return nodeVersions[key].version; + if (nodeVersions[key].files.includes(fileType) && semver.satisfies(nodeVersions[key].version, maxNodeMainVersion)) { + return { nodeVersion: nodeVersions[key].version, npmVersion: nodeVersions[key].npm }; } } } -async function resolveLinkedWorkspaces(dirPath) { - let movedLinks = 0; - const files = await fs.readdirAsync(dirPath, { withFileTypes: true }); - const dirPromises = []; - for (const file of files) { - const filePath = path.join(dirPath, file.name); - if (file.isSymbolicLink()) { - const linkTarget = await fs.readlinkAsync(filePath); - await fs.renameAsync(path.join(dirPath, linkTarget), filePath); - movedLinks++; - } else if (file.isDirectory()) { - dirPromises.push(resolveLinkedWorkspaces(filePath)); - } - } - return (await Promise.all(dirPromises)).reduce((sum, num) => sum + num, movedLinks); -} - //start by clearing the staging and tmp folders P.all([ rimraf(outDir), @@ -193,19 +178,19 @@ P.all([ console.log('Finished copying static files'); console.log('Compiling node shim'); - return exec('go build -o "'+stagingBin +'/node.exe" shim-node.go', { cwd: goSrcDir }); + return exec('go build -o "'+stagingBin +'/node.exe" ./cmd/node', { cwd: goSrcDir }); }) .then(function(){ console.log('Done compiling node shim'); console.log('Compiling npm shim'); - return exec('go build -o "'+stagingBin +'/npm.exe" shim-npm.go', { cwd: goSrcDir }); + return exec('go build -o "'+stagingBin +'/npm.exe" ./cmd/npm', { cwd: goSrcDir }); }) .then(function(){ console.log('Done compiling npm shim'); console.log('Compiling npx shim'); - return exec('go build -o "'+stagingBin +'/npx.exe" shim-npx.go', { cwd: goSrcDir }); + return exec('go build -o "'+stagingBin +'/npx.exe" ./cmd/npx', { cwd: goSrcDir }); }) .then(function() { console.log('Done compiling npx shim'); @@ -217,7 +202,7 @@ P.all([ }); }) .then(function(res){ - nodeVersion = getLatestNodeVersionFor(res.body, 'win-x86-exe'); + ({ nodeVersion, npmVersion } = getLatestUsableNodeVersionFor(res.body, 'win-x86-exe')); nodeLatestUrlx86 = nodeLatestUrlx86.replace('VERSION',nodeVersion); nodeLatestUrlx64 = nodeLatestUrlx64.replace('VERSION',nodeVersion); console.log('Latest version of Node ' + nodeVersion); @@ -253,14 +238,9 @@ P.all([ ); }) .then(function(){ - console.log('Figure out the latest version of NPM'); - return npm.latestVersion(); - }) - .then(function(version){ - npmVersion = version; - var downloadLink = npm.downloadUrl(version); - console.log('Determined latest NPM as ' + npmVersion); - console.log('Downloading latest NPM from ' + downloadLink); + var downloadLink = npm.downloadUrl(npmVersion); + console.log('Determined matching NPM as ' + npmVersion); + console.log('Downloading matching NPM from ' + downloadLink); return Promise.resolve() .then(() => mkdirp(stagingNpmDir+'/'+npmVersion.replace('v',''))) .then(() => { @@ -291,7 +271,7 @@ P.all([ }) .then(function() { console.log('Installation complete'); - return resolveLinkedWorkspaces(path.join(stagingNpmDir, npmVersion.replace('v', ''), 'node_modules')); + return helper.resolveLinkedWorkspaces(path.join(stagingNpmDir, npmVersion.replace('v', '')), false); }) .then(function(movedLinks) { if (movedLinks) { diff --git a/lib/build.js b/lib/build.js index 5e60f44b..d76b68cb 100644 --- a/lib/build.js +++ b/lib/build.js @@ -4,8 +4,12 @@ var path = require('path'); var promisePipe = require('promisepipe'); var ProgressBar = require('progress'); var request = require('request'); +const P = require('bluebird'); var debug = require('debug')('nodist:build') +//make some promising APIs +P.promisifyAll(fs); + /** * Copy File * @param {string} source @@ -141,3 +145,51 @@ exports.downloadFileStream = function downloadFileStream(url) { }); return req } + +/** + * Npm version >= 18 using symlinks that do not work in windows and have to be fixed + * this function replace the broken symlinks with NTFS junction or move the directory if junctions are not supported + * + * @param {string} dirPath + * @param {boolean} preferSymlink + * @returns {Promise} number of changed links + */ +exports.resolveLinkedWorkspaces = async function resolveLinkedWorkspaces(dirPath, preferSymlink = true) { + let fixedLinks = 0; + const packageLockJson = JSON.parse(fs.readFileSync(path.join(dirPath, 'package-lock.json')).toString()); + await Promise.all(Object.entries(packageLockJson.packages) + .filter(([pkgPath, pkg]) => pkg.link === true) + .map(async ([pkgPath, pkg]) => { + + const linkPath = path.join(dirPath, pkgPath); + + + if (await fs.accessAsync(linkPath, fs.constants.F_OK).then(() => true).catch(() => false)) { + await fs.unlinkAsync(linkPath); + } + + let linkCreated = false; + if (preferSymlink) { + const linkTarget = path.join( + ...pkgPath.split('/').slice(0, -1).map(() => '..'), + pkg.resolved + ); + debug('Create symlink for ', linkPath, 'with target', linkTarget); + try { + await fs.symlinkAsync(linkTarget, linkPath, 'junction'); + linkCreated = true; + } catch (e) { + debug('Link ', linkPath, 'could not be created'); + } + } + if (!linkCreated) { + const from = path.join(dirPath, pkg.resolved); + debug('Move', from, 'to', linkPath); + await fs.renameAsync(from, linkPath); + } + + fixedLinks++; + })); + + return fixedLinks; +}; diff --git a/lib/npm.js b/lib/npm.js index 273f6ea9..65c3070e 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -36,41 +36,6 @@ module.exports = npmist var NPMIST = npmist.prototype -/** - * Npm version >= 18 using symlinks that do not work in windows and have to be fixed - * this function replace the broken symlinks with NTFS junction or move the directory if junctions are not supported - * - * @param {string} dirPath - * @returns {Promise} number of changed links - */ -async function resolveLinkedWorkspaces(dirPath) { - let fixedLinks = 0; - const packageLockJson = JSON.parse(fs.readFileSync(path.join(dirPath, 'package-lock.json')).toString()); - await Promise.all(Object.entries(packageLockJson.packages) - .filter(([pkgPath, pkg]) => pkg.link === true) - .map(async ([pkgPath, pkg]) => { - const linkTarget = path.join( - ...pkgPath.split('/').slice(0, -1).map(() => '..'), - pkg.resolved - ); - const linkPath = path.join(dirPath, pkgPath); - - debug('Create symlink for ', linkPath, 'with target', linkTarget); - if (await fs.accessAsync(linkPath, fs.constants.F_OK).then(() => true).catch(() => false)) { - await fs.unlinkAsync(linkPath); - } - - try { - await fs.symlinkAsync(linkTarget, linkPath, 'junction'); - } catch (e) { - await fs.renameAsync(path.join(dirPath, linkTarget), linkPath); - } - fixedLinks++; - })); - - return fixedLinks; -} - /** * List available NPM versions * @return {string} @@ -331,7 +296,7 @@ NPMIST.install = function(v,done){ .then(() => { if (semver.gte(version, '8.0.0')) { debug('Fix symlinks for npm version >= 8'); - return resolveLinkedWorkspaces(path.join(archivePath)) + return buildHelper.resolveLinkedWorkspaces(path.join(archivePath)) .then(fixedLinks => { debug(`Fixed ${fixedLinks} symlinks for npm node_modules`); }); diff --git a/src/cmd/node/main.go b/src/cmd/node/main.go new file mode 100644 index 00000000..2a6f0a32 --- /dev/null +++ b/src/cmd/node/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "os" + "os/exec" + + nodist "github.com/nodists/nodist/internal" +) + +func main() { + err, nodebin, _, _ := nodist.DetermineNodeExecutable("node") + + // Run node! + cmd := exec.Command(nodebin, os.Args[1:]...) + nodist.ExecuteCommand(cmd, err) +} diff --git a/src/cmd/npm/main.go b/src/cmd/npm/main.go new file mode 100644 index 00000000..d63f8a4e --- /dev/null +++ b/src/cmd/npm/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + "os/exec" + + nodist "github.com/nodists/nodist/internal" +) + +func main() { + err, nodebin, dir, nodeVersion := nodist.DetermineNodeExecutable("node") + + path, _ := nodist.DetermineNpmPath(dir, nodeVersion) + + npmbin := path + "/bin/npm-cli.js" + + args := []string{npmbin} + args = append(args, os.Args[1:]...) + + // Run npm! + cmd := exec.Command(nodebin, args...) + nodist.ExecuteCommand(cmd, err) +} diff --git a/src/cmd/npx/main.go b/src/cmd/npx/main.go new file mode 100644 index 00000000..2c4f168d --- /dev/null +++ b/src/cmd/npx/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + + nodist "github.com/nodists/nodist/internal" +) + +func main() { + err, nodebin, dir, nodeVersion := nodist.DetermineNodeExecutable("node") + + path, npmVersion := nodist.DetermineNpmPath(dir, nodeVersion) + + npxbin := path + "/bin/npx-cli.js" + + if _, err := os.Stat(npxbin); errors.Is(err, os.ErrNotExist) { + fmt.Println("Npx not found for selected npm version:", npmVersion) + os.Exit(47) + } + + args := []string{npxbin} + args = append(args, os.Args[1:]...) + + // Run npx! + cmd := exec.Command(nodebin, args...) + nodist.ExecuteCommand(cmd, err) +} diff --git a/src/go.mod b/src/go.mod index 8a1ed8ed..20fbf44d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -1,6 +1,6 @@ module github.com/nodists/nodist -go 1.12 +go 1.20 require ( github.com/marcelklehr/semver v0.0.0-20160716173943-4590ea5640ed // indirect diff --git a/src/internal/nodist.go b/src/internal/nodist.go new file mode 100644 index 00000000..cc655008 --- /dev/null +++ b/src/internal/nodist.go @@ -0,0 +1,463 @@ +package nodist + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/signal" + "path/filepath" + "sort" + "strings" + "syscall" + + "github.com/marcelklehr/semver" + + . "github.com/visionmedia/go-debug" +) + +var debug = Debug("nodist:shim") + +const pathSep = string(os.PathSeparator) + +func GetCurrentNodeVersionSpec(currentDir string) (spec string) { + // Determine version spec + var v string + clever := os.Getenv("NODIST_INSPECT_PACKAGEJSON") + if v = os.Getenv("NODE_VERSION"); v != "" { + spec = v + debug("NODE_VERSION found:'%s'", spec) + } else if v = os.Getenv("NODIST_NODE_VERSION"); v != "" { + spec = v + debug("NODIST_NODE_VERSION found:'%s'", spec) + } else if v, err := getLocalEngineNode(currentDir); clever != "" && err == nil && strings.Trim(string(v), " \r\n") != "" { + spec = v + debug("Target engine found:'%s'", spec) + } else if v, localFile, err := getLocalNodeVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" { + spec = string(v) + debug("Local file found:'%s' @ %s", spec, localFile) + } else if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX") + "\\.node-version-global"); err == nil { + spec = string(v) + debug("Global file found: '%s'", spec) + } + + spec = strings.Trim(spec, "v \r\n") + return +} + +func GetCurrentNpmVersionSpec(currentDir string) (spec string) { + // Determine version spec + var v string + clever := os.Getenv("NODIST_INSPECT_PACKAGEJSON") + if v = os.Getenv("NODIST_NPM_VERSION"); v != "" { + spec = v + debug("NODIST_NPM_VERSION found:'%s'", spec) + } else if v, err := getLocalEngineNpm(currentDir); clever != "" && err == nil && strings.Trim(string(v), " \r\n") != "" { + spec = v + debug("Target engine npm spec found:'%s'", spec) + } else if v, localFile, err := getLocalNpmVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" { + spec = string(v) + debug("Local file with npm spec found:'%s' @ %s", spec, localFile) + } else if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX") + "\\.npm-version-global"); err == nil { + spec = string(v) + debug("Global file found: '%s'", spec) + } + + spec = strings.Trim(spec, "v \r\n") + return +} + +func ResolveNodeVersion(spec string) (version string, err error) { + // Find an installed version matching the spec... + + installed, err := GetInstalledNodeVersions() + + if err != nil { + return + } + + version, err = resolveVersion(spec, installed) + return +} + +func GetInstalledNodeVersions() (versions []*semver.Version, err error) { + // Determine architecture + x64 := false + if wantX64 := os.Getenv("NODIST_X64"); wantX64 != "" { + x64 = (wantX64 == "1") + } + // construct path to version dir + path := os.Getenv("NODIST_PREFIX") + "/v" + if x64 { + path += "-x64" + } + versions, err = getInstalledVersions(path) + return +} + +func ResolveNpmVersion(spec string, nodeVersion string) (version string, err error) { + // Find an installed version matching the spec... + + installed, err := GetInstalledNpmVersions() + + if err != nil { + return + } + + if spec == "match" { + spec, err = getMatchingNpmVersion(nodeVersion) + if err != nil { + return + } + // we feed this result to resolveVersion, too, because we need + // to see if it is actually installed + } + + version, err = resolveVersion(spec, installed) + return +} + +func resolveVersion(spec string, installed []*semver.Version) (version string, err error) { + var constraint *semver.Constraints + + if spec != "latest" { + constraint, err = semver.NewConstraint(spec) + + if err != nil { + return + } + } + + if spec == "latest" { + version = installed[0].String() + } else { + for _, v := range installed { + debug("checking %s against %s", v.String(), spec) + if constraint.Check(v) { + version = v.String() + break + } + } + } + + if version == "" { + err = errors.New("Couldn't find any matching version") + } + return +} + +type Version struct { + Version string + Npm string +} + +func getMatchingNpmVersion(nodeVersion string) (version string, err error) { + file := os.Getenv("NODIST_PREFIX") + pathSep + "versions.json" + rawJSON, err := ioutil.ReadFile(file) + if err != nil { + return + } + var versions []Version + err = json.Unmarshal(rawJSON, &versions) + if err != nil { + return + } + for i := 0; i < len(versions); i++ { + if versions[i].Version[1:] != nodeVersion { + continue + } + version = versions[i].Npm + return + } + err = errors.New("No npm version found that matches node version " + nodeVersion) + return +} + +func GetInstalledNpmVersions() (versions []*semver.Version, err error) { + // construct path to version dir + path := os.Getenv("NODIST_PREFIX") + "/npmv" + versions, err = getInstalledVersions(path) + return +} + +func getInstalledVersions(path string) (versions []*semver.Version, err error) { + entries, err := ioutil.ReadDir(path) + if err != nil { + return + } + + versions = make([]*semver.Version, 0) + for _, entry := range entries { + if !entry.IsDir() { + continue + } + v, err := semver.NewVersion(entry.Name()) + if err == nil { + versions = append(versions, v) + } + } + + sort.Sort(sort.Reverse(semver.Collection(versions))) + + return +} + +func getLocalNodeVersion(dir string) (version string, file string, err error) { + version, file, err = getLocalVersion(dir, ".node-version") + return +} + +func getLocalNpmVersion(dir string) (version string, file string, err error) { + version, file, err = getLocalVersion(dir, ".npm-version") + return +} + +func getLocalVersion(dir string, filename string) (version string, file string, returnedError error) { + dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist] + + for len(dirSlice) != 1 { + dir = strings.Join(dirSlice, pathSep) + file = dir + pathSep + filename + v, err := ioutil.ReadFile(file) + + if err == nil { + version = string(v) + return + } + + if !os.IsNotExist(err) { + returnedError = err // some other error.. bad luck. + return + } + + // `$ cd ..` + dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir + } + + version = "" + return +} + +func getLocalEngineNode(dir string) (spec string, err error) { + packageJSON, err := getLocalPackageJSON(dir) + if err != nil { + return + } + spec = packageJSON.Engines.Node + return +} + +func getLocalEngineNpm(dir string) (spec string, err error) { + packageJSON, err := getLocalPackageJSON(dir) + if err != nil { + return + } + spec = packageJSON.Engines.Npm + return +} + +func getLocalPackageJSON(dir string) (packageJSON PackageJSON, returnedError error) { + debug("getTargetEngine: targetDir: %s", dir) + + dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist] + + for len(dirSlice) != 1 { + dir = strings.Join(dirSlice, pathSep) + file := dir + "\\package.json" + rawPackageJSON, err := ioutil.ReadFile(file) + debug("getTargetEngine: ReadFile %s", file) + if err == nil { + // no error handling for parsing, cause we don't want to use a different package.json if we've already found one + packageJSON, returnedError = parsePackageJSON(rawPackageJSON) + return + } + + if !os.IsNotExist(err) { + returnedError = err // some other error.. bad luck. + return + } + + // `$ cd ..` + dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir + } + + return +} + +type PackageJSON struct { + Engines struct { + Npm string + Node string + } +} + +func parsePackageJSON(rawPackageJSON []byte) (packageJSON PackageJSON, err error) { + err = json.Unmarshal(rawPackageJSON, &packageJSON) + + if err == nil { + debug("parsePackageJSON: %+v", packageJSON) + return + } + + debug("parsePackageJSON: error: %s", err.Error()) + + // incorrect JSON -- bad luck + return +} + +func getTargetDirectoryForNode() (dir string, err error) { + if len(os.Args) < 2 { + dir, err = os.Getwd() + if err != nil { + return + } + return + } else { + targetFile := os.Args[1] + dir = filepath.Dir(targetFile) + + if filepath.IsAbs(dir) { + return + } + + var cwd string + cwd, err = os.Getwd() + if err != nil { + return + } + dir = filepath.Join(cwd, dir) + return + } +} + +func getTargetDirectoryForNpm() (dir string, returnedError error) { + dir, returnedError = os.Getwd() + return +} + +func DetermineNodeExecutable(runType string) (error, string, string, string) { + if "" == os.Getenv("NODIST_PREFIX") { + fmt.Println("Please set the path to the nodist directory in the NODIST_PREFIX environment variable.") + os.Exit(40) + } + + var dir string = "" + var err error = nil + if runType == "node" { + dir, err = getTargetDirectoryForNode() + } else { + dir, err = getTargetDirectoryForNpm() + } + + if err != nil { + fmt.Println("Sorry, there's a problem with nodist. Couldn't determine the target directory. Please report this.") + os.Exit(46) + } + + debug("current target directory: %s", dir) + + // Determine version spec + spec := GetCurrentNodeVersionSpec(dir) + + if spec == "" { + fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which node version to use. Please set a version.") + os.Exit(41) + } + + debug("Current version spec: %s", spec) + + version, err := ResolveNodeVersion(spec) + + if err != nil { + fmt.Printf("Sorry, there's a problem with nodist. Couldn't resolve version spec %s: %s\n", spec, err.Error()) + os.Exit(44) + } + + debug("found matching version: %s", version) + + // Determine architecture + + x64 := false + + if wantX64 := os.Getenv("NODIST_X64"); wantX64 != "" { + x64 = (wantX64 == "1") + } + debug("Determined architecture: x64:%s", x64) + + // Set up binary path + + var path string + var nodebin string + + path = os.Getenv("NODIST_PREFIX") + "/v" + + if x64 { + path += "-x64" + } + + path = path + "/" + version + nodebin = path + "/node.exe" + debug("Going to execute the following binary: %s", nodebin) + return err, nodebin, dir, version +} + +func ExecuteCommand(cmd *exec.Cmd, err error) { + // Proxy stdio + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + + // Set npm prefix correctly. Can't do this in installer, since npm doesn't know where to look (it looks at /v/x.x.x/ by default, so we'd have to put an npmrc in every version folder, which is overkill) + cmd.Env = append(os.Environ(), "npm_config_prefix="+os.Getenv("NODIST_PREFIX")+"/bin") + + // Proxy signals + sigc := make(chan os.Signal, 1) + signal.Notify(sigc) + go func() { + for s := range sigc { + cmd.Process.Signal(s) + } + }() + + err = cmd.Run() + signal.Stop(sigc) + + if err != nil { + exitError, isExitError := err.(*(exec.ExitError)) + if isExitError { + // You know it. Black Magic... + os.Exit(exitError.Sys().(syscall.WaitStatus).ExitStatus()) + } else { + fmt.Println("Sorry, there's a problem with nodist.") + fmt.Println("Error: ", err) + os.Exit(42) + } + } +} + +func DetermineNpmPath(dir string, nodeVersion string) (string, string) { + + npmSpec := GetCurrentNpmVersionSpec(dir) + debug("current npm version spec: %s", npmSpec) + + if npmSpec == "" { + fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which npm version to use. Please set a version.") + os.Exit(41) + } + + npmVersion, err := ResolveNpmVersion(npmSpec, nodeVersion) + + if err != nil { + fmt.Println("Sorry, there's a problem with nodist. Couldn't resolve npm version spec", npmSpec, ":", err.Error()) + os.Exit(44) + } + + debug("determined npm version: %s", npmVersion) + + path := os.Getenv("NODIST_PREFIX") + "/npmv" + + path = path + "/" + npmVersion + return path, npmVersion +} diff --git a/src/lib/nodist.go b/src/lib/nodist.go deleted file mode 100644 index c43f7616..00000000 --- a/src/lib/nodist.go +++ /dev/null @@ -1,308 +0,0 @@ -package lib - -import ( - "errors" - "os" - "io/ioutil" - "strings" - "sort" - "encoding/json" - "github.com/marcelklehr/semver" -) - -import . "github.com/visionmedia/go-debug" - -var debug = Debug("nodist:shim") -const pathSep = string(os.PathSeparator) - -func GetCurrentNodeVersionSpec(currentDir string) (spec string) { - // Determine version spec - var v string - clever := os.Getenv("NODIST_INSPECT_PACKAGEJSON"); - if v = os.Getenv("NODE_VERSION"); v != "" { - spec = v - debug("NODE_VERSION found:'%s'", spec) - } else - if v = os.Getenv("NODIST_NODE_VERSION"); v != "" { - spec = v - debug("NODIST_NODE_VERSION found:'%s'", spec) - } else - if v, err := getLocalEngineNode(currentDir); clever != "" && err == nil && strings.Trim(string(v), " \r\n") != "" { - spec = v - debug("Target engine found:'%s'", spec) - } else - if v, localFile, err := getLocalNodeVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" { - spec = string(v) - debug("Local file found:'%s' @ %s", spec, localFile) - } else - if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX")+"\\.node-version-global"); err == nil { - spec = string(v) - debug("Global file found: '%s'", spec) - } - - spec = strings.Trim(spec, "v \r\n") - return -} - -func GetCurrentNpmVersionSpec(currentDir string) (spec string) { - // Determine version spec - var v string - clever := os.Getenv("NODIST_INSPECT_PACKAGEJSON"); - if v = os.Getenv("NODIST_NPM_VERSION"); v != "" { - spec = v - debug("NODIST_NPM_VERSION found:'%s'", spec) - } else - if v, err := getLocalEngineNpm(currentDir); clever != "" && err == nil && strings.Trim(string(v), " \r\n") != "" { - spec = v - debug("Target engine npm spec found:'%s'", spec) - } else - if v, localFile, err := getLocalNpmVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" { - spec = string(v) - debug("Local file with npm spec found:'%s' @ %s", spec, localFile) - } else - if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX")+"\\.npm-version-global"); err == nil { - spec = string(v) - debug("Global file found: '%s'", spec) - } - - spec = strings.Trim(spec, "v \r\n") - return -} - -func ResolveNodeVersion(spec string) (version string, err error){ - // Find an installed version matching the spec... - - installed, err := GetInstalledNodeVersions() - - if err != nil { - return - } - - version, err = resolveVersion(spec, installed) - return -} - -func GetInstalledNodeVersions() (versions []*semver.Version, err error) { - // Determine architecture - x64 := false - if wantX64 := os.Getenv("NODIST_X64"); wantX64 != "" { - x64 = (wantX64 == "1") - } - // construct path to version dir - path := os.Getenv("NODIST_PREFIX")+"/v" - if x64 { - path += "-x64" - } - versions, err = getInstalledVersions(path) - return -} - -func ResolveNpmVersion(spec string, nodeVersion string) (version string, err error){ - // Find an installed version matching the spec... - - installed, err := GetInstalledNpmVersions() - - if err != nil { - return - } - - if spec == "match" { - spec, err = getMatchingNpmVersion(nodeVersion) - if err != nil { - return - } - // we feed this result to resolveVersion, too, because we need - // to see if it is actually installed - } - - version, err = resolveVersion(spec, installed) - return -} - -func resolveVersion(spec string, installed []*semver.Version) (version string, err error) { - var constraint *semver.Constraints - - if spec != "latest" { - constraint, err = semver.NewConstraint(spec) - - if err != nil { - return - } - } - - if spec == "latest" { - version = installed[0].String() - }else{ - for _, v := range installed { - debug("checking %s against %s", v.String(), spec) - if constraint.Check(v) { - version = v.String() - break - } - } - } - - if version == "" { - err = errors.New("Couldn't find any matching version") - } - return -} - -type Version struct { - Version string - Npm string -} - -func getMatchingNpmVersion(nodeVersion string) (version string, err error) { - file := os.Getenv("NODIST_PREFIX")+pathSep+"versions.json" - rawJSON, err := ioutil.ReadFile(file) - if err != nil { - return - } - var versions []Version - err = json.Unmarshal(rawJSON, &versions) - if err != nil { - return - } - for i:=0; i < len(versions); i++ { - if versions[i].Version[1:] != nodeVersion { - continue - } - version = versions[i].Npm - return - } - err = errors.New("No npm version found that matches node version "+nodeVersion) - return -} - -func GetInstalledNpmVersions() (versions []*semver.Version, err error) { - // construct path to version dir - path := os.Getenv("NODIST_PREFIX")+"/npmv" - versions, err = getInstalledVersions(path) - return -} - -func getInstalledVersions(path string) (versions []*semver.Version, err error) { - entries, err := ioutil.ReadDir(path) - if err != nil { - return - } - - versions = make([]*semver.Version, 0) - for _, entry := range entries { - if !entry.IsDir() { - continue - } - v, err := semver.NewVersion(entry.Name()) - if err == nil { - versions = append(versions, v) - } - } - - sort.Sort(sort.Reverse(semver.Collection(versions))) - - return -} - -func getLocalNodeVersion(dir string) (version string, file string, err error) { - version, file, err = getLocalVersion(dir, ".node-version") - return -} - -func getLocalNpmVersion(dir string) (version string, file string, err error) { - version, file, err = getLocalVersion(dir, ".npm-version") - return -} - -func getLocalVersion(dir string, filename string) (version string, file string, returnedError error) { - dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist] - - for len(dirSlice) != 1 { - dir = strings.Join(dirSlice, pathSep) - file = dir+pathSep+filename - v, err := ioutil.ReadFile(file); - - if err == nil { - version = string(v) - return - } - - if !os.IsNotExist(err) { - returnedError = err // some other error.. bad luck. - return - } - - // `$ cd ..` - dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir - } - - version = "" - return -} - -func getLocalEngineNode(dir string) (spec string, err error) { - packageJSON, err := getLocalPackageJSON(dir) - if err != nil { - return - } - spec = packageJSON.Engines.Node - return -} - -func getLocalEngineNpm(dir string) (spec string, err error) { - packageJSON, err := getLocalPackageJSON(dir) - if err != nil { - return - } - spec = packageJSON.Engines.Npm - return -} - -func getLocalPackageJSON(dir string) (packageJSON PackageJSON, returnedError error) { - debug("getTargetEngine: targetDir: %s", dir) - - dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist] - - for len(dirSlice) != 1 { - dir = strings.Join(dirSlice, pathSep) - file := dir+"\\package.json" - rawPackageJSON, err := ioutil.ReadFile(file); - debug("getTargetEngine: ReadFile %s", file) - if err == nil { - // no error handling for parsing, cause we don't want to use a different package.json if we've already found one - packageJSON, returnedError = parsePackageJSON(rawPackageJSON) - return - } - - if !os.IsNotExist(err) { - returnedError = err // some other error.. bad luck. - return - } - - // `$ cd ..` - dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir - } - - return -} - -type PackageJSON struct { - Engines struct { - Npm string - Node string - } -} - -func parsePackageJSON(rawPackageJSON []byte) (packageJSON PackageJSON, err error) { - err = json.Unmarshal(rawPackageJSON, &packageJSON) - - if err == nil { - debug("parsePackageJSON: %+v", packageJSON) - return - } - - debug("parsePackageJSON: error: %s", err.Error()) - - // incorrect JSON -- bad luck - return -} diff --git a/src/shim-node.go b/src/shim-node.go deleted file mode 100644 index c4325ae7..00000000 --- a/src/shim-node.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "os/exec" - "os/signal" - "syscall" - nodist "github.com/nodists/nodist/lib" -) - -import . "github.com/visionmedia/go-debug" - -var debug = Debug("nodist:shim-node") - -func main() { - if "" == os.Getenv("NODIST_PREFIX") { - fmt.Println("Please set the path to the nodist directory in the NODIST_PREFIX environment variable.") - os.Exit(40) - } - - dir, err := getTargetDirectory() - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't determine the target directory. Please report this.") - os.Exit(46) - } - - debug("current target directory: %s", dir) - - // Determine version spec - spec := nodist.GetCurrentNodeVersionSpec(dir) - - if spec == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which node version to use. Please set a version.") - os.Exit(41) - } - - debug("Current version spec: %s", spec) - - version, err := nodist.ResolveNodeVersion(spec) - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't resolve version spec %s: %s", spec, err.Error()) - os.Exit(44) - } - - debug("found matching version: %s", version) - - // Determine architecture - - x64 := false - - if wantX64 := os.Getenv("NODIST_X64"); wantX64 != "" { - x64 = (wantX64 == "1") - } - debug("Determined architecture: x64:%s", x64) - - // Set up binary path - - var path string - var nodebin string - - path = os.Getenv("NODIST_PREFIX")+"/v" - - if x64 { - path += "-x64" - } - - path = path+"/"+version - nodebin = path+"/node.exe" - debug("Going to execute the following binary: %s", nodebin) - - // Run node! - - cmd := exec.Command(nodebin, os.Args[1:]...) - - // Proxy stdio - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - // Set npm prefix correctly. Can't do this in installer, since npm doesn't know where to look (it looks at /v/x.x.x/ by default, so we'd have to put an npmrc in every version folder, which is overkill) - cmd.Env = append(os.Environ(), "npm_config_prefix="+os.Getenv("NODIST_PREFIX")+"/bin") - - // Proxy signals - sigc := make(chan os.Signal, 1) - signal.Notify(sigc) - go func() { - for s := range sigc { - cmd.Process.Signal(s) - } - }() - - err = cmd.Run() - signal.Stop(sigc) - - if err != nil { - exitError, isExitError := err.(*(exec.ExitError)) - if isExitError { - // You know it. Black Magic... - os.Exit(exitError.Sys().(syscall.WaitStatus).ExitStatus()) - } else { - fmt.Println("Sorry, there's a problem with nodist.") - fmt.Println("Error: ", err) - os.Exit(42) - } - } -} - -func getTargetDirectory() (dir string, err error) { - if len(os.Args) < 2 { - dir, err = os.Getwd() - if err != nil { - return - } - return - }else{ - targetFile := os.Args[1] - dir = filepath.Dir(targetFile) - - if filepath.IsAbs(dir) { - return - } - - var cwd string - cwd, err = os.Getwd() - if err != nil { - return - } - dir = filepath.Join(cwd, dir) - return - } -} diff --git a/src/shim-npm.go b/src/shim-npm.go deleted file mode 100644 index 9ca86e25..00000000 --- a/src/shim-npm.go +++ /dev/null @@ -1,121 +0,0 @@ -package main - -import ( - "fmt" - "os" - "os/exec" - "os/signal" - "syscall" - nodist "github.com/nodists/nodist/lib" -) - -import . "github.com/visionmedia/go-debug" - -var debug = Debug("nodist:shim-npm") - -func main() { - if "" == os.Getenv("NODIST_PREFIX") { - fmt.Println("Please set the path to the nodist directory in the NODIST_PREFIX environment variable.") - os.Exit(40) - } - - dir, err := getTargetDirectory() - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't determine the target directory. Please report this.") - os.Exit(46) - } - - // Determine node version first - - spec := nodist.GetCurrentNodeVersionSpec(dir) - - if spec == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which node version to use. Please set a version.") - os.Exit(41) - } - - version, err := nodist.ResolveNodeVersion(spec) - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't resolve node version spec %s: %s", spec, err.Error()) - os.Exit(44) - } - - if version == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't find an installed node version that matches version spec ", spec) - os.Exit(45) - } - - debug("determined node version: %s", version) - - // Determine npm version - - npmSpec := nodist.GetCurrentNpmVersionSpec(dir) - debug("current npm version spec: %s", npmSpec) - - if npmSpec == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which npm version to use. Please set a version.") - os.Exit(41) - } - - npmVersion, err := nodist.ResolveNpmVersion(npmSpec, version) - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't resolve npm version spec", npmSpec, ":", err.Error()) - os.Exit(44) - } - - debug("determined npm version: %s", npmVersion) - - // Set up binary path - - path := os.Getenv("NODIST_PREFIX")+"/npmv" - - path = path+"/"+npmVersion - npmbin := path+"/bin/npm-cli.js" - - args := []string{npmbin} - args = append(args, os.Args[1:]...) - - // Run npm! - - cmd := exec.Command("node", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "NODIST_NODE_VERSION="+version)// Lock the node version for all child processes - // Set npm prefix correctly. Can't do this in installer, since npm doesn't know where to look (it looks at /v/x.x.x/ by default, so we'd have to put an npmrc in every version folder, which is overkill) - cmd.Env = append(cmd.Env, "npm_config_prefix="+os.Getenv("NODIST_PREFIX")+"/bin") - - - // Proxy signals - sigc := make(chan os.Signal, 1) - signal.Notify(sigc) - go func() { - for s := range sigc { - cmd.Process.Signal(s) - } - }() - - err = cmd.Run() - signal.Stop(sigc) - - if err != nil { - exitError, isExitError := err.(*(exec.ExitError)) - if isExitError { - // You know it. Black Magic... - os.Exit(exitError.Sys().(syscall.WaitStatus).ExitStatus()) - } else { - fmt.Println("Sorry, there's a problem with nodist.") - fmt.Println("Error: ", err) - os.Exit(42) - } - } -} - -func getTargetDirectory() (dir string, returnedError error) { - dir, returnedError = os.Getwd() - return -} diff --git a/src/shim-npx.go b/src/shim-npx.go deleted file mode 100644 index b6a24c60..00000000 --- a/src/shim-npx.go +++ /dev/null @@ -1,127 +0,0 @@ -package main - -import ( - "fmt" - "os" - "errors" - "os/exec" - "os/signal" - "syscall" - nodist "github.com/nodists/nodist/lib" -) - -import . "github.com/visionmedia/go-debug" - -var debug = Debug("nodist:shim-npx") - -func main() { - if "" == os.Getenv("NODIST_PREFIX") { - fmt.Println("Please set the path to the nodist directory in the NODIST_PREFIX environment variable.") - os.Exit(40) - } - - dir, err := getTargetDirectory() - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't determine the target directory. Please report this.") - os.Exit(46) - } - - // Determine node version first - - spec := nodist.GetCurrentNodeVersionSpec(dir) - - if spec == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which node version to use. Please set a version.") - os.Exit(41) - } - - version, err := nodist.ResolveNodeVersion(spec) - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't resolve node version spec %s: %s", spec, err.Error()) - os.Exit(44) - } - - if version == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't find an installed node version that matches version spec ", spec) - os.Exit(45) - } - - debug("determined node version: %s", version) - - // Determine npm version - - npmSpec := nodist.GetCurrentNpmVersionSpec(dir) - debug("current npm version spec: %s", npmSpec) - - if npmSpec == "" { - fmt.Println("Sorry, there's a problem with nodist. Couldn't decide which npm version to use. Please set a version.") - os.Exit(41) - } - - npmVersion, err := nodist.ResolveNpmVersion(npmSpec, version) - - if err != nil { - fmt.Println("Sorry, there's a problem with nodist. Couldn't resolve npm version spec", npmSpec, ":", err.Error()) - os.Exit(44) - } - - debug("determined npm version: %s", npmVersion) - - // Set up binary path - - path := os.Getenv("NODIST_PREFIX")+"/npmv" - - path = path+"/"+npmVersion - npxbin := path+"/bin/npx-cli.js" - - if _, err := os.Stat(npxbin); errors.Is(err, os.ErrNotExist) { - fmt.Println("Npx not found for selected npm version:", npmVersion); - os.Exit(47); - } - - args := []string{npxbin} - args = append(args, os.Args[1:]...) - - // Run npm! - - cmd := exec.Command("node", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "NODIST_NODE_VERSION="+version)// Lock the node version for all child processes - // Set npm prefix correctly. Can't do this in installer, since npm doesn't know where to look (it looks at /v/x.x.x/ by default, so we'd have to put an npmrc in every version folder, which is overkill) - cmd.Env = append(cmd.Env, "npm_config_prefix="+os.Getenv("NODIST_PREFIX")+"/bin") - - - // Proxy signals - sigc := make(chan os.Signal, 1) - signal.Notify(sigc) - go func() { - for s := range sigc { - cmd.Process.Signal(s) - } - }() - - err = cmd.Run() - signal.Stop(sigc) - - if err != nil { - exitError, isExitError := err.(*(exec.ExitError)) - if isExitError { - // You know it. Black Magic... - os.Exit(exitError.Sys().(syscall.WaitStatus).ExitStatus()) - } else { - fmt.Println("Sorry, there's a problem with nodist.") - fmt.Println("Error: ", err) - os.Exit(42) - } - } -} - -func getTargetDirectory() (dir string, returnedError error) { - dir, returnedError = os.Getwd() - return -}