![gal@spitfire.co.il](/assets/img/avatar_default.png)
47 changed files with 1508 additions and 186 deletions
@ -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 |
@ -0,0 +1 @@ |
|||||||
|
sed -i 's@"flags": \[\]@"flags": ["-ccopt", "-static"]@' package.json |
@ -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 |
After Width: | Height: | Size: 195 KiB |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
open Nsw; |
||||||
|
|
||||||
|
let run = () => { |
||||||
|
Console.log( |
||||||
|
Printf.sprintf("export PATH=%s/bin:$PATH", Directories.currentVersion), |
||||||
|
); |
||||||
|
|
||||||
|
Lwt.return(); |
||||||
|
}; |
@ -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 |
||||||
|
}; |
@ -1,122 +1,37 @@ |
|||||||
module Path = { |
open Nsw; |
||||||
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; |
|
||||||
|
|
||||||
|
let colorizeVersions = (~current, ~versions) => { |
||||||
let strings = |
let strings = |
||||||
versions |
versions |
||||||
|> List.map(version => { |
|> List.map(version => { |
||||||
let fullPath = Path.join([Directories.nodeVersions, version]); |
open Versions.Local; |
||||||
let str = "- " ++ version; |
let str = "- " ++ version.name; |
||||||
fullPath == current ? <Pastel color=Green> str </Pastel> : str; |
|
||||||
|
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 = () => { |
let getVersionsString = () => |
||||||
open Result; |
Result.( |
||||||
|
{ |
||||||
|
let%bind versions = |
||||||
|
Versions.getInstalledVersions() |> Result.map(Array.to_list); |
||||||
|
|
||||||
let%bind current = |
let current = Versions.getCurrentVersion(); |
||||||
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); |
|
||||||
|
|
||||||
Console.log( |
colorizeVersions(~current, ~versions) |> Result.return; |
||||||
<Pastel color=Pastel.Cyan> "## List of installed versions:" </Pastel>, |
} |
||||||
); |
); |
||||||
printableVersions(~current, ~versions) |> Console.log; |
|
||||||
Result.return(); |
let run = () => getVersionsString() |> Result.map(Console.log) |> Lwt.return; |
||||||
}; |
|
||||||
|
@ -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(); |
||||||
|
} |
||||||
|
); |
@ -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(); |
||||||
|
}; |
@ -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; |
||||||
|
@ -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 |
||||||
|
}; |
@ -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 |
@ -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 |
@ -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 |
@ -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"), |
||||||
|
); |
||||||
|
}; |
@ -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"); |
@ -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; |
@ -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; |
||||||
|
}; |
@ -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) |
||||||
|
}; |
||||||
|
}; |
@ -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); |
@ -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) |
||||||
|
}; |
@ -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"; |
||||||
|
}; |
@ -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; |
||||||
|
}; |
@ -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(); |
||||||
|
}); |
||||||
|
}); |
@ -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>"); |
@ -1,2 +1,3 @@ |
|||||||
Nsw.Util.foo(); |
include SmokeTest; |
||||||
print_endline("Add Your Test Cases Here"); |
|
||||||
|
TestFramework.cli(); |
@ -0,0 +1,3 @@ |
|||||||
|
Smoke test › env |
||||||
|
export PATH=<sfwRoot>/current/bin:$PATH |
||||||
|
|
Loading…
Reference in new issue