react-native-update-cli 1.44.2 → 1.44.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,497 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import type {
3
+ RequestOptions as HttpRequestOptions,
4
+ Agent,
5
+ IncomingMessage,
6
+ } from 'node:http';
7
+ import type { RequestOptions as HttpsRequestOptions } from 'node:https';
8
+ import { join, dirname, resolve as pathResolve, parse } from 'node:path';
9
+ import { npm, yarn } from 'global-dirs';
10
+ import { homedir } from 'node:os';
11
+ import { URL } from 'node:url';
12
+
13
+ import getRegistryUrl from 'registry-auth-token/registry-url';
14
+ import registryAuthToken from 'registry-auth-token';
15
+ import maxSatisfying from 'semver/ranges/max-satisfying';
16
+ import gt from 'semver/functions/gt';
17
+
18
+ interface RegistryVersions {
19
+ /**
20
+ * The latest version of the package found on the registry (if found).
21
+ */
22
+ latest?: string;
23
+ /**
24
+ * The next version of the package found on the registry (if found).
25
+ */
26
+ next?: string;
27
+ /**
28
+ * The latest version of the package found on the registry and satisfied by the wanted tag or version range.
29
+ */
30
+ wanted?: string;
31
+ }
32
+
33
+ interface InstalledVersions {
34
+ /**
35
+ * The current local installed version of the package (if installed).
36
+ */
37
+ local?: string;
38
+ /**
39
+ * The current npm global installed version of the package (if installed).
40
+ */
41
+ globalNpm?: string;
42
+ /**
43
+ * The current yarn global installed version of the package (if installed).
44
+ */
45
+ globalYarn?: string;
46
+ }
47
+
48
+ interface LatestVersionPackage extends InstalledVersions, RegistryVersions {
49
+ /**
50
+ * The name of the package.
51
+ */
52
+ name: string;
53
+ /**
54
+ * The tag or version range that was provided (if provided).
55
+ * @default "latest"
56
+ */
57
+ wantedTagOrRange?: string;
58
+ /**
59
+ * Whether the local or global installed versions (if any) could be upgraded or not, based on the wanted version.
60
+ */
61
+ updatesAvailable:
62
+ | {
63
+ local: string | false;
64
+ globalNpm: string | false;
65
+ globalYarn: string | false;
66
+ }
67
+ | false;
68
+ /**
69
+ * Any error that might have occurred during the process.
70
+ */
71
+ error?: Error;
72
+ }
73
+
74
+ interface RequestOptions {
75
+ readonly ca?: string | Buffer | Array<string | Buffer>;
76
+ readonly rejectUnauthorized?: boolean;
77
+ readonly agent?: Agent | boolean;
78
+ readonly timeout?: number;
79
+ }
80
+
81
+ interface LatestVersionOptions {
82
+ /**
83
+ * Awaiting the api to return might take time, depending on the network, and might impact your package loading performance.
84
+ * You can use the cache mechanism to improve load performance and reduce unnecessary network requests.
85
+ * If `useCache` is not supplied, the api will always check for updates and wait for every requests to return before returning itself.
86
+ * If `useCache` is used, the api will always returned immediately, with either (for each provided packages):
87
+ * 1) a latest/next version available if a cache was found
88
+ * 2) no latest/next version available if no cache was found - in such case updates will be fetched in the background and a cache will
89
+ * be created for each provided packages and made available for the next call to the api.
90
+ * @default false
91
+ */
92
+ readonly useCache?: boolean;
93
+
94
+ /**
95
+ * How long the cache for the provided packages should be used before being refreshed (in milliseconds).
96
+ * If `useCache` is not supplied, this option has no effect.
97
+ * If `0` is used, this will force the cache to refresh immediately:
98
+ * 1) The api will returned immediately (without any latest nor next version available for the provided packages)
99
+ * 2) New updates will be fetched in the background
100
+ * 3) The cache for each provided packages will be refreshed and made available for the next call to the api
101
+ * @default ONE_DAY
102
+ */
103
+ readonly cacheMaxAge?: number;
104
+
105
+ /**
106
+ * A JavaScript package registry url that implements the CommonJS Package Registry specification.
107
+ * @default "Looks at any registry urls in the .npmrc file or fallback to the default npm registry instead"
108
+ * @example <caption>.npmrc</caption>
109
+ * registry = 'https://custom-registry.com/'
110
+ * @pkgscope:registry = 'https://custom-registry.com/'
111
+ */
112
+ readonly registryUrl?: string;
113
+
114
+ /**
115
+ * Set of options to be passed down to Node.js http/https request.
116
+ * @example <caption>Behind a proxy with self-signed certificate</caption>
117
+ * { ca: [ fs.readFileSync('proxy-cert.pem') ] }
118
+ * @example <caption>Bypassing certificate validation</caption>
119
+ * { rejectUnauthorized: false }
120
+ */
121
+ readonly requestOptions?: RequestOptions;
122
+ }
123
+
124
+ interface LatestVersion {
125
+ /**
126
+ * Get latest versions of packages from of a package json like object.
127
+ * @param {PackageJson} item - A package json like object (with dependencies, devDependencies and peerDependencies attributes).
128
+ * @example { dependencies: { 'npm': 'latest' }, devDependencies: { 'npm': '1.3.2' }, peerDependencies: { '@scope/name': '^5.0.2' } }
129
+ * @param {LatestVersionOptions} [options] - An object optionally specifying the use of the cache, the max age of the cache, the registry url and the http or https options.
130
+ * If `useCache` is not supplied, the default of `false` is used.
131
+ * If `cacheMaxAge` is not supplied, the default of `one day` is used.
132
+ * If `registryUrl` is not supplied, the default from `.npmrc` is used or a fallback to the `npm registry url` instead.
133
+ * @returns {Promise<LatestVersionPackage[]>}
134
+ */
135
+ (item: PackageJson, options?: LatestVersionOptions): Promise<
136
+ LatestVersionPackage[]
137
+ >;
138
+
139
+ /**
140
+ * Get latest version of a single package.
141
+ * @param {Package} item - A single package object (represented by a string that should match the following format: `${'@' | ''}${string}@${string}`)
142
+ * @example 'npm', 'npm@1.3.2', '@scope/name@^5.0.2'
143
+ * @param {LatestVersionOptions} [options] - An object optionally specifying the use of the cache, the max age of the cache, the registry url and the http or https options.
144
+ * If `useCache` is not supplied, the default of `false` is used.
145
+ * If `cacheMaxAge` is not supplied, the default of `one day` is used.
146
+ * If `registryUrl` is not supplied, the default from `.npmrc` is used or a fallback to the npm registry url instead.
147
+ * @returns {Promise<LatestVersionPackage>}
148
+ */
149
+ (
150
+ item: Package,
151
+ options?: LatestVersionOptions,
152
+ ): Promise<LatestVersionPackage>;
153
+
154
+ /**
155
+ * Get latest versions of a collection of packages.
156
+ * @param {Package[]} items - A collection of package object (represented by a string that should match the following format: `${'@' | ''}${string}@${string}`)
157
+ * @example ['npm', 'npm@1.3.2', '@scope/name@^5.0.2']
158
+ * @param {LatestVersionOptions} [options] - An object optionally specifying the use of the cache, the max age of the cache, the registry url and the http or https options.
159
+ * If `useCache` is not supplied, the default of `false` is used.
160
+ * If `cacheMaxAge` is not supplied, the default of `one day` is used.
161
+ * If `registryUrl` is not supplied, the default from `.npmrc` is used or a fallback to the npm registry url instead.
162
+ * @returns {Promise<LatestVersionPackage[]>}
163
+ */
164
+ (items: Package[], options?: LatestVersionOptions): Promise<
165
+ LatestVersionPackage[]
166
+ >; // eslint-disable-line @typescript-eslint/unified-signatures
167
+ }
168
+ type PackageRange = `${'@' | ''}${string}@${string}`;
169
+ type Package = PackageRange | string; // eslint-disable-line @typescript-eslint/no-redundant-type-constituents
170
+ type PackageJsonDependencies = Record<string, string>;
171
+ type PackageJson = Record<string, any> &
172
+ (
173
+ | {
174
+ dependencies: PackageJsonDependencies;
175
+ }
176
+ | {
177
+ devDependencies: PackageJsonDependencies;
178
+ }
179
+ | {
180
+ peerDependencies: PackageJsonDependencies;
181
+ }
182
+ );
183
+
184
+ /**
185
+ * @internal
186
+ */
187
+ interface PackageMetadata {
188
+ name: string;
189
+ lastUpdateDate: number;
190
+ versions: string[];
191
+ distTags: Record<string, string>;
192
+ }
193
+
194
+ export const ONE_DAY = 1000 * 60 * 60 * 24; // eslint-disable-line @typescript-eslint/naming-convention
195
+
196
+ const isPackageJson = (obj: any): obj is PackageJson => {
197
+ return (
198
+ (obj as PackageJson).dependencies !== undefined ||
199
+ (obj as PackageJson).devDependencies !== undefined ||
200
+ (obj as PackageJson).peerDependencies !== undefined
201
+ );
202
+ };
203
+
204
+ const downloadMetadata = (
205
+ pkgName: string,
206
+ options?: LatestVersionOptions,
207
+ ): Promise<PackageMetadata> => {
208
+ return new Promise((resolve, reject) => {
209
+ const i = pkgName.indexOf('/');
210
+ const pkgScope = i !== -1 ? pkgName.slice(0, i) : '';
211
+ const registryUrl = options?.registryUrl ?? getRegistryUrl(pkgScope);
212
+ const pkgUrl = new URL(
213
+ encodeURIComponent(pkgName).replace(/^%40/, '@'),
214
+ registryUrl,
215
+ );
216
+
217
+ let requestOptions: HttpRequestOptions | HttpsRequestOptions = {
218
+ headers: {
219
+ accept:
220
+ 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*',
221
+ },
222
+ host: pkgUrl.hostname,
223
+ path: pkgUrl.pathname,
224
+ port: pkgUrl.port,
225
+ };
226
+ const authInfo = registryAuthToken(pkgUrl.toString(), { recursive: true });
227
+ if (authInfo && requestOptions.headers) {
228
+ requestOptions.headers.authorization = `${authInfo.type} ${authInfo.token}`;
229
+ }
230
+ if (options?.requestOptions) {
231
+ requestOptions = { ...requestOptions, ...options.requestOptions };
232
+ }
233
+
234
+ const { get } = require(pkgUrl.protocol === 'https:' ? 'https' : 'http');
235
+ const request = get(requestOptions, (res: IncomingMessage) => {
236
+ if (res.statusCode === 200) {
237
+ let rawData = '';
238
+ res.setEncoding('utf8');
239
+ res.on('data', (chunk: string) => (rawData += chunk));
240
+ res.once('error', (err) => {
241
+ res.removeAllListeners();
242
+ reject(`Request error (${err.message}): ${pkgUrl}`);
243
+ });
244
+ res.once('end', () => {
245
+ res.removeAllListeners();
246
+ try {
247
+ const pkgMetadata = JSON.parse(rawData);
248
+ resolve({
249
+ name: pkgName,
250
+ lastUpdateDate: Date.now(),
251
+ versions: Object.keys(pkgMetadata.versions as string[]),
252
+ distTags: pkgMetadata['dist-tags'],
253
+ });
254
+ return;
255
+ } catch (err) {
256
+ reject(err);
257
+ return;
258
+ }
259
+ });
260
+ } else {
261
+ res.removeAllListeners();
262
+ res.resume(); // consume response data to free up memory
263
+ reject(`Request error (${res.statusCode}): ${pkgUrl}`);
264
+ return;
265
+ }
266
+ });
267
+ const abort = (error: Error | string): void => {
268
+ request.destroy();
269
+ reject(error);
270
+ };
271
+ request.once('timeout', () => {
272
+ abort(`Request timed out: ${pkgUrl}`);
273
+ });
274
+ request.once('error', (err: Error) => {
275
+ abort(err);
276
+ });
277
+ request.on('close', () => {
278
+ request.removeAllListeners();
279
+ });
280
+ });
281
+ };
282
+
283
+ const getCacheDir = (name = '@badisi/latest-version'): string => {
284
+ const homeDir = homedir();
285
+ switch (process.platform) {
286
+ case 'darwin':
287
+ return join(homeDir, 'Library', 'Caches', name);
288
+ case 'win32':
289
+ return join(
290
+ process.env.LOCALAPPDATA ?? join(homeDir, 'AppData', 'Local'),
291
+ name,
292
+ 'Cache',
293
+ );
294
+ default:
295
+ return join(process.env.XDG_CACHE_HOME ?? join(homeDir, '.cache'), name);
296
+ }
297
+ };
298
+
299
+ const saveMetadataToCache = (pkg: PackageMetadata): void => {
300
+ const filePath = join(getCacheDir(), `${pkg.name}.json`);
301
+ if (!existsSync(dirname(filePath))) {
302
+ mkdirSync(dirname(filePath), { recursive: true });
303
+ }
304
+ writeFileSync(filePath, JSON.stringify(pkg));
305
+ };
306
+
307
+ const getMetadataFromCache = (
308
+ pkgName: string,
309
+ options?: LatestVersionOptions,
310
+ ): PackageMetadata | undefined => {
311
+ const maxAge = options?.cacheMaxAge ?? ONE_DAY;
312
+ if (maxAge !== 0) {
313
+ const pkgCacheFilePath = join(getCacheDir(), `${pkgName}.json`);
314
+ if (existsSync(pkgCacheFilePath)) {
315
+ const pkg = JSON.parse(
316
+ readFileSync(pkgCacheFilePath).toString(),
317
+ ) as PackageMetadata;
318
+ if (Date.now() - pkg.lastUpdateDate < maxAge) {
319
+ return pkg;
320
+ }
321
+ }
322
+ }
323
+ return undefined; // invalidates cache
324
+ };
325
+
326
+ const getRegistryVersions = async (
327
+ pkgName: string,
328
+ tagOrRange?: string,
329
+ options?: LatestVersionOptions,
330
+ ): Promise<RegistryVersions> => {
331
+ let pkgMetadata: PackageMetadata | undefined;
332
+ if (pkgName.length && options?.useCache) {
333
+ pkgMetadata = getMetadataFromCache(pkgName, options);
334
+ if (!pkgMetadata) {
335
+ pkgMetadata = await downloadMetadata(pkgName, options);
336
+ saveMetadataToCache(pkgMetadata);
337
+ }
338
+ } else if (pkgName.length) {
339
+ pkgMetadata = await downloadMetadata(pkgName, options);
340
+ }
341
+
342
+ const versions: RegistryVersions = {
343
+ latest: pkgMetadata?.distTags.latest,
344
+ next: pkgMetadata?.distTags.next,
345
+ };
346
+ if (tagOrRange && pkgMetadata?.distTags[tagOrRange]) {
347
+ versions.wanted = pkgMetadata.distTags[tagOrRange];
348
+ } else if (tagOrRange && pkgMetadata?.versions.length) {
349
+ versions.wanted =
350
+ maxSatisfying(pkgMetadata.versions, tagOrRange) ?? undefined;
351
+ }
352
+ return versions;
353
+ };
354
+
355
+ const getInstalledVersion = (
356
+ pkgName: string,
357
+ location: keyof InstalledVersions = 'local',
358
+ ): string | undefined => {
359
+ try {
360
+ if (location === 'globalNpm') {
361
+ return require(join(npm.packages, pkgName, 'package.json'))
362
+ ?.version as string;
363
+ } else if (location === 'globalYarn') {
364
+ // Make sure package is globally installed by Yarn
365
+ const yarnGlobalPkg = require(pathResolve(
366
+ yarn.packages,
367
+ '..',
368
+ 'package.json',
369
+ ));
370
+ if (!yarnGlobalPkg?.dependencies?.[pkgName]) {
371
+ return undefined;
372
+ }
373
+ return require(join(yarn.packages, pkgName, 'package.json'))
374
+ ?.version as string;
375
+ } else {
376
+ /**
377
+ * Compute the local paths manually as require.resolve() and require.resolve.paths()
378
+ * cannot be trusted anymore.
379
+ * @see https://github.com/nodejs/node/issues/33460
380
+ * @see https://github.com/nodejs/loaders/issues/26
381
+ */
382
+ const { root } = parse(process.cwd());
383
+ let path = process.cwd();
384
+ const localPaths = [join(path, 'node_modules')];
385
+ while (path !== root) {
386
+ path = dirname(path);
387
+ localPaths.push(join(path, 'node_modules'));
388
+ }
389
+ for (const localPath of localPaths) {
390
+ const pkgPath = join(localPath, pkgName, 'package.json');
391
+ if (existsSync(pkgPath)) {
392
+ return require(pkgPath)?.version as string;
393
+ }
394
+ }
395
+ }
396
+ return undefined;
397
+ } catch {
398
+ return undefined;
399
+ }
400
+ };
401
+
402
+ const getInfo = async (
403
+ pkg: Package,
404
+ options?: LatestVersionOptions,
405
+ ): Promise<LatestVersionPackage> => {
406
+ const i = pkg.lastIndexOf('@');
407
+ let pkgInfo: LatestVersionPackage = {
408
+ name: i > 1 ? pkg.slice(0, i) : pkg,
409
+ wantedTagOrRange: i > 1 ? pkg.slice(i + 1) : 'latest',
410
+ updatesAvailable: false,
411
+ };
412
+
413
+ try {
414
+ pkgInfo = {
415
+ ...pkgInfo,
416
+ local: getInstalledVersion(pkgInfo.name, 'local'),
417
+ globalNpm: getInstalledVersion(pkgInfo.name, 'globalNpm'),
418
+ globalYarn: getInstalledVersion(pkgInfo.name, 'globalYarn'),
419
+ ...(await getRegistryVersions(
420
+ pkgInfo.name,
421
+ pkgInfo.wantedTagOrRange,
422
+ options,
423
+ )),
424
+ };
425
+ const local =
426
+ pkgInfo.local && pkgInfo.wanted
427
+ ? gt(pkgInfo.wanted, pkgInfo.local)
428
+ ? pkgInfo.wanted
429
+ : false
430
+ : false;
431
+ const globalNpm =
432
+ pkgInfo.globalNpm && pkgInfo.wanted
433
+ ? gt(pkgInfo.wanted, pkgInfo.globalNpm)
434
+ ? pkgInfo.wanted
435
+ : false
436
+ : false;
437
+ const globalYarn =
438
+ pkgInfo.globalYarn && pkgInfo.wanted
439
+ ? gt(pkgInfo.wanted, pkgInfo.globalYarn)
440
+ ? pkgInfo.wanted
441
+ : false
442
+ : false;
443
+ pkgInfo.updatesAvailable =
444
+ local || globalNpm || globalYarn
445
+ ? { local, globalNpm, globalYarn }
446
+ : false;
447
+ } catch (err: any) {
448
+ pkgInfo.error = err?.message ?? err;
449
+ }
450
+
451
+ return pkgInfo;
452
+ };
453
+
454
+ const latestVersion: LatestVersion = async (
455
+ arg: Package | Package[] | PackageJson,
456
+ options?: LatestVersionOptions,
457
+ ): Promise<any> => {
458
+ const pkgs: Package[] = [];
459
+ if (typeof arg === 'string') {
460
+ pkgs.push(arg);
461
+ } else if (Array.isArray(arg)) {
462
+ pkgs.push(...arg);
463
+ } else if (isPackageJson(arg)) {
464
+ const addDeps = (deps?: PackageJsonDependencies): void => {
465
+ if (deps) {
466
+ pkgs.push(
467
+ ...Object.keys(deps).map((key: string) => `${key}@${deps[key]}`),
468
+ );
469
+ }
470
+ };
471
+ addDeps(arg.dependencies as PackageJsonDependencies | undefined);
472
+ addDeps(arg.devDependencies as PackageJsonDependencies | undefined);
473
+ addDeps(arg.peerDependencies as PackageJsonDependencies | undefined);
474
+ }
475
+
476
+ const jobs = await Promise.allSettled(
477
+ pkgs.map((pkg) => getInfo(pkg, options)),
478
+ );
479
+ const results = jobs.map(
480
+ (jobResult: PromiseSettledResult<LatestVersionPackage>) =>
481
+ (jobResult as PromiseFulfilledResult<LatestVersionPackage>).value,
482
+ );
483
+ return typeof arg === 'string' ? results[0] : results;
484
+ };
485
+
486
+ export type {
487
+ LatestVersion,
488
+ Package,
489
+ PackageRange,
490
+ PackageJson,
491
+ PackageJsonDependencies,
492
+ RegistryVersions,
493
+ LatestVersionPackage,
494
+ RequestOptions,
495
+ LatestVersionOptions,
496
+ };
497
+ export default latestVersion;