Browse Source

Nsw

remotes/origin/add-simple-redirecting-site
Gal Schlezinger 6 years ago
parent
commit
f9de4c152e
  1. 9
      .ci/create-static-binary.sh
  2. 6
      .ci/esy-build-steps.yml
  3. 1
      .ci/prepare-static-build.sh
  4. 3
      .ci/shasum
  5. 4
      .dockerignore
  6. 1
      .gitignore
  7. 18
      Dockerfile
  8. 59
      README.md
  9. 41
      azure-pipelines.yml
  10. BIN
      docs/nvm_comparison.png
  11. 262
      esy.lock/index.json
  12. 36
      esy.lock/opam/cmdliner.1.0.3/opam
  13. 35
      esy.lock/opam/lambdasoup.0.6.3/opam
  14. 52
      esy.lock/opam/markup.0.8.0/opam
  15. 35
      esy.lock/opam/semver.0.1.0/opam
  16. 49
      esy.lock/opam/topkg.1.0.0/opam
  17. 36
      esy.lock/opam/uchar.0.0.2/opam
  18. 38
      esy.lock/opam/uutf.1.0.1/opam
  19. 9
      executable/Env.re
  20. 101
      executable/Install.re
  21. 137
      executable/ListInstallations.re
  22. 24
      executable/ListLocal.re
  23. 26
      executable/ListRemote.re
  24. 100
      executable/NswApp.re
  25. 63
      executable/Use.re
  26. 6
      executable/dune
  27. 8
      feature_tests/basic/run.sh
  28. 1
      feature_tests/nvmrc/.nvmrc
  29. 10
      feature_tests/nvmrc/run.sh
  30. 32
      feature_tests/run.sh
  31. 15
      library/Compression.re
  32. 13
      library/Directories.re
  33. 19
      library/Fs.re
  34. 53
      library/Http.re
  35. 14
      library/Nvmrc.re
  36. 33
      library/Opt.re
  37. 43
      library/Result.re
  38. 70
      library/System.re
  39. 1
      library/Util.re
  40. 147
      library/Versions.re
  41. 2
      library/dune
  42. 21
      package.json
  43. 17
      test/SmokeTest.re
  44. 31
      test/TestFramework.re
  45. 5
      test/TestNsw.re
  46. 3
      test/__snapshots__/Smoke_test.4d362c3c.0.snapshot
  47. 3
      test/dune

9
.ci/create-static-binary.sh

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
#!/bin/bash
echo "Building binary in docker"
docker build . -t schlez/nsw-static-binary
echo "Copying to ./nsw-linux"
docker run --rm -v $(pwd):$(pwd) --workdir $(pwd) schlez/nsw-static-binary cp /app/_build/default/executable/NswApp.exe ./nsw-linux

6
.ci/esy-build-steps.yml

@ -4,8 +4,8 @@ steps: @@ -4,8 +4,8 @@ steps:
- task: NodeTool@0
inputs:
versionSpec: '8.9'
- script: npm install -g esy@0.4.3
displayName: 'npm install -g esy@0.4.3'
- script: npm install -g esy@latest
displayName: 'npm install -g esy@latest'
- script: esy install
displayName: 'esy install'
- script: esy pesy
@ -14,7 +14,7 @@ steps: @@ -14,7 +14,7 @@ steps:
displayName: 'esy build'
- script: esy test
displayName: 'esy test'
- script: esy x NswApp.exe
- script: esy x nsw.exe
displayName: 'Run the main binary'
- script: esy ls-libs
continueOnError: true

1
.ci/prepare-static-build.sh

@ -0,0 +1 @@ @@ -0,0 +1 @@
sed -i 's@"flags": \[\]@"flags": ["-ccopt", "-static"]@' package.json

3
.ci/shasum

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
#!/bin/bash
shift 2
sha1sum $@

4
.dockerignore

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
_build
_esy
node_modules
Dockerfile

1
.gitignore vendored

@ -9,3 +9,4 @@ _esy/ @@ -9,3 +9,4 @@ _esy/
nsw.install
.DS_Store
*.install
.tmp

18
Dockerfile

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
FROM frolvlad/alpine-glibc
RUN apk add --no-cache nodejs bash npm curl g++ make m4 patch gmp-dev perl git jq
ADD .ci/shasum /usr/bin/shasum
USER root
RUN npm -g config set user root
RUN npm i -g esy@latest
WORKDIR /app
ADD . /app
RUN jq '. | .buildDirs.executable.flags |= . + ["-ccopt", "-static"]' package.json > package.json.new && mv package.json.new package.json
RUN npx esy i
RUN npx esy pesy
RUN npx esy b
RUN npx esy test

59
README.md

