1 | import os from 'os';
|
2 | import path from 'path';
|
3 | import semver from 'semver';
|
4 | import { getVersion, DownloadInfo, VersionListOpts, clearCache } from './version-list';
|
5 | import { getCurrentLinuxDistro } from './linux-distro';
|
6 | import { inspect } from 'util';
|
7 | import _debug from 'debug';
|
8 | const debug = _debug('mongodb-download-url');
|
9 |
|
10 | type PriorityValue<T> = { value: T; priority: number; }
|
11 |
|
12 | type ArtifactOptions = {
|
13 | |
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | version?: string;
|
20 | |
21 |
|
22 |
|
23 | arch?: string;
|
24 | |
25 |
|
26 |
|
27 |
|
28 | platform?: string;
|
29 | |
30 |
|
31 |
|
32 | distro?: string;
|
33 | |
34 |
|
35 |
|
36 | enterprise?: boolean;
|
37 | |
38 |
|
39 |
|
40 |
|
41 | cryptd?: boolean;
|
42 | |
43 |
|
44 |
|
45 | bits?: '32' | '64' | 32 | 64
|
46 | };
|
47 |
|
48 | export type Options = ArtifactOptions & VersionListOpts;
|
49 |
|
50 | export type DownloadArtifactInfo = Required<ArtifactOptions> & {
|
51 |
|
52 | url: string;
|
53 |
|
54 | artifact: string;
|
55 |
|
56 | name: string;
|
57 |
|
58 |
|
59 | ext: string;
|
60 |
|
61 | filenamePlatform: string;
|
62 |
|
63 | debug: false;
|
64 |
|
65 | branch: 'master';
|
66 | };
|
67 |
|
68 | type ProcessedOptions = {
|
69 | version: string;
|
70 | arch: string[];
|
71 | target: PriorityValue<string>[];
|
72 | enterprise: boolean;
|
73 | cryptd: boolean;
|
74 | };
|
75 |
|
76 | function getPriority<T>(values: PriorityValue<T>[], candidate: T): number {
|
77 | for (const { value, priority } of values) {
|
78 | if (value === candidate) {
|
79 | return priority;
|
80 | }
|
81 | }
|
82 | return 0;
|
83 | }
|
84 |
|
85 | function maximizer<T>(values: T[], evaluator: (v: T) => number): T | undefined {
|
86 | let max = -Infinity;
|
87 | let maximizer: T | undefined;
|
88 | for (const v of values) {
|
89 | const result = evaluator(v);
|
90 | if (result > max) {
|
91 | max = result;
|
92 | maximizer = v;
|
93 | }
|
94 | }
|
95 | return maximizer;
|
96 | }
|
97 |
|
98 | function parseArch(arch: string): string[] {
|
99 | if (['i686', 'i386', 'x86', 'ia32'].includes(arch)) {
|
100 | return ['i686', 'i386', 'x86', 'ia32'];
|
101 | }
|
102 | if (['x86_64', 'x64'].includes(arch)) {
|
103 | return ['x86_64', 'x64'];
|
104 | }
|
105 | if (['arm64', 'aarch64'].includes(arch)) {
|
106 | return ['arm64', 'aarch64'];
|
107 | }
|
108 | if (['ppc64', 'ppc64le'].includes(arch)) {
|
109 | return ['ppc64', 'ppc64le'];
|
110 | }
|
111 | return [arch];
|
112 | }
|
113 |
|
114 | async function parseTarget(distro: string | undefined, platform: string, archs: string[], version: string): Promise<PriorityValue<string>[]> {
|
115 | if (platform === 'linux') {
|
116 | const results: PriorityValue<string>[] = [];
|
117 | if (distro) {
|
118 | results.push({ value: distro, priority: 1000 });
|
119 |
|
120 | if (archs.includes('x86_64')) {
|
121 | if (distro === 'amzn64' || distro === 'amazon1') {
|
122 | results.push({ value: 'amazon', priority: 900 });
|
123 | }
|
124 | if (distro === 'amazon' || distro === 'amazon1') {
|
125 | results.push({ value: 'amzn64', priority: 900 });
|
126 | }
|
127 | }
|
128 | }
|
129 |
|
130 | if (archs.includes('x86_64')) {
|
131 | results.push({ value: 'linux_x86_64', priority: 1 });
|
132 | } else if (archs.includes('i686')) {
|
133 | results.push({ value: 'linux_i686', priority: 1 });
|
134 | }
|
135 |
|
136 | let distroResultsErr;
|
137 | try {
|
138 | results.push(...await getCurrentLinuxDistro());
|
139 | } catch (err) {
|
140 | distroResultsErr = err;
|
141 | }
|
142 | if (distro === undefined &&
|
143 | distroResultsErr &&
|
144 | (version === '*' ||
|
145 | version === 'latest-alpha' ||
|
146 | semver.gte(version, '4.0.0'))) {
|
147 | throw distroResultsErr;
|
148 | }
|
149 | return results;
|
150 | } else if (platform === 'sunos') {
|
151 | return [{ value: 'sunos5', priority: 1 }];
|
152 | } else if (['win32', 'windows'].includes(platform)) {
|
153 | if (archs.includes('i686')) {
|
154 | return [
|
155 | { value: 'windows', priority: 1 },
|
156 | { value: 'windows_i686', priority: 10 }
|
157 | ];
|
158 | } else {
|
159 | return [
|
160 | { value: 'windows', priority: 1 },
|
161 | { value: 'windows_x86_64', priority: 10 },
|
162 | { value: 'windows_x86_64-2008plus', priority: 10 },
|
163 | { value: 'windows_x86_64-2008plus-ssl', priority: 100 },
|
164 | { value: 'windows_x86_64-2012plus', priority: 100 }
|
165 | ];
|
166 | }
|
167 | } else if (['darwin', 'osx', 'macos'].includes(platform)) {
|
168 | return [
|
169 | { value: 'osx', priority: 1 },
|
170 | { value: 'osx-ssl', priority: 10 },
|
171 | { value: 'darwin', priority: 1 },
|
172 | { value: 'macos', priority: 1 }
|
173 | ];
|
174 | }
|
175 | return [{ value: platform, priority: 1 }];
|
176 | }
|
177 |
|
178 | async function resolve(opts: ProcessedOptions): Promise<DownloadArtifactInfo> {
|
179 | let download: DownloadInfo;
|
180 | if (opts.version === 'latest-alpha' && opts.enterprise) {
|
181 | const targets = opts.target.map(({ value }) => value);
|
182 | let url, target;
|
183 | if (targets.includes('macos')) {
|
184 | url = 'https://downloads.mongodb.com/osx/mongodb-macos-x86_64-enterprise-latest.tgz';
|
185 | target = 'macos';
|
186 | } else if (targets.includes('linux_x86_64')) {
|
187 | target = maximizer(opts.target, candidate => candidate.priority).value;
|
188 | url = `https://downloads.mongodb.com/linux/mongodb-linux-x86_64-enterprise-${target}-latest.tgz`;
|
189 | } else if (targets.includes('windows_x86_64')) {
|
190 | target = 'windows';
|
191 | url = 'https://downloads.mongodb.com/windows/mongodb-windows-x86_64-enterprise-latest.zip';
|
192 | }
|
193 | if (url) {
|
194 | download = {
|
195 | target,
|
196 | edition: 'enterprise',
|
197 | arch: 'x86_64',
|
198 | archive: {
|
199 | url,
|
200 | sha1: '',
|
201 | sha256: '',
|
202 | debug_symbols: ''
|
203 | }
|
204 | };
|
205 | }
|
206 | }
|
207 |
|
208 | let version;
|
209 | if (!download) {
|
210 | version = await getVersion(opts);
|
211 | if (!version) {
|
212 | throw new Error(`Could not find version matching ${inspect(opts)}`);
|
213 | }
|
214 | const bestDownload = maximizer(version.downloads.map((candidate: DownloadInfo) => {
|
215 | if (opts.enterprise) {
|
216 | if (candidate.edition !== 'enterprise') {
|
217 | return { value: candidate, priority: 0 };
|
218 | }
|
219 | } else {
|
220 | if (candidate.edition !== 'targeted' && candidate.edition !== 'base') {
|
221 | return { value: candidate, priority: 0 };
|
222 | }
|
223 | }
|
224 |
|
225 | if (!opts.arch.includes(candidate.arch)) {
|
226 | return { value: candidate, priority: 0 };
|
227 | }
|
228 |
|
229 | const targetPriority = getPriority(opts.target, candidate.target);
|
230 | return { value: candidate, priority: targetPriority };
|
231 | }), (candidate: PriorityValue<DownloadInfo>) => candidate.priority);
|
232 | if (bestDownload.priority > 0) {
|
233 | download = bestDownload.value;
|
234 | }
|
235 | }
|
236 | if (!download) {
|
237 | throw new Error(`Could not find download URL for version ${version?.version} ${inspect(opts)}`);
|
238 | }
|
239 |
|
240 | debug('fully resolved', JSON.stringify(opts, null, 2), download);
|
241 | const wantsCryptd = opts.cryptd && download.target;
|
242 | let { url } = (wantsCryptd ? download.cryptd : null) ?? download.archive;
|
243 | if (wantsCryptd) {
|
244 |
|
245 | url = url.replace('mongodb-shell-windows', 'mongodb-cryptd-windows');
|
246 | }
|
247 |
|
248 | return {
|
249 | ...opts,
|
250 | name: 'mongodb',
|
251 | url: url,
|
252 | arch: download.arch,
|
253 | distro: download.target,
|
254 | platform: download.target,
|
255 | filenamePlatform: download.target,
|
256 | version: version?.version ?? '*',
|
257 | artifact: path.basename(url),
|
258 | debug: false,
|
259 | enterprise: download.edition === 'enterprise',
|
260 | branch: 'master',
|
261 | bits: ['i386', 'i686'].includes(download.arch) ? '32' : '64',
|
262 | ext: url.match(/\.([^.]+)$/)?.[1] ?? 'tgz'
|
263 | };
|
264 | }
|
265 |
|
266 | async function options(opts: Options | string = {}): Promise<ProcessedOptions & VersionListOpts> {
|
267 | if (typeof opts === 'string') {
|
268 | opts = {
|
269 | version: opts
|
270 | };
|
271 | } else {
|
272 | opts = { ...opts };
|
273 | }
|
274 |
|
275 | if (opts.bits && !opts.arch) {
|
276 | opts.arch = +opts.bits === 32 ? 'ia32' : 'x64';
|
277 | }
|
278 | if (!opts.arch) {
|
279 | opts.arch = os.arch();
|
280 | }
|
281 | if (!opts.platform) {
|
282 | opts.platform = os.platform();
|
283 | }
|
284 | if (!opts.version) {
|
285 | opts.version = process.env.MONGODB_VERSION || 'stable';
|
286 | }
|
287 |
|
288 | if (opts.version === 'stable' || opts.version === 'latest' || opts.version === '*') {
|
289 | opts.version = '*';
|
290 | opts.productionOnly = true;
|
291 | } else if (opts.version === 'unstable') {
|
292 | opts.version = '*';
|
293 | opts.productionOnly = false;
|
294 | }
|
295 |
|
296 | const processedOptions: ProcessedOptions & VersionListOpts = {
|
297 | ...opts,
|
298 | arch: parseArch(opts.arch),
|
299 | target: [],
|
300 | enterprise: !!opts.enterprise,
|
301 | cryptd: !!opts.cryptd,
|
302 | version: opts.version as string
|
303 | };
|
304 | processedOptions.target = await parseTarget(
|
305 | opts.distro,
|
306 | opts.platform,
|
307 | processedOptions.arch,
|
308 | processedOptions.version);
|
309 | return processedOptions;
|
310 | }
|
311 |
|
312 | export async function getDownloadURL(opts?: Options | string): Promise<DownloadArtifactInfo> {
|
313 | const parsedOptions = await options(opts);
|
314 |
|
315 | debug('Building URL for options `%j`', parsedOptions);
|
316 | return await resolve(parsedOptions);
|
317 | }
|
318 |
|
319 | export default getDownloadURL;
|
320 | export { clearCache };
|