diff --git a/.ci/create-static-binary.sh b/.ci/create-static-binary.sh
new file mode 100755
index 000000000..27e934b6b
--- /dev/null
+++ b/.ci/create-static-binary.sh
@@ -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
diff --git a/.ci/esy-build-steps.yml b/.ci/esy-build-steps.yml
index 7aad2d6b1..6f98189e9 100644
--- a/.ci/esy-build-steps.yml
+++ b/.ci/esy-build-steps.yml
@@ -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:
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
diff --git a/.ci/prepare-static-build.sh b/.ci/prepare-static-build.sh
new file mode 100644
index 000000000..f84570ebd
--- /dev/null
+++ b/.ci/prepare-static-build.sh
@@ -0,0 +1 @@
+sed -i 's@"flags": \[\]@"flags": ["-ccopt", "-static"]@' package.json
diff --git a/.ci/shasum b/.ci/shasum
new file mode 100755
index 000000000..1d9c32d31
--- /dev/null
+++ b/.ci/shasum
@@ -0,0 +1,3 @@
+#!/bin/bash
+shift 2
+sha1sum $@
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..7ac74dd4e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+_build
+_esy
+node_modules
+Dockerfile
diff --git a/.gitignore b/.gitignore
index b07980916..d4d1fb963 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ _esy/
nsw.install
.DS_Store
*.install
+.tmp
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..9abc7c555
--- /dev/null
+++ b/Dockerfile
@@ -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
\ No newline at end of file
diff --git a/README.md b/README.md
index 7594aa5f6..835ebbceb 100644
--- a/README.md
+++ b/README.md
@@ -1,35 +1,45 @@
-# nsw
+
+ Node Switcher (nsw
)
+
+> 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
+git clone https://github.com/Schniz/nsw.git
esy install
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
```
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 820a19942..5b0aee121 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -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:
# 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:
dependsOn:
- Linux
- MacOS
- - Windows
+ # - Windows
condition: succeeded()
pool:
vmImage: ubuntu-16.04
diff --git a/docs/nvm_comparison.png b/docs/nvm_comparison.png
new file mode 100644
index 000000000..7c3c2913f
Binary files /dev/null and b/docs/nvm_comparison.png differ
diff --git a/esy.lock/index.json b/esy.lock/index.json
index 6251a8fd7..fa96723f4 100644
--- a/esy.lock/index.json
+++ b/esy.lock/index.json
@@ -1,5 +1,5 @@
{
- "checksum": "fc3353f4500533a4dfc452ed120be173",
+ "checksum": "bc7707f147bdaf3b3f87430bb4b3f0cd",
"root": "nsw@link:./package.json",
"node": {
"refmterr@3.1.10@d41d8cd9": {
@@ -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 @@
"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 @@
"@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 @@
"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 @@
"@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 @@
],
"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 @@
"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 @@
"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 @@
"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": [],
diff --git a/esy.lock/opam/cmdliner.1.0.3/opam b/esy.lock/opam/cmdliner.1.0.3/opam
new file mode 100644
index 000000000..661a34be7
--- /dev/null
+++ b/esy.lock/opam/cmdliner.1.0.3/opam
@@ -0,0 +1,36 @@
+opam-version: "2.0"
+maintainer: "Daniel Bünzli "
+authors: ["Daniel Bünzli "]
+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"
+}
diff --git a/esy.lock/opam/lambdasoup.0.6.3/opam b/esy.lock/opam/lambdasoup.0.6.3/opam
new file mode 100644
index 000000000..f27c0679e
--- /dev/null
+++ b/esy.lock/opam/lambdasoup.0.6.3/opam
@@ -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 "
+maintainer: "Anton Bachin "
+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"
+}
diff --git a/esy.lock/opam/markup.0.8.0/opam b/esy.lock/opam/markup.0.8.0/opam
new file mode 100644
index 000000000..a22644c9d
--- /dev/null
+++ b/esy.lock/opam/markup.0.8.0/opam
@@ -0,0 +1,52 @@
+opam-version: "2.0"
+version: "0.8.0"
+
+maintainer: "Anton Bachin "
+authors: "Anton Bachin "
+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"
+}
diff --git a/esy.lock/opam/semver.0.1.0/opam b/esy.lock/opam/semver.0.1.0/opam
new file mode 100644
index 000000000..3ee31e3ee
--- /dev/null
+++ b/esy.lock/opam/semver.0.1.0/opam
@@ -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"
+}
diff --git a/esy.lock/opam/topkg.1.0.0/opam b/esy.lock/opam/topkg.1.0.0/opam
new file mode 100644
index 000000000..2276edb33
--- /dev/null
+++ b/esy.lock/opam/topkg.1.0.0/opam
@@ -0,0 +1,49 @@
+opam-version: "2.0"
+maintainer: "Daniel Bünzli "
+authors: ["Daniel Bünzli "]
+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"
+}
diff --git a/esy.lock/opam/uchar.0.0.2/opam b/esy.lock/opam/uchar.0.0.2/opam
new file mode 100644
index 000000000..428d7aa6f
--- /dev/null
+++ b/esy.lock/opam/uchar.0.0.2/opam
@@ -0,0 +1,36 @@
+opam-version: "2.0"
+maintainer: "Daniel Bünzli "
+authors: ["Daniel Bünzli "]
+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"
+}
diff --git a/esy.lock/opam/uutf.1.0.1/opam b/esy.lock/opam/uutf.1.0.1/opam
new file mode 100644
index 000000000..e0d3ea812
--- /dev/null
+++ b/esy.lock/opam/uutf.1.0.1/opam
@@ -0,0 +1,38 @@
+opam-version: "2.0"
+maintainer: "Daniel Bünzli "
+authors: ["Daniel Bünzli "]
+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"
+}
diff --git a/executable/Env.re b/executable/Env.re
new file mode 100644
index 000000000..057138597
--- /dev/null
+++ b/executable/Env.re
@@ -0,0 +1,9 @@
+open Nsw;
+
+let run = () => {
+ Console.log(
+ Printf.sprintf("export PATH=%s/bin:$PATH", Directories.currentVersion),
+ );
+
+ Lwt.return();
+};
diff --git a/executable/Install.re b/executable/Install.re
new file mode 100644
index 000000000..c5a090a54
--- /dev/null
+++ b/executable/Install.re
@@ -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(
+
+ "Creating "
+ Directories.downloads
+ " for the first time"
+ ,
+ );
+ 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(
+
+ "Looking for node "
+ versionName
+ " for "
+
+ {System.NodeOS.toString(os)}
+ " "
+ {System.NodeArch.toString(arch)}
+
+ ,
+ );
+
+ let%lwt filepath =
+ Versions.getFileToDownload(~version=versionName, ~os, ~arch);
+ let tarDestination =
+ Filename.concat(Directories.downloads, versionName ++ ".tar.gz");
+
+ Console.log(
+
+ "Downloading "
+ filepath
+ " to "
+ tarDestination
+ ,
+ );
+
+ let%lwt _ = System.mkdirp(Filename.dirname(tarDestination));
+ let%lwt _ = Http.download(filepath, ~into=tarDestination);
+ let extractionDestination =
+ Filename.concat(Directories.nodeVersions, versionName);
+
+ Console.log(
+
+ "Extracting "
+ tarDestination
+ " to "
+ extractionDestination
+ ,
+ );
+
+ 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(
+
+ "Version exists, but can't find a file for your system:\n"
+ " OS: "
+ {System.NodeOS.toString(os)}
+ "\n"
+ " Architecture: "
+ {System.NodeArch.toString(arch)}
+ ,
+ )
+ |> Lwt.return
+ | Versions.Version_not_found(version) =>
+ Console.log(
+
+ "Version "
+ version
+ " not found!"
+ ,
+ )
+ |> Lwt.return
+ };
\ No newline at end of file
diff --git a/executable/ListInstallations.re b/executable/ListInstallations.re
index 97b211cd7..15558f96b 100644
--- a/executable/ListInstallations.re
+++ b/executable/ListInstallations.re
@@ -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 ? str : str;
+ open Versions.Local;
+ let str = "- " ++ version.name;
+
+ let color =
+ current
+ |> Opt.bind(current =>
+ current.name == version.name ? Some(Pastel.Green) : None
+ );
+
+ str ;
});
- ...strings ;
+
+ "## List of installed versions:\n"
+ ...strings
+ ;
};
-let run = () => {
- open Result;
+let getVersionsString = () =>
+ Result.(
+ {
+ let%bind versions =
+ Versions.getInstalledVersions() |> Result.map(Array.to_list);
- 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%bind versions =
- Fs.readdir(Directories.nodeVersions) |> Result.map(Array.to_list);
+ let current = Versions.getCurrentVersion();
- Console.log(
- "## List of installed versions:" ,
+ colorizeVersions(~current, ~versions) |> Result.return;
+ }
);
- printableVersions(~current, ~versions) |> Console.log;
- Result.return();
-};
\ No newline at end of file
+
+let run = () => getVersionsString() |> Result.map(Console.log) |> Lwt.return;
diff --git a/executable/ListLocal.re b/executable/ListLocal.re
new file mode 100644
index 000000000..1d95c10f3
--- /dev/null
+++ b/executable/ListLocal.re
@@ -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( "* " {version.name} );
+ });
+
+ Lwt.return();
+ }
+ );
diff --git a/executable/ListRemote.re b/executable/ListRemote.re
new file mode 100644
index 000000000..4b013c64e
--- /dev/null
+++ b/executable/ListRemote.re
@@ -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( str );
+ });
+
+ Lwt.return();
+};
diff --git a/executable/NswApp.re b/executable/NswApp.re
index c94b841f6..b883bf517 100644
--- a/executable/NswApp.re
+++ b/executable/NswApp.re
@@ -1 +1,99 @@
-ListInstallations.run();
\ No newline at end of file
+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;
diff --git a/executable/Use.re b/executable/Use.re
new file mode 100644
index 000000000..df11b2ec9
--- /dev/null
+++ b/executable/Use.re
@@ -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(
+
+ "Linking "
+ source
+ " to "
+ destination
+ ,
+ );
+
+ let%lwt _ = Lwt_unix.unlink(Directories.currentVersion) |> lwtIgnore;
+ let%lwt _ = Lwt_unix.symlink(destination, Directories.currentVersion);
+
+ Console.log(
+ "Using " version ,
+ );
+
+ 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(
+
+ "The following version is not installed: "
+ version
+ ,
+ )
+ |> Lwt.return
+ | Nvmrc.Version_Not_Provided =>
+ Console.log(
+
+ "No .nvmrc was found in the current directory. Please provide a version number."
+ ,
+ )
+ |> Lwt.return
+ };
diff --git a/executable/dune b/executable/dune
index a22c44806..a4234b3e4 100644
--- a/executable/dune
+++ b/executable/dune
@@ -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
)
\ No newline at end of file
diff --git a/feature_tests/basic/run.sh b/feature_tests/basic/run.sh
new file mode 100644
index 000000000..95b34e96f
--- /dev/null
+++ b/feature_tests/basic/run.sh
@@ -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
\ No newline at end of file
diff --git a/feature_tests/nvmrc/.nvmrc b/feature_tests/nvmrc/.nvmrc
new file mode 100644
index 000000000..e3cbcda79
--- /dev/null
+++ b/feature_tests/nvmrc/.nvmrc
@@ -0,0 +1 @@
+10.9.0
\ No newline at end of file
diff --git a/feature_tests/nvmrc/run.sh b/feature_tests/nvmrc/run.sh
new file mode 100644
index 000000000..206a8adaf
--- /dev/null
+++ b/feature_tests/nvmrc/run.sh
@@ -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
\ No newline at end of file
diff --git a/feature_tests/run.sh b/feature_tests/run.sh
new file mode 100755
index 000000000..683f522ba
--- /dev/null
+++ b/feature_tests/run.sh
@@ -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
diff --git a/library/Compression.re b/library/Compression.re
new file mode 100644
index 000000000..e983e6361
--- /dev/null
+++ b/library/Compression.re
@@ -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"),
+ );
+};
diff --git a/library/Directories.re b/library/Directories.re
new file mode 100644
index 000000000..179a695b2
--- /dev/null
+++ b/library/Directories.re
@@ -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");
\ No newline at end of file
diff --git a/library/Fs.re b/library/Fs.re
new file mode 100644
index 000000000..6be72ae3a
--- /dev/null
+++ b/library/Fs.re
@@ -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;
diff --git a/library/Http.re b/library/Http.re
new file mode 100644
index 000000000..bba03dbe1
--- /dev/null
+++ b/library/Http.re
@@ -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;
+};
\ No newline at end of file
diff --git a/library/Nvmrc.re b/library/Nvmrc.re
new file mode 100644
index 000000000..ff8c370f8
--- /dev/null
+++ b/library/Nvmrc.re
@@ -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)
+ };
+};
diff --git a/library/Opt.re b/library/Opt.re
new file mode 100644
index 000000000..d2e5ebf47
--- /dev/null
+++ b/library/Opt.re
@@ -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);
diff --git a/library/Result.re b/library/Result.re
new file mode 100644
index 000000000..3df533f56
--- /dev/null
+++ b/library/Result.re
@@ -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)
+ };
diff --git a/library/System.re b/library/System.re
new file mode 100644
index 000000000..925c553ad
--- /dev/null
+++ b/library/System.re
@@ -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";
+};
diff --git a/library/Util.re b/library/Util.re
deleted file mode 100644
index 0e7526842..000000000
--- a/library/Util.re
+++ /dev/null
@@ -1 +0,0 @@
-let foo = () => print_endline("Hello");
diff --git a/library/Versions.re b/library/Versions.re
new file mode 100644
index 000000000..2954f32ad
--- /dev/null
+++ b/library/Versions.re
@@ -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;
+};
\ No newline at end of file
diff --git a/library/dune b/library/dune
index 30a4e791b..ac7c1d677 100644
--- a/library/dune
+++ b/library/dune
@@ -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
)
\ No newline at end of file
diff --git a/package.json b/package.json
index 1515a7e7a..a4c814f46 100644
--- a/package.json
+++ b/package.json
@@ -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": "*"
diff --git a/test/SmokeTest.re b/test/SmokeTest.re
new file mode 100644
index 000000000..cbb57fd06
--- /dev/null
+++ b/test/SmokeTest.re
@@ -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();
+ });
+});
\ No newline at end of file
diff --git a/test/TestFramework.re b/test/TestFramework.re
new file mode 100644
index 000000000..d72f7f598
--- /dev/null
+++ b/test/TestFramework.re
@@ -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), "");
\ No newline at end of file
diff --git a/test/TestNsw.re b/test/TestNsw.re
index bc88c6a54..9c5860749 100644
--- a/test/TestNsw.re
+++ b/test/TestNsw.re
@@ -1,2 +1,3 @@
-Nsw.Util.foo();
-print_endline("Add Your Test Cases Here");
+include SmokeTest;
+
+TestFramework.cli();
\ No newline at end of file
diff --git a/test/__snapshots__/Smoke_test.4d362c3c.0.snapshot b/test/__snapshots__/Smoke_test.4d362c3c.0.snapshot
new file mode 100644
index 000000000..4156fabd0
--- /dev/null
+++ b/test/__snapshots__/Smoke_test.4d362c3c.0.snapshot
@@ -0,0 +1,3 @@
+Smoke test › env
+export PATH=/current/bin:$PATH
+
diff --git a/test/dune b/test/dune
index 209103ebf..331f7cf68 100644
--- a/test/dune
+++ b/test/dune
@@ -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
)
\ No newline at end of file