1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
local Result = require "mason-core.result"
local _ = require "mason-core.functional"
local installer = require "mason-core.installer"
local log = require "mason-core.log"
local path = require "mason-core.path"
local platform = require "mason-core.platform"
local semver = require "mason-core.semver"
local spawn = require "mason-core.spawn"
local M = {}
---@async
---@param predicate fun(npm_version: Semver): boolean
---@return boolean
local function npm_version_satisfies(predicate)
return Result.try(function(try)
local npm_versions = try(spawn.npm { "version", "--json" }).stdout
---@type { npm: string }
local versions = try(Result.pcall(vim.json.decode, npm_versions))
---@type Semver
local npm_version = try(semver.parse(versions.npm))
return predicate(npm_version)
end):get_or_else(false)
end
---@async
function M.init()
log.debug "npm: init"
local ctx = installer.context()
return Result.try(function(try)
try(ctx.spawn.npm {
"init",
"--yes",
"--scope=mason",
})
-- Use shallow install-strategy. The reasons for this are:
-- a) To avoid polluting the executables (aka bin-links) that npm creates.
-- b) The installation is, after all, more similar to a "global" installation. We don't really gain
-- any of the benefits of not using global style (e.g., deduping the dependency tree).
--
-- We write to .npmrc manually instead of going through npm because managing a local .npmrc file
-- is a bit unreliable across npm versions (especially <7), so we take extra measures to avoid
-- inadvertently polluting global npm config.
try(Result.pcall(function()
if npm_version_satisfies(_.gte(semver.new "9.0.0")) then
ctx.fs:append_file(".npmrc", "\ninstall-strategy=shallow")
else
ctx.fs:append_file(".npmrc", "\nglobal-style=true")
end
end))
ctx.stdio_sink:stdout "Initialized npm root.\n"
end)
end
---@async
---@param pkg string
---@param version string
---@param opts? { extra_packages?: string[] }
function M.install(pkg, version, opts)
opts = opts or {}
log.fmt_debug("npm: install %s %s %s", pkg, version, opts)
local ctx = installer.context()
ctx.stdio_sink:stdout(("Installing npm package %s@%s…\n"):format(pkg, version))
return ctx.spawn.npm {
"install",
("%s@%s"):format(pkg, version),
opts.extra_packages or vim.NIL,
}
end
---@async
---@param pkg string
function M.uninstall(pkg)
local ctx = installer.context()
ctx.stdio_sink:stdout(("Uninstalling npm package %s…\n"):format(pkg))
return ctx.spawn.npm { "uninstall", pkg }
end
---@param exec string
function M.bin_path(exec)
return Result.pcall(platform.when, {
unix = function()
return path.concat { "node_modules", ".bin", exec }
end,
win = function()
return path.concat { "node_modules", ".bin", ("%s.cmd"):format(exec) }
end,
})
end
return M
|