v7
1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import { $ } from './core.js'
16import { spinner } from './experimental.js'
17
18export async function installDeps(
19 dependencies: Record<string, string>,
20 prefix?: string
21) {
22 const packages = Object.entries(dependencies).map(
23 ([name, version]) => `${name}@${version}`
24 )
25 const flags = prefix ? `--prefix=${prefix}` : ''
26 if (packages.length === 0) {
27 return
28 }
29 await spinner(`npm i ${packages.join(' ')}`, () =>
30 $`npm install --no-save --no-audit --no-fund ${flags} ${packages}`.nothrow()
31 )
32}
33
34const builtins = new Set([
35 '_http_agent',
36 '_http_client',
37 '_http_common',
38 '_http_incoming',
39 '_http_outgoing',
40 '_http_server',
41 '_stream_duplex',
42 '_stream_passthrough',
43 '_stream_readable',
44 '_stream_transform',
45 '_stream_wrap',
46 '_stream_writable',
47 '_tls_common',
48 '_tls_wrap',
49 'assert',
50 'async_hooks',
51 'buffer',
52 'child_process',
53 'cluster',
54 'console',
55 'constants',
56 'crypto',
57 'dgram',
58 'dns',
59 'domain',
60 'events',
61 'fs',
62 'http',
63 'http2',
64 'https',
65 'inspector',
66 'module',
67 'net',
68 'os',
69 'path',
70 'perf_hooks',
71 'process',
72 'punycode',
73 'querystring',
74 'readline',
75 'repl',
76 'stream',
77 'string_decoder',
78 'sys',
79 'timers',
80 'tls',
81 'trace_events',
82 'tty',
83 'url',
84 'util',
85 'v8',
86 'vm',
87 'wasi',
88 'worker_threads',
89 'zlib',
90])
91const importRe = [
92 /\bimport\s+['"](?<path>[^'"]+)['"]/,
93 /\bimport\(['"](?<path>[^'"]+)['"]\)/,
94 /\brequire\(['"](?<path>[^'"]+)['"]\)/,
95 /\bfrom\s+['"](?<path>[^'"]+)['"]/,
96]
97const nameRe = /^(?<name>(@[a-z0-9-]+\/)?[a-z0-9-.]+)\/?.*$/i
98const versionRe = /(\/\/|\/\*)\s*@(?<version>[~^]?([\dvx*]+([-.][\dx*]+)*))/i
99
100export function parseDeps(content: Buffer): Record<string, string> {
101 const deps: Record<string, string> = {}
102 const lines = content.toString().split('\n')
103 for (let line of lines) {
104 const tuple = parseImports(line)
105 if (tuple) {
106 deps[tuple.name] = tuple.version
107 }
108 }
109 return deps
110}
111
112function parseImports(
113 line: string
114): { name: string; version: string } | undefined {
115 for (let re of importRe) {
116 const name = parsePackageName(re.exec(line)?.groups?.path)
117 const version = parseVersion(line)
118 if (name) {
119 return { name, version }
120 }
121 }
122}
123
124function parsePackageName(path?: string): string | undefined {
125 if (!path) return
126 const name = nameRe.exec(path)?.groups?.name
127 if (name && !builtins.has(name)) {
128 return name
129 }
130}
131
132function parseVersion(line: string) {
133 return versionRe.exec(line)?.groups?.version || 'latest'
134}