@ -1,35 +1,45 @@ @@ -1,35 +1,45 @@
# nsw
<h1 align="center">
Node Switcher (<code>nsw</code>) <a href="https://dev.azure.com/galstar0385/nsw/_build/latest?definitionId=1?branchName=master"><img alt="Build Status" src="https://dev.azure.com/galstar0385/nsw/_apis/build/status/Schniz.nsw?branchName=master" /></a>
</h1>
> A simple and fast `nvm` replacement, built in native ReasonML.
[![CircleCI](https://circleci.com/gh/yourgithubhandle/nsw/tree/master.svg?style=svg)](https://circleci.com/gh/yourgithubhandle/nsw/tree/master)
:rocket: Single executable :rocket: Blazing fast :rocket:
![Blazing fast](./docs/nvm_comparison.png)
**Contains the following libraries and executables:**
## Features
- Single file, easy installation :sparkles:
- Fast fast fast fast :rocket:
- Install multiple node versions without a hassle! :clap:
- [Project-specific `.nvmrc` file support](./features_tests/nvmrc)
```
nsw@0.0.0
├─test/
│ name: TestNsw.exe
│ main: TestNsw
│ require: nsw.lib
├─library/
│ library name: nsw.lib
│ namespace: Nsw
│ require:
└─executable/
name: NswApp.exe
main: NswApp
require: nsw.lib
## Installation
* Download the [latest release binary](https://github.com/Schniz/nsw/releases) for your system
* Make it available globally on `$PATH`
* Add the following line to your `.bashrc`/`.zshrc` file:
```bash
eval `nsw env`
```
## TODO
- [ ] Feature: make versions complete the latest: `10` would infer the latest minor and patch versions of node 10. `10.1` would infer the latest patch version of node 10.1
- [ ] Feature: `nsw use --install`, `nsw use --quiet`
- [ ] Feature: `nsw install lts`?
- [ ] Feature: `nsw alias`?
- [ ] Feature: Consider nvm-like per-shell usage with symlinks on `/tmp` directory
- [ ] OSX: Add to homebrew?
- [ ] Windows Support?
- [ ] Linux: Replace `curl` usage with `cohttp`/`ocurl` or something else which is statically-linkable
- [ ] Linux: Replace `tar` with a statically linked library too (for ungzip + untar)
## Developing:
```
npm install -g esy
git clone <this-repo>
git clone https://github.com/Schniz/nsw.git
esy install
esy build
```
@ -39,12 +49,15 @@ esy build @@ -39,12 +49,15 @@ esy build
After building the project, you can run the main binary that is produced.
```
esy x NswApp.exe
esy x nsw.exe
```
## Running Tests:
```
# Runs the "test" command in `package.json`.
# Runs some smoke-unity test
esy test
# Runs the feature tests
feature_tests/run.sh
```

41
azure-pipelines.yml

@ -17,8 +17,16 @@ jobs: @@ -17,8 +17,16 @@ jobs:
steps:
# TODO: Uncomment both this and 'publish-build-cache' below to enable build caching for Linux.
# - template: .ci/restore-build-cache.yml
- template: .ci/esy-build-steps.yml
- script: .ci/create-static-binary.sh
- script: ./feature_tests/run.sh $(pwd)/nsw-linux
# - script: bash .ci/prepare-static-build.sh
# - template: .ci/esy-build-steps.yml
# - template: .ci/publish-build-cache.yml
- task: PublishBuildArtifacts@1
displayName: 'Save artifact'
inputs:
PathtoPublish: 'nsw-linux'
ArtifactName: nsw-linux
- job: MacOS
timeoutInMinutes: 0
@ -33,21 +41,28 @@ jobs: @@ -33,21 +41,28 @@ jobs:
# TODO: Uncomment both this and 'publish-build-cache' below to enable build caching for Mac.
# - template: .ci/restore-build-cache.yml
- template: .ci/esy-build-steps.yml
- script: cp _build/default/executable/NswApp.exe _build/nsw
- script: ./feature_tests/run.sh $(pwd)/_build/nsw
# - template: .ci/publish-build-cache.yml
- task: PublishBuildArtifacts@1
displayName: 'Save artifact'
inputs:
PathtoPublish: '_build/nsw'
ArtifactName: nsw-macos
- job: Windows
timeoutInMinutes: 0
pool:
vmImage: 'vs2017-win2016'
# - job: Windows
# timeoutInMinutes: 0
# pool:
# vmImage: 'vs2017-win2016'
variables:
ESY__CACHE_INSTALL_PATH: C:\Users\VssAdministrator\.esy\3_\i
ESY__CACHE_SOURCE_TARBALL_PATH: C:\Users\VssAdministrator\.esy\source\i
# variables:
# ESY__CACHE_INSTALL_PATH: C:\Users\VssAdministrator\.esy\3_\i
# ESY__CACHE_SOURCE_TARBALL_PATH: C:\Users\VssAdministrator\.esy\source\i
steps:
- template: .ci/restore-build-cache.yml
- template: .ci/esy-build-steps.yml
- template: .ci/publish-build-cache.yml
# steps:
# - template: .ci/restore-build-cache.yml
# - template: .ci/esy-build-steps.yml
# - template: .ci/publish-build-cache.yml
- job: Release
timeoutInMinutes: 0
@ -55,7 +70,7 @@ jobs: @@ -55,7 +70,7 @@ jobs:
dependsOn:
- Linux
- MacOS
- Windows
# - Windows
condition: succeeded()
pool:
vmImage: ubuntu-16.04

BIN
docs/nvm_comparison.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

262
esy.lock/index.json

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
{
"checksum": "fc3353f4500533a4dfc452ed120be173",
"checksum": "bc7707f147bdaf3b3f87430bb4b3f0cd",
"root": "nsw@link:./package.json",
"node": {
"refmterr@3.1.10@d41d8cd9": {
@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/re@opam:1.7.3@83095efd",
"@opam/dune@opam:1.6.3@a7d7baed", "@esy-ocaml/reason@3.3.7@d41d8cd9"
"@opam/dune@opam:1.6.3@a7d7baed", "@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": []
},
@ -55,46 +55,87 @@ @@ -55,46 +55,87 @@
"overrides": [],
"dependencies": [
"refmterr@3.1.10@d41d8cd9", "pesy@0.4.1@d41d8cd9",
"ocaml@4.6.9@d41d8cd9", "@reason-native/pastel@0.0.0@d41d8cd9",
"@reason-native/console@0.0.1@d41d8cd9",
"ocaml@4.6.9@d41d8cd9", "@reason-native/rely@1.0.1@d41d8cd9",
"@reason-native/pastel@0.0.1@d41d8cd9",
"@reason-native/console@0.0.2@d41d8cd9",
"@opam/semver@opam:0.1.0@595ed2e0",
"@opam/ppx_let@opam:v0.11.0@15f51b1c",
"@opam/lwt_ppx@opam:1.2.1@db1172a7", "@opam/lwt@opam:4.1.0@111fc2bf",
"@opam/lambdasoup@opam:0.6.3@b8ef0a81",
"@opam/dune@opam:1.6.3@a7d7baed", "@opam/core@opam:v0.11.3@ac79d7b5",
"@esy-ocaml/reason@3.3.7@d41d8cd9"
"@opam/cmdliner@opam:1.0.3@96d31520",
"@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": [ "@opam/merlin@opam:3.2.2@829ee6dd" ]
},
"@reason-native/pastel@0.0.0@d41d8cd9": {
"id": "@reason-native/pastel@0.0.0@d41d8cd9",
"name": "@reason-native/pastel",
"version": "0.0.0",
"@reason-native/rely@1.0.1@d41d8cd9": {
"id": "@reason-native/rely@1.0.1@d41d8cd9",
"name": "@reason-native/rely",
"version": "1.0.1",
"source": {
"type": "install",
"source": [
"archive:https://registry.npmjs.org/@reason-native/pastel/-/pastel-0.0.0.tgz#sha1:44f357fea33b894c9fbf651ecb3f2029e04ee013"
"archive:https://registry.npmjs.org/@reason-native/rely/-/rely-1.0.1.tgz#sha1:14afdbf5bada7739dd9a68d4817c53e7d5ddd50c"
]
},
"overrides": [],
"dependencies": [
"refmterr@3.1.10@d41d8cd9", "ocaml@4.6.9@d41d8cd9",
"@opam/dune@opam:1.6.3@a7d7baed", "@esy-ocaml/reason@3.3.7@d41d8cd9"
"@reason-native/pastel@0.0.1@d41d8cd9",
"@reason-native/file-context-printer@0.0.2@d41d8cd9",
"@opam/dune@opam:1.6.3@a7d7baed", "@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": []
},
"@reason-native/console@0.0.1@d41d8cd9": {
"id": "@reason-native/console@0.0.1@d41d8cd9",
"name": "@reason-native/console",
"@reason-native/pastel@0.0.1@d41d8cd9": {
"id": "@reason-native/pastel@0.0.1@d41d8cd9",
"name": "@reason-native/pastel",
"version": "0.0.1",
"source": {
"type": "install",
"source": [
"archive:https://registry.npmjs.org/@reason-native/console/-/console-0.0.1.tgz#sha1:08a6786dd34a1aa326f88a5cf3c9819df3339fb1"
"archive:https://registry.npmjs.org/@reason-native/pastel/-/pastel-0.0.1.tgz#sha1:ff305233ffd915d317cdcebee534d16c0aada198"
]
},
"overrides": [],
"dependencies": [
"refmterr@3.1.10@d41d8cd9", "ocaml@4.6.9@d41d8cd9",
"@opam/dune@opam:1.6.3@a7d7baed", "@esy-ocaml/reason@3.3.7@d41d8cd9"
"ocaml@4.6.9@d41d8cd9", "@opam/dune@opam:1.6.3@a7d7baed",
"@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": []
},
"@reason-native/file-context-printer@0.0.2@d41d8cd9": {
"id": "@reason-native/file-context-printer@0.0.2@d41d8cd9",
"name": "@reason-native/file-context-printer",
"version": "0.0.2",
"source": {
"type": "install",
"source": [
"archive:https://registry.npmjs.org/@reason-native/file-context-printer/-/file-context-printer-0.0.2.tgz#sha1:effbcb6db360cca9ac293a0075bc0d5912bd0ce0"
]
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@reason-native/pastel@0.0.1@d41d8cd9",
"@opam/re@opam:1.7.3@83095efd", "@opam/dune@opam:1.6.3@a7d7baed",
"@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": []
},
"@reason-native/console@0.0.2@d41d8cd9": {
"id": "@reason-native/console@0.0.2@d41d8cd9",
"name": "@reason-native/console",
"version": "0.0.2",
"source": {
"type": "install",
"source": [
"archive:https://registry.npmjs.org/@reason-native/console/-/console-0.0.2.tgz#sha1:25bd391653579a56d53ddf7cc502a237b784163b"
]
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/dune@opam:1.6.3@a7d7baed",
"@esy-ocaml/reason@3.4.0@d41d8cd9"
],
"devDependencies": []
},
@ -156,6 +197,58 @@ @@ -156,6 +197,58 @@
"@opam/base@opam:v0.11.1@0e54024e"
]
},
"@opam/uutf@opam:1.0.1@c4650647": {
"id": "@opam/uutf@opam:1.0.1@c4650647",
"name": "@opam/uutf",
"version": "opam:1.0.1",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/b8/b8535f974027357094c5cdb4bf03a21b#md5:b8535f974027357094c5cdb4bf03a21b",
"archive:http://erratique.ch/software/uutf/releases/uutf-1.0.1.tbz#md5:b8535f974027357094c5cdb4bf03a21b"
],
"opam": {
"name": "uutf",
"version": "1.0.1",
"path": "esy.lock/opam/uutf.1.0.1"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/uchar@opam:0.0.2@c8218eea",
"@opam/topkg@opam:1.0.0@61f4ccf9",
"@opam/ocamlfind@opam:1.8.0@96572762",
"@opam/ocamlbuild@opam:0.12.0@6c616094",
"@opam/cmdliner@opam:1.0.3@96d31520",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/uchar@opam:0.0.2@c8218eea"
]
},
"@opam/uchar@opam:0.0.2@c8218eea": {
"id": "@opam/uchar@opam:0.0.2@c8218eea",
"name": "@opam/uchar",
"version": "opam:0.0.2",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/c9/c9ba2c738d264c420c642f7bb1cf4a36#md5:c9ba2c738d264c420c642f7bb1cf4a36",
"archive:https://github.com/ocaml/uchar/releases/download/v0.0.2/uchar-0.0.2.tbz#md5:c9ba2c738d264c420c642f7bb1cf4a36"
],
"opam": {
"name": "uchar",
"version": "0.0.2",
"path": "esy.lock/opam/uchar.0.0.2"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/ocamlbuild@opam:0.12.0@6c616094",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [ "ocaml@4.6.9@d41d8cd9" ]
},
"@opam/typerep@opam:v0.11.0@625676b6": {
"id": "@opam/typerep@opam:v0.11.0@625676b6",
"name": "@opam/typerep",
@ -182,6 +275,34 @@ @@ -182,6 +275,34 @@
"ocaml@4.6.9@d41d8cd9", "@opam/base@opam:v0.11.1@0e54024e"
]
},
"@opam/topkg@opam:1.0.0@61f4ccf9": {
"id": "@opam/topkg@opam:1.0.0@61f4ccf9",
"name": "@opam/topkg",
"version": "opam:1.0.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/e3/e3d76bda06bf68cb5853caf6627da603#md5:e3d76bda06bf68cb5853caf6627da603",
"archive:http://erratique.ch/software/topkg/releases/topkg-1.0.0.tbz#md5:e3d76bda06bf68cb5853caf6627da603"
],
"opam": {
"name": "topkg",
"version": "1.0.0",
"path": "esy.lock/opam/topkg.1.0.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/result@opam:1.3@bee8bf2e",
"@opam/ocamlfind@opam:1.8.0@96572762",
"@opam/ocamlbuild@opam:0.12.0@6c616094",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/result@opam:1.3@bee8bf2e",
"@opam/ocamlbuild@opam:0.12.0@6c616094"
]
},
"@opam/stdio@opam:v0.11.0@3b11cb88": {
"id": "@opam/stdio@opam:v0.11.0@3b11cb88",
"name": "@opam/stdio",
@ -312,6 +433,30 @@ @@ -312,6 +433,30 @@
"@opam/parsexp@opam:v0.11.0@7febd99d", "@opam/num@opam:1.1@dcdca088"
]
},
"@opam/semver@opam:0.1.0@595ed2e0": {
"id": "@opam/semver@opam:0.1.0@595ed2e0",
"name": "@opam/semver",
"version": "opam:0.1.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/ce/ce6614ba2f91754028b29a12989f9da6#md5:ce6614ba2f91754028b29a12989f9da6",
"archive:https://github.com/rgrinberg/ocaml-semver/archive/v0.1.0.tar.gz#md5:ce6614ba2f91754028b29a12989f9da6"
],
"opam": {
"name": "semver",
"version": "0.1.0",
"path": "esy.lock/opam/semver.0.1.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/ocamlfind@opam:1.8.0@96572762",
"@opam/ocamlbuild@opam:0.12.0@6c616094",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [ "ocaml@4.6.9@d41d8cd9" ]
},
"@opam/result@opam:1.3@bee8bf2e": {
"id": "@opam/result@opam:1.3@bee8bf2e",
"name": "@opam/result",
@ -1517,6 +1662,33 @@ @@ -1517,6 +1662,33 @@
],
"devDependencies": [ "ocaml@4.6.9@d41d8cd9" ]
},
"@opam/markup@opam:0.8.0@e4958f14": {
"id": "@opam/markup@opam:0.8.0@e4958f14",
"name": "@opam/markup",
"version": "opam:0.8.0",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/be/be0e44a8e8a540f633996e0e26109b4d#md5:be0e44a8e8a540f633996e0e26109b4d",
"archive:https://github.com/aantron/markup.ml/archive/0.8.0.tar.gz#md5:be0e44a8e8a540f633996e0e26109b4d"
],
"opam": {
"name": "markup",
"version": "0.8.0",
"path": "esy.lock/opam/markup.0.8.0"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/uutf@opam:1.0.1@c4650647",
"@opam/uchar@opam:0.0.2@c8218eea", "@opam/dune@opam:1.6.3@a7d7baed",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/uutf@opam:1.0.1@c4650647",
"@opam/uchar@opam:0.0.2@c8218eea"
]
},
"@opam/lwt_ppx@opam:1.2.1@db1172a7": {
"id": "@opam/lwt_ppx@opam:1.2.1@db1172a7",
"name": "@opam/lwt_ppx",
@ -1579,6 +1751,32 @@ @@ -1579,6 +1751,32 @@
"ocaml@4.6.9@d41d8cd9", "@opam/result@opam:1.3@bee8bf2e"
]
},
"@opam/lambdasoup@opam:0.6.3@b8ef0a81": {
"id": "@opam/lambdasoup@opam:0.6.3@b8ef0a81",
"name": "@opam/lambdasoup",
"version": "opam:0.6.3",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/89/89f0596aa05a6e7a33bf9d74797905f1#md5:89f0596aa05a6e7a33bf9d74797905f1",
"archive:https://github.com/aantron/lambda-soup/archive/0.6.3.tar.gz#md5:89f0596aa05a6e7a33bf9d74797905f1"
],
"opam": {
"name": "lambdasoup",
"version": "0.6.3",
"path": "esy.lock/opam/lambdasoup.0.6.3"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/markup@opam:0.8.0@e4958f14",
"@opam/jbuilder@opam:transition@58bdfe0a",
"@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [
"ocaml@4.6.9@d41d8cd9", "@opam/markup@opam:0.8.0@e4958f14"
]
},
"@opam/jbuilder@opam:transition@58bdfe0a": {
"id": "@opam/jbuilder@opam:transition@58bdfe0a",
"name": "@opam/jbuilder",
@ -1903,6 +2101,28 @@ @@ -1903,6 +2101,28 @@
"dependencies": [ "@esy-ocaml/substs@0.0.1@d41d8cd9" ],
"devDependencies": []
},
"@opam/cmdliner@opam:1.0.3@96d31520": {
"id": "@opam/cmdliner@opam:1.0.3@96d31520",
"name": "@opam/cmdliner",
"version": "opam:1.0.3",
"source": {
"type": "install",
"source": [
"archive:https://opam.ocaml.org/cache/md5/36/3674ad01d4445424105d33818c78fba8#md5:3674ad01d4445424105d33818c78fba8",
"archive:http://erratique.ch/software/cmdliner/releases/cmdliner-1.0.3.tbz#md5:3674ad01d4445424105d33818c78fba8"
],
"opam": {
"name": "cmdliner",
"version": "1.0.3",
"path": "esy.lock/opam/cmdliner.1.0.3"
}
},
"overrides": [],
"dependencies": [
"ocaml@4.6.9@d41d8cd9", "@esy-ocaml/substs@0.0.1@d41d8cd9"
],
"devDependencies": [ "ocaml@4.6.9@d41d8cd9" ]
},
"@opam/biniou@opam:1.2.0@c8516f18": {
"id": "@opam/biniou@opam:1.2.0@c8516f18",
"name": "@opam/biniou",
@ -2049,14 +2269,14 @@ @@ -2049,14 +2269,14 @@
"dependencies": [],
"devDependencies": []
},
"@esy-ocaml/reason@3.3.7@d41d8cd9": {
"id": "@esy-ocaml/reason@3.3.7@d41d8cd9",
"@esy-ocaml/reason@3.4.0@d41d8cd9": {
"id": "@esy-ocaml/reason@3.4.0@d41d8cd9",
"name": "@esy-ocaml/reason",
"version": "3.3.7",
"version": "3.4.0",
"source": {
"type": "install",
"source": [
"archive:https://registry.npmjs.org/@esy-ocaml/reason/-/reason-3.3.7.tgz#sha1:4d75b8876807c4178c6fff2359962066bb69d944"
"archive:https://registry.npmjs.org/@esy-ocaml/reason/-/reason-3.4.0.tgz#sha1:8c84c183a95d489a3e82ff0465effe4b56ff12af"
]
},
"overrides": [],

36
esy.lock/opam/cmdliner.1.0.3/opam

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
opam-version: "2.0"
maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>"
authors: ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"]
homepage: "http://erratique.ch/software/cmdliner"
doc: "http://erratique.ch/software/cmdliner/doc/Cmdliner"
dev-repo: "git+http://erratique.ch/repos/cmdliner.git"
bug-reports: "https://github.com/dbuenzli/cmdliner/issues"
tags: [ "cli" "system" "declarative" "org:erratique" ]
license: "ISC"
depends:[ "ocaml" {>= "4.03.0"} ]
build: [[ make "all" "PREFIX=%{prefix}%" ]]
install:
[[make "install" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%" ]
[make "install-doc" "LIBDIR=%{_:lib}%" "DOCDIR=%{_:doc}%" ]]
synopsis: """Declarative definition of command line interfaces for OCaml"""
description: """\
Cmdliner allows the declarative definition of command line interfaces
for OCaml.
It provides a simple and compositional mechanism to convert command
line arguments to OCaml values and pass them to your functions. The
module automatically handles syntax errors, help messages and UNIX man
page generation. It supports programs with single or multiple commands
and respects most of the [POSIX][1] and [GNU][2] conventions.
Cmdliner has no dependencies and is distributed under the ISC license.
[1]: http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html
[2]: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
"""
url {
archive: "http://erratique.ch/software/cmdliner/releases/cmdliner-1.0.3.tbz"
checksum: "3674ad01d4445424105d33818c78fba8"
}

35
esy.lock/opam/lambdasoup.0.6.3/opam

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
opam-version: "2.0"
version: "0.6.3"
homepage: "https://github.com/aantron/lambda-soup"
doc: "http://aantron.github.io/lambda-soup"
bug-reports: "https://github.com/aantron/lambda-soup/issues"
license: "BSD"
authors: "Anton Bachin <antonbachin@yahoo.com>"
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
dev-repo: "git+https://github.com/aantron/lambda-soup.git"
depends: [
"ocaml"
"jbuilder" {build & >= "1.0+beta10"}
"markup" {>= "0.7.1"}
"ounit" {with-test}
]
build: [
["jbuilder" "build" "-p" name "-j" jobs]
]
synopsis: "Easy functional HTML scraping and manipulation with CSS selectors"
description: """
Lambda Soup is an HTML scraping library inspired by Python's Beautiful Soup. It
provides lazy traversals from HTML nodes to their parents, children, siblings,
etc., and to nodes matching CSS selectors. The traversals can be manipulated
using standard functional combinators such as fold, filter, and map.
The DOM tree is mutable. You can use Lambda Soup for automatic HTML rewriting in
scripts. Lambda Soup rewrites its own ocamldoc page this way.
A major goal of Lambda Soup is to be easy to use, including in interactive
sessions, and to have a minimal learning curve. It is a very simple library."""
url {
src: "https://github.com/aantron/lambda-soup/archive/0.6.3.tar.gz"
checksum: "md5=89f0596aa05a6e7a33bf9d74797905f1"
}

52
esy.lock/opam/markup.0.8.0/opam

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
opam-version: "2.0"
version: "0.8.0"
maintainer: "Anton Bachin <antonbachin@yahoo.com>"
authors: "Anton Bachin <antonbachin@yahoo.com>"
homepage: "https://github.com/aantron/markup.ml"
doc: "http://aantron.github.io/markup.ml"
bug-reports: "https://github.com/aantron/markup.ml/issues"
dev-repo: "git+https://github.com/aantron/markup.ml.git"
license: "BSD"
depends: [
"ocaml"
"dune" {build}
"ounit" {with-test}
"uchar"
"uutf" {>= "1.0.0"}
]
# Markup.ml implicitly requires OCaml 4.02.3, as this is a contraint of Dune.
# Without that, Markup.ml implicitly requires OCaml 4.01.0, as this is a
# constraint of Uutf. Without *that*, Markup.ml works on OCaml 3.11 or searlier.
build: [
["dune" "build" "-p" name "-j" jobs]
]
synopsis: "Error-recovering functional HTML5 and XML parsers and writers"
description: """
Markup.ml provides an HTML parser and an XML parser. The parsers are wrapped in
a simple interface: they are functions that transform byte streams to parsing
signal streams. Streams can be manipulated in various ways, such as processing
by fold, filter, and map, assembly into DOM tree structures, or serialization
back to HTML or XML.
Both parsers are based on their respective standards. The HTML parser, in
particular, is based on the state machines defined in HTML5.
The parsers are error-recovering by default, and accept fragments. This makes it
very easy to get a best-effort parse of some input. The parsers can, however, be
easily configured to be strict, and to accept only full documents.
Apart from this, the parsers are streaming (do not build up a document in
memory), non-blocking (can be used with threading libraries), lazy (do not
consume input unless the signal stream is being read), and process the input in
a single pass. They automatically detect the character encoding of the input
stream, and convert everything to UTF-8."""
url {
src: "https://github.com/aantron/markup.ml/archive/0.8.0.tar.gz"
checksum: "md5=be0e44a8e8a540f633996e0e26109b4d"
}

35
esy.lock/opam/semver.0.1.0/opam

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
opam-version: "2.0"
maintainer: "rudi.grinberg@gmail.com"
authors: [
"Tikhon Jelvis"
"Rudi Grinberg"
]
homepage: "https://github.com/rgrinberg/ocaml-semver"
bug-reports: "https://github.com/rgrinberg/ocaml-semver/issues"
license: "BSD3"
dev-repo: "git+https://github.com/rgrinberg/ocaml-semver.git"
build: [
["ocaml" "setup.ml" "-configure"]
["ocaml" "setup.ml" "-build"]
["ocaml" "setup.ml" "-configure" "--enable-tests"] {with-test}
["ocaml" "setup.ml" "-build"] {with-test}
["ocaml" "setup.ml" "-test"] {with-test}
["ocaml" "setup.ml" "-doc"] {with-doc}
]
install: ["ocaml" "setup.ml" "-install"]
remove: ["ocamlfind" "remove" "semver"]
depends: [
"ocaml" {>= "4.02.0"}
"ocamlfind" {build}
"ounit" {with-test}
"ocamlbuild" {build}
]
synopsis: "Semantic versioning module"
description: """
Provides a single module `Semver` that can parse, compare, and manipulate
software versions of the form x.x.x. See http://semver.org/"""
flags: light-uninstall
url {
src: "https://github.com/rgrinberg/ocaml-semver/archive/v0.1.0.tar.gz"
checksum: "md5=ce6614ba2f91754028b29a12989f9da6"
}

49
esy.lock/opam/topkg.1.0.0/opam

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
opam-version: "2.0"
maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>"
authors: ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"]
homepage: "http://erratique.ch/software/topkg"
doc: "http://erratique.ch/software/topkg/doc"
license: "ISC"
dev-repo: "git+http://erratique.ch/repos/topkg.git"
bug-reports: "https://github.com/dbuenzli/topkg/issues"
tags: ["packaging" "ocamlbuild" "org:erratique"]
depends: [
"ocaml" {>= "4.01.0"}
"ocamlfind" {build & >= "1.6.1"}
"ocamlbuild"
"result" ]
build: [[
"ocaml" "pkg/pkg.ml" "build"
"--pkg-name" name
"--dev-pkg" "%{pinned}%" ]]
synopsis: """The transitory OCaml software packager"""
description: """\
Topkg is a packager for distributing OCaml software. It provides an
API to describe the files a package installs in a given build
configuration and to specify information about the package's
distribution, creation and publication procedures.
The optional topkg-care package provides the `topkg` command line tool
which helps with various aspects of a package's life cycle: creating
and linting a distribution, releasing it on the WWW, publish its
documentation, add it to the OCaml opam repository, etc.
Topkg is distributed under the ISC license and has **no**
dependencies. This is what your packages will need as a *build*
dependency.
Topkg-care is distributed under the ISC license it depends on
[fmt][fmt], [logs][logs], [bos][bos], [cmdliner][cmdliner],
[webbrowser][webbrowser] and `opam-format`.
[fmt]: http://erratique.ch/software/fmt
[logs]: http://erratique.ch/software/logs
[bos]: http://erratique.ch/software/bos
[cmdliner]: http://erratique.ch/software/cmdliner
[webbrowser]: http://erratique.ch/software/webbrowser
"""
url {
src: "http://erratique.ch/software/topkg/releases/topkg-1.0.0.tbz"
checksum: "md5=e3d76bda06bf68cb5853caf6627da603"
}

36
esy.lock/opam/uchar.0.0.2/opam

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
opam-version: "2.0"
maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>"
authors: ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"]
homepage: "http://ocaml.org"
doc: "https://ocaml.github.io/uchar/"
dev-repo: "git+https://github.com/ocaml/uchar.git"
bug-reports: "https://github.com/ocaml/uchar/issues"
tags: [ "text" "character" "unicode" "compatibility" "org:ocaml.org" ]
license: "typeof OCaml system"
depends: [
"ocaml" {>= "3.12.0"}
"ocamlbuild" {build}
]
build: [
["ocaml" "pkg/git.ml"]
[
"ocaml"
"pkg/build.ml"
"native=%{ocaml:native}%"
"native-dynlink=%{ocaml:native-dynlink}%"
]
]
synopsis: "Compatibility library for OCaml's Uchar module"
description: """
The `uchar` package provides a compatibility library for the
[`Uchar`][1] module introduced in OCaml 4.03.
The `uchar` package is distributed under the license of the OCaml
compiler. See [LICENSE](LICENSE) for details.
[1]: http://caml.inria.fr/pub/docs/manual-ocaml/libref/Uchar.html"""
url {
src:
"https://github.com/ocaml/uchar/releases/download/v0.0.2/uchar-0.0.2.tbz"
checksum: "md5=c9ba2c738d264c420c642f7bb1cf4a36"
}

38
esy.lock/opam/uutf.1.0.1/opam

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
opam-version: "2.0"
maintainer: "Daniel Bünzli <daniel.buenzl i@erratique.ch>"
authors: ["Daniel Bünzli <daniel.buenzl i@erratique.ch>"]
homepage: "http://erratique.ch/software/uutf"
doc: "http://erratique.ch/software/uutf/doc/Uutf"
dev-repo: "git+http://erratique.ch/repos/uutf.git"
bug-reports: "https://github.com/dbuenzli/uutf/issues"
tags: [ "unicode" "text" "utf-8" "utf-16" "codec" "org:erratique" ]
license: "ISC"
depends: [
"ocaml" {>= "4.01.0"}
"ocamlfind" {build}
"ocamlbuild" {build}
"topkg" {build}
"uchar"
]
depopts: ["cmdliner"]
conflicts: ["cmdliner" { < "0.9.6"} ]
build: [[
"ocaml" "pkg/pkg.ml" "build"
"--pinned" "%{pinned}%"
"--with-cmdliner" "%{cmdliner:installed}%" ]]
synopsis: "Non-blocking streaming Unicode codec for OCaml"
description: """
Uutf is a non-blocking streaming codec to decode and encode the UTF-8,
UTF-16, UTF-16LE and UTF-16BE encoding schemes. It can efficiently
work character by character without blocking on IO. Decoders perform
character position tracking and support newline normalization.
Functions are also provided to fold over the characters of UTF encoded
OCaml string values and to directly encode characters in OCaml
Buffer.t values.
Uutf has no dependency and is distributed under the ISC license."""
url {
src: "http://erratique.ch/software/uutf/releases/uutf-1.0.1.tbz"
checksum: "md5=b8535f974027357094c5cdb4bf03a21b"
}

9
executable/Env.re

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
open Nsw;
let run = () => {
Console.log(
Printf.sprintf("export PATH=%s/bin:$PATH", Directories.currentVersion),
);
Lwt.return();
};

101
executable/Install.re

@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
open Nsw;
let mkDownloadsDir = () => {
let exists = Lwt_unix.file_exists(Directories.downloads);
if%lwt (exists |> Lwt.map(x => !x)) {
Console.log(
<Pastel>
"Creating "
<Pastel color=Pastel.Cyan> Directories.downloads </Pastel>
" for the first time"
</Pastel>,
);
let%lwt _ = System.mkdirp(Directories.downloads);
Lwt.return();
} else {
Lwt.return();
};
};
let main = (~version as versionName) => {
let%lwt os = System.NodeOS.get()
and arch = System.NodeArch.get()
and versionName =
switch (versionName) {
| Some(versionName) => Lwt.return(versionName)
| None => Nvmrc.getVersion()
};
let versionName = Versions.format(versionName);
Console.log(
<Pastel>
"Looking for node "
<Pastel color=Pastel.Cyan> versionName </Pastel>
" for "
<Pastel color=Pastel.Cyan>
{System.NodeOS.toString(os)}
" "
{System.NodeArch.toString(arch)}
</Pastel>
</Pastel>,
);
let%lwt filepath =
Versions.getFileToDownload(~version=versionName, ~os, ~arch);
let tarDestination =
Filename.concat(Directories.downloads, versionName ++ ".tar.gz");
Console.log(
<Pastel>
"Downloading "
<Pastel color=Pastel.Cyan> filepath </Pastel>
" to "
<Pastel color=Pastel.Cyan> tarDestination </Pastel>
</Pastel>,
);
let%lwt _ = System.mkdirp(Filename.dirname(tarDestination));
let%lwt _ = Http.download(filepath, ~into=tarDestination);
let extractionDestination =
Filename.concat(Directories.nodeVersions, versionName);
Console.log(
<Pastel>
"Extracting "
<Pastel color=Pastel.Cyan> tarDestination </Pastel>
" to "
<Pastel color=Pastel.Cyan> extractionDestination </Pastel>
</Pastel>,
);
let%lwt _ =
Compression.extractFile(tarDestination, ~into=extractionDestination);
Lwt.return();
};
let run = (~version) =>
try%lwt (main(~version)) {
| Versions.No_Download_For_System(os, arch) =>
Console.log(
<Pastel>
"Version exists, but can't find a file for your system:\n"
" OS: "
<Pastel color=Pastel.Cyan> {System.NodeOS.toString(os)} </Pastel>
"\n"
" Architecture: "
<Pastel color=Pastel.Cyan> {System.NodeArch.toString(arch)} </Pastel>
</Pastel>,
)
|> Lwt.return
| Versions.Version_not_found(version) =>
Console.log(
<Pastel>
"Version "
<Pastel color=Pastel.Cyan> version </Pastel>
" not found!"
</Pastel>,
)
|> Lwt.return
};

137
executable/ListInstallations.re

@ -1,122 +1,37 @@ @@ -1,122 +1,37 @@
module Path = {
let rec join = xs =>
switch (xs) {
| [x] => x
| [x, ...xs] => Filename.concat(x, join(xs))
| [] => ""
};
};
module Fs = {
open Core;
let readdir = dir =>
switch (Sys.readdir(dir)) {
| x => Ok(x)
| exception (Sys_error(error)) => Error(error)
};
let realpath = Filename.realpath;
};
module Result = {
let return = x => Ok(x);
let both = (a, b) =>
switch (a, b) {
| (Error(_) as e, _)
| (_, Error(_) as e) => e
| (Ok(ax), Ok(bx)) => Ok((ax, bx))
};
let map = (fn, res) =>
switch (res) {
| Ok(x) => Ok(fn(x))
| Error(_) as e => e
};
let bind = (fn, res) =>
switch (res) {
| Ok(x) => fn(x)
| Error(_) as e => e
};
let fold = (error, ok, res) =>
switch (res) {
| Ok(x) => ok(x)
| Error(x) => error(x)
};
module Let_syntax = {
let map = (x, ~f) => map(f, x);
let bind = (x, ~f) => bind(f, x);
};
};
module Opt = {
let orThrow = (message, opt) =>
switch (opt) {
| None => failwith(message)
| Some(x) => x
};
let fold = (none, some, opt) =>
switch (opt) {
| None => none()
| Some(x) => some(x)
};
let toResult = (error, opt) =>
switch (opt) {
| None => Error(error)
| Some(x) => Ok(x)
};
};
module Directories = {
open Core;
let home =
Sys.getenv("HOME")
|> Opt.orThrow("There isn't $HOME environment variable set.");
let sfwRoot = Path.join([home, ".nsw"]);
let nodeVersions = Path.join([sfwRoot, "node-versions"]);
let currentVersion = Path.join([sfwRoot, "current"]);
};
let currentVersion = () =>
switch (Fs.realpath(Directories.currentVersion)) {
| x => Some(x)
| exception (Unix.Unix_error(_, _, _)) => None
};
let printableVersions = (~current, ~versions) => {
open Pastel;
open Nsw;
let colorizeVersions = (~current, ~versions) => {
let strings =
versions
|> List.map(version => {
let fullPath = Path.join([Directories.nodeVersions, version]);
let str = "- " ++ version;
fullPath == current ? <Pastel color=Green> str </Pastel> : str;
open Versions.Local;
let str = "- " ++ version.name;
let color =
current
|> Opt.bind(current =>
current.name == version.name ? Some(Pastel.Green) : None
);
<Pastel ?color> str </Pastel>;
});
<Pastel> ...strings </Pastel>;
<Pastel>
<Pastel color=Pastel.Cyan> "## List of installed versions:\n" </Pastel>
<Pastel> ...strings </Pastel>
</Pastel>;
};
let run = () => {
open Result;
let%bind current =
currentVersion()
|> Opt.toResult("No version selected")
|> Result.fold(x => x, x => x)
|> Result.return;
let%bind x = Fs.readdir(Directories.nodeVersions);
let getVersionsString = () =>
Result.(
{
let%bind versions =
Fs.readdir(Directories.nodeVersions) |> Result.map(Array.to_list);
Versions.getInstalledVersions() |> Result.map(Array.to_list);
let current = Versions.getCurrentVersion();
Console.log(
<Pastel color=Pastel.Cyan> "## List of installed versions:" </Pastel>,
colorizeVersions(~current, ~versions) |> Result.return;
}
);
printableVersions(~current, ~versions) |> Console.log;
Result.return();
};
let run = () => getVersionsString() |> Result.map(Console.log) |> Lwt.return;

24
executable/ListLocal.re

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
open Nsw;
let run = () =>
Versions.Local.(
{
let%lwt versions = Versions.getInstalledVersions() |> Result.toLwt;
let currentVersion = Versions.getCurrentVersion();
Console.log("The following versions are installed:");
versions
|> Array.iter(version => {
let color =
switch (currentVersion) {
| None => None
| Some(x) when x.name == version.name => Some(Pastel.Cyan)
| Some(_) => None
};
Console.log(<Pastel ?color> "* " {version.name} </Pastel>);
});
Lwt.return();
}
);

26
executable/ListRemote.re

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
open Nsw;
let run = () => {
Console.log("Looking for some node versions upstream...");
let%lwt versions = Versions.getRemoteVersions();
let currentVersion = Versions.getCurrentVersion();
versions
|> List.iter(version => {
open Versions.Remote;
let str = "* " ++ version.name;
let color =
switch (currentVersion, version.installed) {
| (Some({name: currentVersionName, _}), _)
when currentVersionName == version.name =>
Some(Pastel.Cyan)
| (_, true) => Some(Pastel.Green)
| (_, false) => None
};
Console.log(<Pastel ?color> str </Pastel>);
});
Lwt.return();
};

100
executable/NswApp.re

@ -1 +1,99 @@ @@ -1 +1,99 @@
ListInstallations.run();
let version = "1.0.0";
module Commands = {
let use = version => Lwt_main.run(Use.run(version));
let listRemote = () => Lwt_main.run(ListRemote.run());
let listLocal = () => Lwt_main.run(ListLocal.run());
let install = version => Lwt_main.run(Install.run(~version));
let env = () => Lwt_main.run(Env.run());
};
open Cmdliner;
let help_secs = [
`S(Manpage.s_common_options),
`P("These options are common to all commands."),
`S("MORE HELP"),
`P("Use `$(mname) $(i,COMMAND) --help' for help on a single command."),
`Noblank,
`S(Manpage.s_bugs),
`P("File bug reports at https://github.com/Schniz/nsw"),
];
let install = {
let doc = "Install another node version";
let man = [];
let selectedVersion = {
let doc = "Install another version specified in $(docv).";
Arg.(
value & pos(0, some(string), None) & info([], ~docv="VERSION", ~doc)
);
};
(
Term.(const(Commands.install) $ selectedVersion),
Term.info("install", ~version, ~doc, ~exits=Term.default_exits, ~man),
);
};
let listLocal = {
let doc = "List all the installed versions";
let man = [];
(
Term.(app(const(Commands.listLocal), const())),
Term.info("ls", ~version, ~doc, ~exits=Term.default_exits, ~man),
);
};
let listRemote = {
let doc = "List all the versions upstream";
let man = [];
(
Term.(app(const(Commands.listRemote), const())),
Term.info("ls-remote", ~version, ~doc, ~exits=Term.default_exits, ~man),
);
};
let use = {
let doc = "Switch to another installed node version";
let man = [];
let selectedVersion = {
let doc = "Switch to version $(docv).\nLeave empty to look for value from `.nvmrc`";
Arg.(
value & pos(0, some(string), None) & info([], ~docv="VERSION", ~doc)
);
};
(
Term.(const(Commands.use) $ selectedVersion),
Term.info("use", ~version, ~doc, ~exits=Term.default_exits, ~man),
);
};
let env = {
let doc = "Show env configurations";
let sdocs = Manpage.s_common_options;
let man = help_secs;
(
Term.(const(Commands.env) $ const()),
Term.info("env", ~version, ~doc, ~exits=Term.default_exits, ~man, ~sdocs),
);
};
let defaultCmd = {
let doc = "Manage Node.js installations";
let sdocs = Manpage.s_common_options;
let man = help_secs;
(
Term.(ret(const(_ => `Help((`Pager, None))) $ const())),
Term.info("nsw", ~version, ~doc, ~exits=Term.default_exits, ~man, ~sdocs),
);
};
let _ =
Term.eval_choice(defaultCmd, [install, use, listLocal, listRemote, env])
|> Term.exit;

63
executable/Use.re

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
open Nsw;
let lwtIgnore = lwt => Lwt.catch(() => lwt, _ => Lwt.return());
exception Version_Not_Installed(string);
let switchVersion = version => {
let versionDir = Filename.concat(Directories.nodeVersions, version);
let%lwt _ =
if%lwt (Lwt_unix.file_exists(versionDir) |> Lwt.map(x => !x)) {
Lwt.fail(Version_Not_Installed(version));
};
let destination = Filename.concat(versionDir, "installation");
let source = Directories.currentVersion;
Console.log(
<Pastel>
"Linking "
<Pastel color=Pastel.Cyan> source </Pastel>
" to "
<Pastel color=Pastel.Cyan> destination </Pastel>
</Pastel>,
);
let%lwt _ = Lwt_unix.unlink(Directories.currentVersion) |> lwtIgnore;
let%lwt _ = Lwt_unix.symlink(destination, Directories.currentVersion);
Console.log(
<Pastel> "Using " <Pastel color=Pastel.Cyan> version </Pastel> </Pastel>,
);
Lwt.return();
};
let main = (~version as providedVersion) => {
let%lwt version =
switch (providedVersion) {
| Some(version) => Lwt.return(version)
| None => Nvmrc.getVersion()
};
switchVersion(Versions.format(version));
};
let run = version =>
try%lwt (main(~version)) {
| Version_Not_Installed(version) =>
Console.log(
<Pastel color=Pastel.Red>
"The following version is not installed: "
version
</Pastel>,
)
|> Lwt.return
| Nvmrc.Version_Not_Provided =>
Console.log(
<Pastel color=Pastel.Red>
"No .nvmrc was found in the current directory. Please provide a version number."
</Pastel>,
)
|> Lwt.return
};

6
executable/dune

@ -5,8 +5,8 @@ @@ -5,8 +5,8 @@
(executable
; The entrypoint module
(name NswApp) ; From package.json main field
; The name of the executable (runnable via esy x NswApp.exe)
(public_name NswApp.exe) ; From package.json name field
(libraries core lwt lwt.unix console.lib pastel.lib nsw.lib ) ; From package.json require field (array of strings)
; The name of the executable (runnable via esy x nsw.exe)
(public_name nsw.exe) ; From package.json name field
(libraries core cmdliner lwt lwt.unix lambdasoup console.lib pastel.lib nsw.lib ) ; From package.json require field (array of strings)
(preprocess ( pps lwt_ppx ppx_let )) ; From package.json preprocess field
)

8
feature_tests/basic/run.sh

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
eval $(nsw env)
nsw install v8.11.3
nsw use v8.11.3
if [ "$(node --version)" != "v8.11.3" ]; then
echo "Node version is not v8.11.3!"
exit 1
fi

1
feature_tests/nvmrc/.nvmrc

@ -0,0 +1 @@ @@ -0,0 +1 @@
10.9.0

10
feature_tests/nvmrc/run.sh

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
#!/bin/bash
eval $(nsw env)
nsw install
nsw use
if [ "$(node --version)" != "v10.9.0" ]; then
echo "Node version is not v10.9.0!"
exit 1
fi

32
feature_tests/run.sh

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
#!/bin/bash
set -e
DIRECTORY=`dirname $0`
BINARY=$1
TEMP_DIR_BASE=$(pwd)/$DIRECTORY/.tmp
TEMP_BINARY_PATH=$TEMP_DIR_BASE/bin
TEMP_NSW_DIR=$TEMP_DIR_BASE/.nsw
if [ "$BINARY" == "" ]; then
echo "No binary supplied!"
exit 1
fi
echo "using nvm=$BINARY"
rm -rf $TEMP_DIR_BASE
mkdir $TEMP_DIR_BASE $TEMP_BINARY_PATH
cp $BINARY $TEMP_BINARY_PATH/nsw
for test_file in $DIRECTORY/*/run.sh; do
rm -rf $TEMP_NSW_DIR
echo "Running test in $test_file"
echo "Running test in $test_file" | sed "s/./-/g"
(cd $(dirname $test_file) && NSW_DIR=$TEMP_NSW_DIR PATH=$TEMP_BINARY_PATH:$PATH bash $(basename $test_file))
echo ""
echo " -> Finished!"
rm -rf $TEMP_NSW_DIR
done

15
library/Compression.re

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
let extractFile = (~into as destination, filepath) => {
let%lwt _ = System.mkdirp(destination);
let%lwt _ =
System.unix_exec(
"tar",
~args=[|"-xvf", filepath, "--directory", destination|],
~stderr=`Dev_null,
);
let%lwt files = Fs.readdir(destination) |> Result.toLwt;
let filename = files[0];
Lwt_unix.rename(
Filename.concat(destination, filename),
Filename.concat(destination, "installation"),
);
};

13
library/Directories.re

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
let sfwRoot =
Opt.(
Sys.getenv_opt("NSW_DIR")
or {
let home =
Sys.getenv_opt("HOME")
|> Opt.orThrow("There isn't $HOME environment variable set.");
Filename.concat(home, ".nsw");
}
);
let nodeVersions = Filename.concat(sfwRoot, "node-versions");
let currentVersion = Filename.concat(sfwRoot, "current");
let downloads = Filename.concat(sfwRoot, "downloads");

19
library/Fs.re

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
open Core;
let readdir = dir =>
switch (Sys.readdir(dir)) {
| x => Ok(x)
| exception (Sys_error(error)) => Error(error)
};
let writeFile = (path, contents) => {
let%lwt x = Lwt_unix.openfile(path, [Unix.O_RDWR, Unix.O_CREAT], 777);
let%lwt _ =
Lwt.finalize(
() => Lwt_unix.write_string(x, contents, 0, String.length(contents)),
() => Lwt_unix.close(x),
);
Lwt.return();
};
let realpath = Filename.realpath;

53
library/Http.re

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
type response = {
body: string,
status: int,
};
let body = response => response.body;
let status = response => response.status;
let rec getBody = listOfStrings => {
switch (listOfStrings) {
| [] => ""
| ["", ...rest] => String.concat("\n", rest)
| [_, ...xs] => getBody(xs)
};
};
let rec getStatus = string => {
List.nth(String.split_on_char(' ', string), 1);
};
exception Unknown_status_code(response);
exception Not_found(response);
exception Internal_server_error(response);
let verifyStatus = response => {
switch (response.status) {
| 200 => Lwt.return(response)
| x when x / 100 == 4 => Lwt.fail(Not_found(response))
| x when x / 100 == 5 => Lwt.fail(Internal_server_error(response))
| x => Lwt.fail(Unknown_status_code(response))
};
};
let parseResponse = lines => {
let body = getBody(lines);
let status = getStatus(lines |> List.hd) |> int_of_string;
{body, status};
};
let makeRequest = url => {
let%lwt response =
System.unix_exec("curl", ~args=[|url, "-D", "-", "--silent"|]);
response |> parseResponse |> verifyStatus;
};
let download = (url, ~into) => {
let%lwt response =
System.unix_exec(
"curl",
~args=[|url, "-D", "-", "--silent", "-o", into|],
);
response |> parseResponse |> verifyStatus;
};

14
library/Nvmrc.re

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
exception Version_Not_Provided;
let getVersion = () => {
let%lwt cwd = Lwt_unix.getcwd();
let nvmrcFile = Filename.concat(cwd, ".nvmrc");
try%lwt (
Lwt_io.lines_of_file(nvmrcFile)
|> Lwt_stream.to_list
|> Lwt.map(List.hd)
|> Lwt.map(String.trim)
) {
| Unix.Unix_error(Unix.ENOENT, _, _) => Lwt.fail(Version_Not_Provided)
};
};

33
library/Opt.re

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
let orThrow = (message, opt) =>
switch (opt) {
| None => failwith(message)
| Some(x) => x
};
let map = (fn, opt) =>
switch (opt) {
| None => None
| Some(x) => Some(fn(x))
};
let bind = (fn, opt) =>
switch (opt) {
| None => None
| Some(x) => fn(x)
};
let fold = (none, some, opt) =>
switch (opt) {
| None => none()
| Some(x) => some(x)
};
let toResult = (error, opt) =>
switch (opt) {
| None => Error(error)
| Some(x) => Ok(x)
};
let some = x => Some(x);
let (or) = (opt, b) => fold(() => b, x => x, opt);

43
library/Result.re

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
let return = x => Ok(x);
let both = (a, b) =>
switch (a, b) {
| (Error(_) as e, _)
| (_, Error(_) as e) => e
| (Ok(ax), Ok(bx)) => Ok((ax, bx))
};
let mapError = (fn, res) =>
switch (res) {
| Error(x) => Error(fn(x))
| Ok(_) as x => x
};
let map = (fn, res) =>
switch (res) {
| Ok(x) => Ok(fn(x))
| Error(_) as e => e
};
let bind = (fn, res) =>
switch (res) {
| Ok(x) => fn(x)
| Error(_) as e => e
};
let fold = (error, ok, res) =>
switch (res) {
| Ok(x) => ok(x)
| Error(x) => error(x)
};
module Let_syntax = {
let map = (x, ~f) => map(f, x);
let bind = (x, ~f) => bind(f, x);
};
let toLwt = res =>
switch (res) {
| Error(x) => Lwt.fail_with(x)
| Ok(x) => Lwt.return(x)
};

70
library/System.re

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
let unix_exec =
(~args=[||], ~env=?, ~stderr: Lwt_process.redirection=`Keep, command) => {
let realArgs = Array.append([|command|], args);
Lwt_process.pread_lines(~stderr, ~env?, ("", realArgs))
|> Lwt_stream.to_list;
};
let mkdirp = destination =>
unix_exec("mkdir", ~stderr=`Dev_null, ~args=[|"-p", destination|]);
module NodeArch = {
type t =
| X32
| X64
| Other;
let rec last = xs =>
switch (xs) {
| [x] => Some(x)
| [_, ...xs] => last(xs)
| [] => None
};
let findArches = unameResult => {
let words = unameResult |> List.hd |> String.split_on_char(' ');
List.exists(word => word == "x86_64", words) ? X64 : X32;
};
/* Get node-compliant architecture (x64, x86) */
let get = () =>
switch (Sys.os_type) {
| "Unix" =>
let%lwt result = unix_exec("uname", ~args=[|"-a"|]);
try (result |> findArches |> Lwt.return) {
| _ => Lwt.fail_with("Error getting unix information")
};
| _ => Lwt.return(Other)
};
let toString =
fun
| X64 => "x64"
| X32 => "x32"
| Other => "other";
};
module NodeOS = {
type t =
| Darwin
| Linux
| Other(string);
let get = () =>
switch (Sys.os_type) {
| "Unix" =>
let%lwt result = unix_exec("uname", ~args=[|"-s"|]);
switch (result |> List.hd) {
| "Darwin" => Lwt.return(Darwin)
| _ => Lwt.return(Linux)
| exception _ => Lwt.fail_with("Error getting unix information")
};
| other => Other(other) |> Lwt.return
};
let toString =
fun
| Darwin => "darwin"
| Linux => "linux"
| Other(_) => "other";
};

1
library/Util.re

@ -1 +0,0 @@ @@ -1 +0,0 @@
let foo = () => print_endline("Hello");

147
library/Versions.re

@ -0,0 +1,147 @@ @@ -0,0 +1,147 @@
module VersionSet = Set.Make(String);
module Local = {
type t = {
name: string,
fullPath: string,
};
};
exception Version_not_found(string);
module Remote = {
type t = {
name: string,
baseURL: string,
installed: bool,
};
let skip = (~amount, str) =>
Str.last_chars(str, String.length(str) - amount);
let parseSemver = version => version |> skip(~amount=1) |> Semver.of_string;
let compare = (v1, v2) =>
switch (parseSemver(v1), parseSemver(v2)) {
| (Some(v1), Some(v2)) => Semver.compare(v1, v2)
| (None, _)
| (_, None) => - Core.String.compare(v1, v2)
};
let getInstalledVersionSet = () =>
Fs.readdir(Directories.nodeVersions)
|> Result.fold(_ => [||], x => x)
|> Array.fold_left(
(acc, curr) => VersionSet.add(curr, acc),
VersionSet.empty,
);
let getRelativeLinksFromHTML = html =>
Soup.parse(html)
|> Soup.select("pre a")
|> Soup.to_list
|> List.map(Soup.attribute("href"))
|> Core.List.filter_map(~f=x => x);
let downloadFileSuffix = ".tar.gz";
let getVersionFromFilename = filename => {
let strings = filename |> String.split_on_char('-');
List.nth(strings, 1);
};
};
let format = version => {
let version =
switch (Str.first_chars(version, 1) |> Int32.of_string) {
| _ => "v" ++ version
| exception _ => version
};
version;
};
let endsWith = (~suffix, str) => {
let suffixLength = String.length(suffix);
String.length(str) > suffixLength
&& Str.last_chars(str, suffixLength) == suffix;
};
exception No_Download_For_System(System.NodeOS.t, System.NodeArch.t);
let getFileToDownload = (~version as versionName, ~os, ~arch) => {
let versionName =
switch (Str.first_chars(versionName, 1) |> Int32.of_string) {
| _ => "v" ++ versionName
| exception _ => versionName
};
let url = "https://nodejs.org/dist/" ++ versionName ++ "/";
let%lwt html =
try%lwt (Http.makeRequest(url) |> Lwt.map(Http.body)) {
| Http.Not_found(_) => Lwt.fail(Version_not_found(versionName))
};
let filenames =
html
|> Remote.getRelativeLinksFromHTML
|> List.filter(
endsWith(
~suffix=
System.NodeOS.toString(os)
++ "-"
++ System.NodeArch.toString(arch)
++ Remote.downloadFileSuffix,
),
);
switch (filenames |> List.hd) {
| x => Lwt.return(url ++ x)
| exception _ => Lwt.fail(No_Download_For_System(os, arch))
};
};
let getCurrentVersion = () =>
switch (Fs.realpath(Directories.currentVersion)) {
| installationPath =>
let fullPath = Filename.dirname(installationPath);
Some(Local.{fullPath, name: Core.Filename.basename(fullPath)});
| exception (Unix.Unix_error(_, _, _)) => None
};
let getInstalledVersions = () =>
Fs.readdir(Directories.nodeVersions)
|> Result.map(x => {
Array.sort(Remote.compare, x);
x;
})
|> Result.map(
Array.map(name =>
Local.{
name,
fullPath: Filename.concat(Directories.nodeVersions, name),
}
),
);
let getRemoteVersions = () => {
let%lwt bodyString =
Http.makeRequest("https://nodejs.org/dist/") |> Lwt.map(Http.body);
let versions = bodyString |> Remote.getRelativeLinksFromHTML;
let installedVersions = Remote.getInstalledVersionSet();
versions
|> Core.List.filter(~f=x =>
Str.last_chars(x, 1) == "/" && Str.first_chars(x, 1) != "."
)
|> Core.List.map(~f=x => Str.first_chars(x, String.length(x) - 1))
|> List.sort(Remote.compare)
|> List.map(name =>
Remote.{
name,
installed: VersionSet.find_opt(name, installedVersions) != None,
baseURL: "https://nodejs.org/dist/" ++ name ++ "/",
}
)
|> Lwt.return;
};

2
library/dune

@ -7,4 +7,6 @@ @@ -7,4 +7,6 @@
(name Nsw)
; Other libraries list this name in their package.json 'require' field to use this library.
(public_name nsw.lib)
(libraries str core lwt lwt.unix lambdasoup semver )
(preprocess ( pps lwt_ppx ppx_let )) ; From package.json preprocess field
)

21
package.json

@ -4,42 +4,51 @@ @@ -4,42 +4,51 @@
"description": "My Project",
"esy": {
"build": "pesy",
"buildsInSource": "_build",
"release": {
"releasedBinaries": [
"NswApp.exe"
"nsw.exe"
]
}
},
"buildDirs": {
"test": {
"require": ["nsw.lib"],
"require": ["nsw.lib", "rely.lib"],
"main": "TestNsw",
"name": "TestNsw.exe"
"name": "TestNsw.exe",
"ocamloptFlags": ["-linkall", "-g"]
},
"library": {
"preprocess": ["pps", "lwt_ppx", "ppx_let"],
"require": ["str", "core", "lwt", "lwt.unix", "lambdasoup", "semver"],
"name": "nsw.lib",
"namespace": "Nsw"
},
"executable": {
"preprocess": ["pps", "lwt_ppx", "ppx_let"],
"require": ["core", "lwt", "lwt.unix", "console.lib", "pastel.lib", "nsw.lib"],
"require": ["core", "cmdliner", "lwt", "lwt.unix", "lambdasoup", "console.lib", "pastel.lib", "nsw.lib"],
"main": "NswApp",
"name": "NswApp.exe"
"name": "nsw.exe"
}
},
"scripts": {
"pesy": "bash -c 'env PESY_MODE=update pesy'",
"test": "esy x TestNsw.exe"
"test": "esy x TestNsw.exe",
"fmt": "bash -c 'refmt --in-place {library,executable,test}/*.re'"
},
"dependencies": {
"@opam/dune": "*",
"@opam/semver": "*",
"@opam/core": "*",
"@opam/cmdliner": "*",
"@opam/lwt": "*",
"@opam/lwt_ppx": "*",
"@opam/ppx_let": "*",
"@reason-native/console": "*",
"@reason-native/pastel": "*",
"@reason-native/rely": "*",
"@esy-ocaml/reason": "*",
"@opam/lambdasoup": "*",
"refmterr": "*",
"ocaml": "~4.6.0",
"pesy": "*"

17
test/SmokeTest.re

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
open TestFramework;
describe("Smoke test", ({test}) => {
test("Tests run!", ({expect}) =>
expect.int(1).toBe(1)
);
test("Get version", ({expect}) => {
let version = run([|"--version"|]);
expect.string(version).toMatch("^[0-9]+.[0-9]+.[0-9]+$");
});
test("env", ({expect}) => {
let env = run([|"env"|]) |> redactSfwRoot;
expect.string(env).toMatchSnapshot();
});
});

31
test/TestFramework.re

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
let projectDir = Sys.getcwd();
let tmpDir = Filename.concat(projectDir, ".nswTmp");
include Rely.Make({
let config =
Rely.TestFrameworkConfig.initialize({
snapshotDir:
Filename.concat(
projectDir,
Filename.concat("test", "__snapshots__"),
),
projectDir,
});
});
let run = args => {
let arguments =
args |> Array.append([|"./_build/default/executable/NswApp.exe"|]);
let env = Unix.environment() |> Array.append([|"NSW_DIR=" ++ tmpDir|]);
let result =
Lwt_process.pread_chars(~env, ("", arguments)) |> Lwt_stream.to_string;
Lwt_main.run(result);
};
let clearTmpDir = () => {
let _ = Lwt_process.pread(("", [|"rm", "-rf", tmpDir|])) |> Lwt_main.run;
();
};
let redactSfwRoot =
Str.global_replace(Str.regexp_string(tmpDir), "<sfwRoot>");

5
test/TestNsw.re

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
Nsw.Util.foo();
print_endline("Add Your Test Cases Here");
include SmokeTest;
TestFramework.cli();

3
test/__snapshots__/Smoke_test.4d362c3c.0.snapshot

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
Smoke test › env
export PATH=<sfwRoot>/current/bin:$PATH

3
test/dune

@ -7,5 +7,6 @@ @@ -7,5 +7,6 @@
(name TestNsw) ; From package.json main field
; The name of the executable (runnable via esy x TestNsw.exe)
(public_name TestNsw.exe) ; From package.json name field
(libraries nsw.lib ) ; From package.json require field (array of strings)
(libraries nsw.lib rely.lib ) ; From package.json require field (array of strings)
(ocamlopt_flags ( -linkall -g )) ; From package.json ocamloptFlags field
)
Loading…
Cancel
Save