forked from react-native-community/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnpm.ts
185 lines (166 loc) · 5.4 KB
/
npm.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {execSync} from 'child_process';
import findUp from 'find-up';
import semver from 'semver';
export function getNpmVersionIfAvailable() {
let npmVersion;
try {
// execSync returns a Buffer -> convert to string
npmVersion = (
execSync('npm --version', {
stdio: [0, 'pipe', 'ignore'],
}).toString() || ''
).trim();
return npmVersion;
} catch (error) {
return null;
}
}
export function isProjectUsingNpm(cwd: string) {
return findUp.sync('package-lock.json', {cwd});
}
export const getNpmRegistryUrl = (() => {
// Lazily resolve npm registry url since it is only needed when initializing a
// new project.
let registryUrl = '';
return () => {
if (!registryUrl) {
try {
registryUrl = execSync(
'npm config get registry --workspaces=false --include-workspace-root',
)
.toString()
.trim();
} catch {
registryUrl = 'https://registry.npmjs.org/';
}
}
return registryUrl;
};
})();
/**
* Convert an npm tag to a concrete version, for example:
* - next -> 0.75.0-rc.0
* - nightly -> 0.75.0-nightly-20240618-5df5ed1a8
*/
export async function npmResolveConcreteVersion(
packageName: string,
tagOrVersion: string,
): Promise<string> {
const url = new URL(getNpmRegistryUrl());
url.pathname = `${packageName}/${tagOrVersion}`;
const resp = await fetch(url);
if (
[
200, // OK
301, // Moved Permanemently
302, // Found
304, // Not Modified
307, // Temporary Redirect
308, // Permanent Redirect
].indexOf(resp.status) === -1
) {
throw new Error(`Unknown version ${packageName}@${tagOrVersion}`);
}
const json: any = await resp.json();
return json.version;
}
type TimeStampString = string;
type TemplateVersion = string;
type VersionedTemplates = {
[rnVersion: string]: Template[];
};
type NpmTemplateResponse = {
versions: {
// Template version, semver including -rc candidates
[version: TemplateVersion]: {
scripts?: {
// Version of react-native this is built for
reactNativeVersion?: string;
// The initial implemntation used this, but moved to reactNativeVersion
version?: string;
};
};
};
time: {
created: string;
modified: string;
[version: TemplateVersion]: TimeStampString;
};
};
class Template {
version: string;
reactNativeVersion: string;
published: Date;
constructor(version: string, reactNativeVersion: string, published: string) {
this.version = version;
this.reactNativeVersion = reactNativeVersion;
this.published = new Date(published);
}
get isPreRelease() {
return this.version.includes('-rc');
}
}
const minorVersion = (version: string) => {
const v = semver.parse(version)!;
return `${v.major}.${v.minor}`;
};
export async function getTemplateVersion(
reactNativeVersion: string,
): Promise<TemplateVersion | undefined> {
const json = await fetch(
new URL('@react-native-community/template', getNpmRegistryUrl()),
).then((resp) => resp.json() as Promise<NpmTemplateResponse>);
// We are abusing which npm metadata is publicly available through the registry. Scripts
// is always captured, and we use this in the Github Action that manages our releases to
// capture the version of React Native the template is built with.
//
// Users are interested in:
// - IF there a match for React Native MAJOR.MINOR.PATCH?
// - Yes: if there are >= 2 versions, pick the one last published. This lets us release
// specific fixes for React Native versions.
// - ELSE, is there a match for React Native MINOR.PATCH?
// - Yes: if there are >= 2 versions, pick the one last published. This decouples us from
// React Native releases.
// - No: we don't have a version of the template for a version of React Native. There should
// at a minimum be at last one version cut for each MINOR.PATCH since 0.75. Before this
// the template was shipped with React Native
const rnToTemplate: VersionedTemplates = {};
for (const [templateVersion, pkg] of Object.entries(json.versions)) {
const rnVersion = pkg?.scripts?.reactNativeVersion ?? pkg?.scripts?.version;
if (rnVersion == null || !semver.valid(rnVersion)) {
// This is a very early version that doesn't have the correct metadata embedded
continue;
}
const template = new Template(
templateVersion,
rnVersion,
json.time[templateVersion],
);
const rnMinorVersion = minorVersion(rnVersion);
rnToTemplate[rnVersion] = rnToTemplate[rnVersion] ?? [];
rnToTemplate[rnVersion].push(template);
rnToTemplate[rnMinorVersion] = rnToTemplate[rnMinorVersion] ?? [];
rnToTemplate[rnMinorVersion].push(template);
}
// Make sure the last published is the first one in each version of React Native
for (const v in rnToTemplate) {
rnToTemplate[v].sort(
(a, b) => b.published.getTime() - a.published.getTime(),
);
}
if (reactNativeVersion in rnToTemplate) {
return rnToTemplate[reactNativeVersion][0].version;
}
const rnMinorVersion = minorVersion(reactNativeVersion);
if (rnMinorVersion in rnToTemplate) {
return rnToTemplate[rnMinorVersion][0].version;
}
return;
}