1: | <?php |
2: | declare(strict_types=1); |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | use Opcenter\Versioning; |
16: | |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | class Node_Module extends \Module\Support\Anyversion |
23: | { |
24: | const NVM_LOCATION = FILESYSTEM_SHARED . '/node/nvm/nvm-exec'; |
25: | |
26: | |
27: | |
28: | |
29: | |
30: | |
31: | |
32: | |
33: | |
34: | |
35: | public function do(...$args): array |
36: | { |
37: | $argc = count($args); |
38: | if ($argc === 2) { |
39: | |
40: | warn("API change: node_do uses new signature"); |
41: | [$version, $command] = $args; |
42: | $pwd = null; |
43: | $args = $env = []; |
44: | } else { |
45: | if ($argc < 5) { |
46: | $args += array_fill($argc, 5-$argc, []); |
47: | } |
48: | [$version, $pwd, $command, $args, $env] = $args; |
49: | } |
50: | |
51: | if ($version === 'lts') { |
52: | $version = '--lts'; |
53: | } else if ($version) { |
54: | $version = escapeshellarg($version); |
55: | } |
56: | |
57: | return $this->exec($pwd, "exec --silent {$version} -- {$command}", $args, $env); |
58: | } |
59: | |
60: | protected function environmentRoot(bool $reset = false): ?string |
61: | { |
62: | return FILESYSTEM_SHARED . '/node/nvm'; |
63: | } |
64: | |
65: | |
66: | |
67: | |
68: | |
69: | |
70: | |
71: | |
72: | |
73: | protected function exec(?string $pwd, string $command, array $args = [], array $env = []): array |
74: | { |
75: | $env['PATH'] = \Util_Process::DEFAULT_PATH . PATH_SEPARATOR . '~/node_modules/.bin'; |
76: | |
77: | return parent::exec($pwd, $command, $args, $env); |
78: | } |
79: | |
80: | |
81: | |
82: | |
83: | |
84: | |
85: | |
86: | public function uninstall(string $version): bool |
87: | { |
88: | if ($version === 'lts') { |
89: | $version = '--lts'; |
90: | } |
91: | $ret = $this->exec(null, 'uninstall %s', [$version]); |
92: | if (!$ret['success']) { |
93: | return error('failed to uninstall Node %s: %s', |
94: | $version, |
95: | coalesce($ret['stderr'], $ret['stdout']) |
96: | ); |
97: | } |
98: | |
99: | return true; |
100: | } |
101: | |
102: | |
103: | |
104: | |
105: | |
106: | |
107: | |
108: | |
109: | |
110: | |
111: | public function make_default(string $version, string $path = '~'): bool |
112: | { |
113: | $path .= '/.nvmrc'; |
114: | if ($version === 'lts') { |
115: | $version = 'lts/*'; |
116: | } |
117: | |
118: | return $this->file_put_file_contents($path, $this->resolveVersion($version), true); |
119: | } |
120: | |
121: | |
122: | |
123: | |
124: | |
125: | |
126: | |
127: | public function version_from_path(string $path): string |
128: | { |
129: | $path .= '/.nvmrc'; |
130: | if (!$this->file_exists($path)) { |
131: | return 'system'; |
132: | } |
133: | |
134: | return trim($this->file_get_file_contents($path)); |
135: | } |
136: | |
137: | public function get_default(string $path): ?string |
138: | { |
139: | deprecated_func('Use version_from_path'); |
140: | return $this->version_from_path($path); |
141: | } |
142: | |
143: | |
144: | |
145: | |
146: | |
147: | |
148: | |
149: | protected function resolveVersion(string $version): ?string |
150: | { |
151: | if ($version === 'lts') { |
152: | $version = 'lts/*'; |
153: | } |
154: | $ret = $this->exec(null, 'version %s', [$version]); |
155: | if ($ret['success']) { |
156: | $resolvedVersion = trim(preg_replace('/^\bv(?=\d)|\s+\*$/', '', $ret['output'])); |
157: | |
158: | if (0 === strpos($resolvedVersion, $version)) { |
159: | return $version; |
160: | } |
161: | |
162: | return $resolvedVersion; |
163: | } |
164: | |
165: | return null; |
166: | } |
167: | |
168: | |
169: | |
170: | |
171: | |
172: | |
173: | |
174: | public function lts_version(string $alias = '*'): ?string |
175: | { |
176: | return $this->resolveVersion('lts/' . $alias); |
177: | } |
178: | |
179: | |
180: | |
181: | |
182: | |
183: | |
184: | |
185: | public function install(string $version): ?string |
186: | { |
187: | if ($version === 'lts') { |
188: | $version = '--lts'; |
189: | } |
190: | $ret = $this->exec(null, 'install %s', [$version]); |
191: | if (!$ret['success']) { |
192: | return nerror('failed to install Node %s, error: %s', |
193: | $version, |
194: | coalesce($ret['stderr'], $ret['stdout']) |
195: | ); |
196: | } |
197: | |
198: | $resolved = $this->exec(null, 'version %s', [$version]); |
199: | |
200: | return rtrim($resolved['stdout'][0] === 'v' ? substr($resolved['stdout'], 1) : $resolved['stdout']); |
201: | } |
202: | |
203: | |
204: | |
205: | |
206: | |
207: | |
208: | |
209: | public function installed(string $version, string $comparator = '='): ?string |
210: | { |
211: | if ($version === 'lts') { |
212: | return $this->lts_installed() ? $this->lts_version() : null; |
213: | } |
214: | $nodes = array_reverse($this->list()); |
215: | |
216: | foreach ($nodes as $alias => $localVersion) { |
217: | if (false !== ($p1 = strpos($localVersion, '.')) && |
218: | strrpos($localVersion, '.') !== $p1 && |
219: | Versioning::compare($localVersion, $version, $comparator)) |
220: | { |
221: | return $localVersion; |
222: | } |
223: | } |
224: | return null; |
225: | } |
226: | |
227: | |
228: | |
229: | |
230: | |
231: | |
232: | public function lts_installed(): bool |
233: | { |
234: | $versions = $this->list(); |
235: | $lts = $versions['lts/*'] ?? null; |
236: | |
237: | return array_has($versions, $lts); |
238: | } |
239: | |
240: | |
241: | |
242: | |
243: | |
244: | |
245: | public function list(): array |
246: | { |
247: | |
248: | $ret = $this->exec(null, 'ls --no-colors'); |
249: | if (!$ret['success']) { |
250: | if ($ret['return'] !== 3) { |
251: | error('failed to query nodes - is nvm installed? error: %s', $ret['error']); |
252: | } |
253: | |
254: | return []; |
255: | } |
256: | if (preg_match_all(\Regex::NVM_NODES, $ret['output'], $versions, PREG_SET_ORDER)) { |
257: | $nodes = array_combine(array_column($versions, 'alias'), array_column($versions, 'version')); |
258: | $active = $this->exec(null, 'version current --no-color')['stdout']; |
259: | if ($active !== 'none') { |
260: | $nodes['active'] = $active; |
261: | } |
262: | return $nodes; |
263: | } |
264: | |
265: | return []; |
266: | } |
267: | |
268: | public function get_available(): array |
269: | { |
270: | $cache = \Cache_Super_Global::spawn(); |
271: | $key = 'node.rem'; |
272: | if (false !== ($res = $cache->get($key))) { |
273: | return $res; |
274: | } |
275: | $ret = $this->exec(null, 'ls-remote'); |
276: | if (!$ret['success']) { |
277: | error('failed to query remote Node versions: %s', coalesce($ret['stderr'], $ret['stdout'])); |
278: | |
279: | return []; |
280: | } |
281: | |
282: | if (!preg_match_all('/\s*v(?<version>\S*)\s*(?:\((?:Latest )?LTS: (\S*)\))?/', $ret['stdout'], $versions, |
283: | PREG_SET_ORDER)) { |
284: | warn('failed to discover any Nodes'); |
285: | |
286: | return []; |
287: | } |
288: | $versions = array_column($versions, 'version'); |
289: | $cache->set($key, $versions); |
290: | |
291: | return $versions; |
292: | } |
293: | } |