Skip to content

Commit 68c602a

Browse files
Fetch versions directly from npm registry url (#57)
* Fetch versions directly from npm registry url * Support alternative npm urls + error handling * Add interface file * Improve error handling some more
1 parent ec46775 commit 68c602a

File tree

5 files changed

+101
-26
lines changed

5 files changed

+101
-26
lines changed

src/ErrorUtils.res

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let getErrorMessage = exn =>
2+
switch exn->Exn.message {
3+
| Some(message) => message
4+
| None => exn->String.make
5+
}

src/NpmRegistry.res

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
type response = {
2+
ok: bool,
3+
status: int,
4+
json: unit => promise<Js.Json.t>,
5+
}
6+
7+
@val external fetch: string => promise<response> = "fetch"
8+
9+
@scope(("process", "env"))
10+
external npm_config_registry: option<string> = "NPM_CONFIG_REGISTRY"
11+
12+
@inline
13+
let defaultRegistryUrl = "https://registry.npmjs.org"
14+
15+
let getNpmRegistry = () =>
16+
npm_config_registry
17+
->Option.flatMap(registry => registry->Node.Url.make)
18+
->Option.mapOr(defaultRegistryUrl, url => url->Node.Url.href)
19+
20+
type fetchError =
21+
| FetchError({message: string})
22+
| HttpError({status: int})
23+
| ParseError
24+
25+
let getFetchErrorMessage = fetchError => {
26+
let message = switch fetchError {
27+
| FetchError({message}) => `Fetch error. Message: ${message}`
28+
| HttpError({status}) => `Http error. Status: ${status->Int.toString}`
29+
| ParseError => "Parse error."
30+
}
31+
32+
`Fetching versions from registry failed: ${message}`
33+
}
34+
35+
let getPackageVersions = async (packageName, range) => {
36+
let registryUrl = getNpmRegistry()
37+
38+
switch await fetch(`${registryUrl}/${packageName}`) {
39+
| response if response.ok =>
40+
switch await response.json() {
41+
| Object(dict) =>
42+
switch dict->Dict.get("versions") {
43+
| Some(Object(dict)) =>
44+
let versions =
45+
dict
46+
->Dict.keysToArray
47+
->Array.filterMap(version =>
48+
version->CompareVersions.satisfies(range) ? Some(version) : None
49+
)
50+
versions->Array.reverse
51+
versions->Ok
52+
53+
| _ => Error(ParseError)
54+
}
55+
| _ => Error(ParseError)
56+
}
57+
58+
| responseNotOk => Error(HttpError({status: responseNotOk.status}))
59+
| exception Exn.Error(exn) => Error(FetchError({message: exn->ErrorUtils.getErrorMessage}))
60+
}
61+
}

src/NpmRegistry.resi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type fetchError =
2+
| FetchError({message: string})
3+
| HttpError({status: int})
4+
| ParseError
5+
6+
let getFetchErrorMessage: fetchError => string
7+
8+
let getPackageVersions: (string, string) => promise<result<array<string>, fetchError>>

src/RescriptVersions.res

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,6 @@ let rescriptCoreVersionRange = ">=1.0.0"
55

66
type versions = {rescriptVersion: string, rescriptCoreVersion: string}
77

8-
let getPackageVersions = async (packageName, range) => {
9-
let {stdout} = await Node.Promisified.ChildProcess.exec(`npm view ${packageName} versions --json`)
10-
11-
let versions = switch JSON.parseExn(stdout) {
12-
| Array(versions) =>
13-
versions->Array.filterMap(json =>
14-
switch json {
15-
| String(version) if version->CompareVersions.satisfies(range) => Some(version)
16-
| _ => None
17-
}
18-
)
19-
| _ => []
20-
}
21-
22-
versions->Array.reverse
23-
versions
24-
}
25-
268
let getCompatibleRescriptCoreVersions = (~rescriptVersion, ~rescriptCoreVersions) =>
279
if CompareVersions.compareVersions(rescriptVersion, "11.1.0")->Ordering.isLess {
2810
rescriptCoreVersions->Array.filter(coreVersion =>
@@ -32,25 +14,36 @@ let getCompatibleRescriptCoreVersions = (~rescriptVersion, ~rescriptCoreVersions
3214
rescriptCoreVersions
3315
}
3416

17+
let spinnerMessage = "Loading available versions..."
18+
3519
let promptVersions = async () => {
3620
let s = P.spinner()
3721

38-
s->P.Spinner.start("Loading available versions...")
22+
s->P.Spinner.start(spinnerMessage)
3923

40-
let (rescriptVersions, rescriptCoreVersions) = await Promise.all2((
41-
getPackageVersions("rescript", rescriptVersionRange),
42-
getPackageVersions("@rescript/core", rescriptCoreVersionRange),
24+
let (rescriptVersionsResult, rescriptCoreVersionsResult) = await Promise.all2((
25+
NpmRegistry.getPackageVersions("rescript", rescriptVersionRange),
26+
NpmRegistry.getPackageVersions("@rescript/core", rescriptCoreVersionRange),
4327
))
4428

45-
s->P.Spinner.stop("Versions loaded.")
29+
switch (rescriptVersionsResult, rescriptCoreVersionsResult) {
30+
| (Ok(_), Ok(_)) => s->P.Spinner.stop("Versions loaded.")
31+
| _ => s->P.Spinner.stop(spinnerMessage)
32+
}
4633

47-
let rescriptVersion = switch rescriptVersions {
48-
| [version] => version
49-
| _ =>
34+
let rescriptVersion = switch rescriptVersionsResult {
35+
| Ok([version]) => version
36+
| Ok(rescriptVersions) =>
5037
await P.select({
5138
message: "ReScript version?",
5239
options: rescriptVersions->Array.map(v => {P.value: v}),
5340
})->P.resultOrRaise
41+
| Error(error) => error->NpmRegistry.getFetchErrorMessage->Error.make->Error.raise
42+
}
43+
44+
let rescriptCoreVersions = switch rescriptCoreVersionsResult {
45+
| Ok(versions) => versions
46+
| Error(error) => error->NpmRegistry.getFetchErrorMessage->Error.make->Error.raise
5447
}
5548

5649
let rescriptCoreVersions = getCompatibleRescriptCoreVersions(

src/bindings/Node.res

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ module Url = {
6464
type t
6565

6666
@module("node:url") external fileURLToPath: t => string = "fileURLToPath"
67+
68+
@new external makeUnsafe: string => t = "URL"
69+
@get external href: t => string = "href"
70+
71+
let make = string =>
72+
try Some(makeUnsafe(string)) catch {
73+
| Exn.Error(_exn) => None
74+
}
6775
}
6876

6977
module Os = {

0 commit comments

Comments
 (0)