UNPKG

9.48 kBPlain TextView Raw
1import os from 'os';
2import path from 'path';
3import semver from 'semver';
4import { getVersion, DownloadInfo, VersionListOpts, clearCache } from './version-list';
5import { getCurrentLinuxDistro } from './linux-distro';
6import { inspect } from 'util';
7import _debug from 'debug';
8const debug = _debug('mongodb-download-url');
9
10type PriorityValue<T> = { value: T; priority: number; }
11
12type ArtifactOptions = {
13 /**
14 * Specify the version as a semver string. This also accepts the special values:
15 * - 'stable' (or 'latest') for the latest stable version (default)
16 * - 'unstable' for the latest unstable version
17 * - 'latest-alpha' for the latest alpha release of the server
18 */
19 version?: string;
20 /**
21 * Specify the binary architecture. Default is host architecture.
22 */
23 arch?: string;
24 /**
25 * Specify the binary platforn. Default is host platform, as given by
26 * `process.platform`.
27 */
28 platform?: string;
29 /**
30 * If known, specify a mongodb DISTRO_ID string to pick a specific Linux distro.
31 */
32 distro?: string;
33 /**
34 * If true, this will return enterprise servers rather than community servers.
35 */
36 enterprise?: boolean;
37 /**
38 * If true, this will return the mongocryptd-only package, if available.
39 * (This typically only makes sense with { enterprise: true }.)
40 */
41 cryptd?: boolean;
42 /**
43 * @deprecated Use arch instead.
44 */
45 bits?: '32' | '64' | 32 | 64
46};
47
48export type Options = ArtifactOptions & VersionListOpts;
49
50export type DownloadArtifactInfo = Required<ArtifactOptions> & {
51 /** Full download URL. */
52 url: string;
53 /** Filename for the given download URL. */
54 artifact: string;
55 /** Currently always 'mongodb'. */
56 name: string;
57 // Legacy properties:
58 /** @deprecated */
59 ext: string;
60 /** @deprecated */
61 filenamePlatform: string;
62 /** @deprecated */
63 debug: false;
64 /** @deprecated */
65 branch: 'master';
66};
67
68type ProcessedOptions = {
69 version: string;
70 arch: string[];
71 target: PriorityValue<string>[];
72 enterprise: boolean;
73 cryptd: boolean;
74};
75
76function 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
85function 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
98function 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
114async 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
178async 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 // cryptd package on Windows was buggy: https://jira.mongodb.org/browse/BUILD-13653
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
266async 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
312export 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
319export default getDownloadURL;
320export { clearCache };