socket-function 1.2.35 → 1.2.37
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.
- package/package.json +1 -1
- package/src/dnsCache.ts +6 -4
- package/src/upreal.ts +274 -69
package/package.json
CHANGED
package/src/dnsCache.ts
CHANGED
|
@@ -158,19 +158,21 @@ export async function resolveHost(hostname: string, family = 0): Promise<DNSReco
|
|
|
158
158
|
// the socket use our cache instead of getaddrinfo. Always resolves via our cache; happy eyeballs
|
|
159
159
|
// (autoSelectFamily) then races the returned addresses.
|
|
160
160
|
export const dnsCacheLookup: net.LookupFunction = function dnsCacheLookup(hostname, options, callback) {
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
// Node's own callers pass the family as a number, but the option is documented as accepting "IPv4"/"IPv6"
|
|
162
|
+
// too, so handle both even though the typings only admit the number.
|
|
163
|
+
let rawFamily: number | string = typeof options === "number" ? options : options.family ?? 0;
|
|
164
|
+
let family = rawFamily === "IPv4" ? 4 : rawFamily === "IPv6" ? 6 : Number(rawFamily) || 0;
|
|
163
165
|
let all = typeof options === "object" && options.all;
|
|
164
166
|
resolveHost(hostname, family).then(
|
|
165
167
|
records => {
|
|
166
168
|
if (all) {
|
|
167
|
-
callback(null, records.map(r => ({ address: r.address, family: r.family })));
|
|
169
|
+
callback(null, records.map(r => ({ address: r.address, family: r.family })), family);
|
|
168
170
|
} else {
|
|
169
171
|
let record = records[0];
|
|
170
172
|
callback(null, record.address, record.family);
|
|
171
173
|
}
|
|
172
174
|
},
|
|
173
|
-
(err: NodeJS.ErrnoException) => callback(err, "",
|
|
175
|
+
(err: NodeJS.ErrnoException) => callback(err, "", family)
|
|
174
176
|
);
|
|
175
177
|
};
|
|
176
178
|
|
package/src/upreal.ts
CHANGED
|
@@ -11,10 +11,23 @@ const DEP_SECTIONS = [
|
|
|
11
11
|
"peerDependencies",
|
|
12
12
|
] as const;
|
|
13
13
|
|
|
14
|
+
// Yarn itself spells this "--dry-run", so accept that plus every spelling anyone reasonably reaches for.
|
|
15
|
+
const DRY_RUN_FLAGS = ["--dry", "-dry", "--dryrun", "-dryrun", "--dry-run", "-dry-run", "--dry_run", "-dry_run"];
|
|
16
|
+
|
|
17
|
+
// yarn install can still decide to re-resolve and re-split; re-running the merge converges in that case.
|
|
18
|
+
const MAX_MERGE_PASSES = 3;
|
|
19
|
+
|
|
20
|
+
// yarn v1 rewrites npm registry urls to its own mirror, so entries we synthesize have to match or yarn
|
|
21
|
+
// churns the lockfile back on the next install.
|
|
22
|
+
const NPM_REGISTRY_HOST = "registry.npmjs.org";
|
|
23
|
+
const YARN_REGISTRY_HOST = "registry.yarnpkg.com";
|
|
24
|
+
|
|
14
25
|
async function main() {
|
|
15
|
-
let
|
|
26
|
+
let args = process.argv.slice(2);
|
|
27
|
+
let packageName = args.find(arg => !arg.startsWith("-"));
|
|
28
|
+
let dryRun = args.some(arg => DRY_RUN_FLAGS.includes(arg.toLowerCase()));
|
|
16
29
|
if (!packageName) {
|
|
17
|
-
console.error("Usage: yarn upreal <package-name>");
|
|
30
|
+
console.error("Usage: yarn upreal <package-name> [--dry-run]");
|
|
18
31
|
process.exit(1);
|
|
19
32
|
return;
|
|
20
33
|
}
|
|
@@ -23,78 +36,100 @@ async function main() {
|
|
|
23
36
|
let packageJsonPath = path.join(projectRoot, "package.json");
|
|
24
37
|
let lockPath = path.join(projectRoot, "yarn.lock");
|
|
25
38
|
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
[section: string]: { [name: string]: string } | undefined;
|
|
29
|
-
};
|
|
39
|
+
let manifest = await getLatestManifest(packageName);
|
|
40
|
+
console.log(`Latest version of ${packageName}: ${manifest.version}`);
|
|
30
41
|
|
|
31
|
-
let
|
|
32
|
-
if (currentRange === undefined) {
|
|
33
|
-
console.error(`Package "${packageName}" is not listed in ${DEP_SECTIONS.join(", ")} of package.json`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
42
|
+
let newRange = updatePackageJson(packageJsonPath, packageName, manifest.version);
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
for (let pass = 1; pass <= MAX_MERGE_PASSES; pass++) {
|
|
45
|
+
if (fs.existsSync(lockPath)) {
|
|
46
|
+
let result = mergeLockEntries(lockPath, packageName, manifest, newRange);
|
|
47
|
+
console.log(`Lockfile pass ${pass}: ${result.specs.length} spec${result.specs.length === 1 ? "" : "s"} for ${packageName} now share one entry at ${manifest.version}: ${result.specs.join(", ")}`);
|
|
48
|
+
for (let moved of result.movedFrom) {
|
|
49
|
+
console.log(` ${moved.spec} was resolving to ${moved.version}`);
|
|
50
|
+
}
|
|
51
|
+
for (let left of result.leftAlone) {
|
|
52
|
+
console.log(` left ${left} alone (not a registry range, so it can't point at a version)`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
console.log(`Dry run: package.json and yarn.lock are rewritten, skipping yarn install.`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
42
60
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log(`package.json range unchanged (${currentRange}); will still re-resolve the lockfile.`);
|
|
46
|
-
} else {
|
|
47
|
-
packageJsonRaw = replaceRange(packageJsonRaw, packageName, currentRange, newRange);
|
|
48
|
-
fs.writeFileSync(packageJsonPath, packageJsonRaw);
|
|
49
|
-
console.log(`Updated package.json: ${packageName} ${currentRange} -> ${newRange}`);
|
|
50
|
-
}
|
|
61
|
+
console.log(`Running yarn install...`);
|
|
62
|
+
await runYarnInstall(projectRoot);
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let lockRaw = fs.readFileSync(lockPath, "utf8");
|
|
57
|
-
let { lock, removed } = removeLockEntries(lockRaw, packageName);
|
|
58
|
-
if (removed > 0) {
|
|
59
|
-
fs.writeFileSync(lockPath, lock);
|
|
60
|
-
console.log(`Removed ${removed} lockfile entr${removed === 1 ? "y" : "ies"} for ${packageName} so they re-resolve.`);
|
|
64
|
+
let versions = getLockedVersions(lockPath, packageName);
|
|
65
|
+
if (versions.length <= 1) {
|
|
66
|
+
console.log(`Done. ${packageName} resolves to a single version: ${versions[0] ?? manifest.version}`);
|
|
67
|
+
return;
|
|
61
68
|
}
|
|
69
|
+
console.log(`${packageName} still resolves to ${versions.length} versions (${versions.join(", ")}); merging again.`);
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
let versions = getLockedVersions(lockPath, packageName);
|
|
73
|
+
console.error(`Failed to collapse ${packageName} onto one version after ${MAX_MERGE_PASSES} passes; still at ${versions.join(", ")}`);
|
|
74
|
+
process.exit(1);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
// Point every package.json section that mentions the package at the latest version, keeping each section's
|
|
78
|
+
// existing range operator, and return the range the lockfile entry has to satisfy.
|
|
79
|
+
function updatePackageJson(packageJsonPath: string, packageName: string, latest: string): string {
|
|
80
|
+
let raw = fs.readFileSync(packageJsonPath, "utf8");
|
|
81
|
+
let packageJson = JSON.parse(raw) as {
|
|
82
|
+
[section: string]: { [name: string]: string } | undefined;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let newRange: string | undefined;
|
|
73
86
|
for (let section of DEP_SECTIONS) {
|
|
74
87
|
let deps = packageJson[section];
|
|
75
|
-
if (deps
|
|
76
|
-
|
|
88
|
+
if (!deps || !(packageName in deps)) {
|
|
89
|
+
continue;
|
|
77
90
|
}
|
|
91
|
+
let currentRange = deps[packageName];
|
|
92
|
+
let sectionRange = applyPrefix(currentRange, latest);
|
|
93
|
+
newRange = newRange ?? sectionRange;
|
|
94
|
+
if (sectionRange === currentRange) {
|
|
95
|
+
console.log(`package.json ${section}.${packageName} already ${currentRange}`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
raw = replaceRange(raw, packageName, currentRange, sectionRange);
|
|
99
|
+
fs.writeFileSync(packageJsonPath, raw);
|
|
100
|
+
console.log(`Updated package.json ${section}.${packageName}: ${currentRange} -> ${sectionRange}`);
|
|
78
101
|
}
|
|
79
|
-
|
|
102
|
+
|
|
103
|
+
if (newRange === undefined) {
|
|
104
|
+
console.log(`${packageName} is not a direct dependency; only merging the transitive references.`);
|
|
105
|
+
return "^" + latest;
|
|
106
|
+
}
|
|
107
|
+
return newRange;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface Manifest {
|
|
111
|
+
version: string;
|
|
112
|
+
dist?: { tarball?: string; shasum?: string; integrity?: string };
|
|
113
|
+
dependencies?: { [name: string]: string };
|
|
114
|
+
optionalDependencies?: { [name: string]: string };
|
|
80
115
|
}
|
|
81
116
|
|
|
82
|
-
async function
|
|
117
|
+
async function getLatestManifest(packageName: string): Promise<Manifest> {
|
|
83
118
|
// Scoped names ("@scope/name") must have their slash encoded in the registry path.
|
|
84
119
|
let encodedName = packageName.startsWith("@")
|
|
85
120
|
? "@" + encodeURIComponent(packageName.slice(1))
|
|
86
121
|
: encodeURIComponent(packageName);
|
|
87
|
-
let url = `https
|
|
122
|
+
let url = `https://${NPM_REGISTRY_HOST}/${encodedName}/latest`;
|
|
88
123
|
|
|
89
124
|
// Via httpsRequest so registry lookups go through the DNS cache (and its re-resolve/retry), rather
|
|
90
125
|
// than getaddrinfo, which caches certain failures forever.
|
|
91
126
|
let body = (await httpsRequest(url)).toString("utf8");
|
|
92
127
|
|
|
93
|
-
let parsed = JSON.parse(body) as
|
|
128
|
+
let parsed = JSON.parse(body) as Manifest;
|
|
94
129
|
if (!parsed.version) {
|
|
95
130
|
throw new Error(`Registry response for ${packageName} had no version field`);
|
|
96
131
|
}
|
|
97
|
-
return parsed
|
|
132
|
+
return parsed;
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
// Keep whatever range operator the user already chose (^, ~, exact, or *) and point it at the new version.
|
|
@@ -120,37 +155,50 @@ function replaceRange(packageJsonRaw: string, packageName: string, oldRange: str
|
|
|
120
155
|
return packageJsonRaw.replace(keyPattern, `$1${newRange}$2`);
|
|
121
156
|
}
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
158
|
+
interface LockBlock {
|
|
159
|
+
// Index of the header line, and the exclusive end of the block (trailing blank lines included).
|
|
160
|
+
start: number;
|
|
161
|
+
end: number;
|
|
162
|
+
specs: string[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface LockFile {
|
|
166
|
+
lines: string[];
|
|
167
|
+
eol: string;
|
|
168
|
+
blocks: LockBlock[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readLock(lockPath: string): LockFile {
|
|
172
|
+
let raw = fs.readFileSync(lockPath, "utf8");
|
|
173
|
+
let eol = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
174
|
+
let lines = raw.split(/\r?\n/);
|
|
175
|
+
|
|
176
|
+
let blocks: LockBlock[] = [];
|
|
177
|
+
let current: LockBlock | undefined;
|
|
178
|
+
for (let i = 0; i < lines.length; i++) {
|
|
179
|
+
if (!isBlockHeader(lines[i])) {
|
|
133
180
|
continue;
|
|
134
181
|
}
|
|
135
|
-
|
|
182
|
+
if (current) {
|
|
183
|
+
current.end = i;
|
|
184
|
+
}
|
|
185
|
+
current = { start: i, end: lines.length, specs: parseHeaderSpecs(lines[i]) };
|
|
186
|
+
blocks.push(current);
|
|
136
187
|
}
|
|
137
|
-
return {
|
|
188
|
+
return { lines, eol, blocks };
|
|
138
189
|
}
|
|
139
190
|
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
if (!headerLine || !headerLine.trimEnd().endsWith(":")) {
|
|
191
|
+
function isBlockHeader(line: string): boolean {
|
|
192
|
+
if (!line || line.startsWith("#") || /^\s/.test(line)) {
|
|
143
193
|
return false;
|
|
144
194
|
}
|
|
145
|
-
|
|
195
|
+
return line.trimEnd().endsWith(":");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function parseHeaderSpecs(headerLine: string): string[] {
|
|
199
|
+
// e.g. `"pkg@^1.0.0", "pkg@~1.2.0":` -> ["pkg@^1.0.0", "pkg@~1.2.0"]
|
|
146
200
|
let specsPart = headerLine.trimEnd().replace(/:$/, "");
|
|
147
|
-
|
|
148
|
-
for (let spec of specs) {
|
|
149
|
-
if (specNamePackage(spec) === packageName) {
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return false;
|
|
201
|
+
return specsPart.split(",").map(spec => spec.trim().replace(/^"|"$/g, ""));
|
|
154
202
|
}
|
|
155
203
|
|
|
156
204
|
// Extract the package name from a lock spec like `@scope/name@^1.0.0` or `name@~1.2.0`.
|
|
@@ -163,6 +211,163 @@ function specNamePackage(spec: string): string {
|
|
|
163
211
|
return spec.slice(0, atIndex);
|
|
164
212
|
}
|
|
165
213
|
|
|
214
|
+
function specRange(spec: string): string {
|
|
215
|
+
return spec.slice(specNamePackage(spec).length + 1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Ranges that don't come from the registry (git urls, file:, link:, npm: aliases) can't be pointed at a
|
|
219
|
+
// version number, so their entry has to stay separate.
|
|
220
|
+
function isRegistryRange(range: string): boolean {
|
|
221
|
+
return !range.includes(":") && !range.includes("/");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// yarn.lock quotes a token only when it would otherwise be ambiguous, and rewriting a token with different
|
|
225
|
+
// quoting than yarn would use makes every later install churn the file.
|
|
226
|
+
function quoteToken(value: string): string {
|
|
227
|
+
let needsQuotes = /[:\s\n\\",[\]]/.test(value) || !/^[a-zA-Z]/.test(value);
|
|
228
|
+
return needsQuotes ? `"${value}"` : value;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function formatHeader(specs: string[]): string {
|
|
232
|
+
return specs.map(quoteToken).join(", ") + ":";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Put every registry spec for the package onto ONE entry resolving to the latest version. Nothing is deleted:
|
|
236
|
+
// a spec that asked for an old version (`pkg@1.2.3`) stays in the header and now points at the new version,
|
|
237
|
+
// which is what stops yarn from re-creating a second entry the next time something re-resolves.
|
|
238
|
+
function mergeLockEntries(lockPath: string, packageName: string, manifest: Manifest, newRange: string): {
|
|
239
|
+
specs: string[];
|
|
240
|
+
movedFrom: { spec: string; version: string }[];
|
|
241
|
+
leftAlone: string[];
|
|
242
|
+
} {
|
|
243
|
+
let lock = readLock(lockPath);
|
|
244
|
+
let specs = new Set<string>([`${packageName}@${newRange}`]);
|
|
245
|
+
let movedFrom: { spec: string; version: string }[] = [];
|
|
246
|
+
let leftAlone: string[] = [];
|
|
247
|
+
let removeLines = new Set<number>();
|
|
248
|
+
let insertAt: number | undefined;
|
|
249
|
+
let body: string[] | undefined;
|
|
250
|
+
|
|
251
|
+
for (let block of lock.blocks) {
|
|
252
|
+
if (!block.specs.some(spec => specNamePackage(spec) === packageName)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
let mergeable = block.specs.filter(spec => specNamePackage(spec) === packageName && isRegistryRange(specRange(spec)));
|
|
256
|
+
let keep = block.specs.filter(spec => !mergeable.includes(spec));
|
|
257
|
+
if (mergeable.length === 0) {
|
|
258
|
+
leftAlone.push(...block.specs);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let version = getBlockVersion(lock, block);
|
|
263
|
+
for (let spec of mergeable) {
|
|
264
|
+
if (!specs.has(spec) && version !== manifest.version) {
|
|
265
|
+
movedFrom.push({ spec, version: version ?? "?" });
|
|
266
|
+
}
|
|
267
|
+
specs.add(spec);
|
|
268
|
+
}
|
|
269
|
+
// Reuse the real entry when the lockfile already holds the latest version, so we keep yarn's own
|
|
270
|
+
// resolved url and integrity hash rather than synthesizing them.
|
|
271
|
+
if (version === manifest.version && !body) {
|
|
272
|
+
body = lock.lines.slice(block.start + 1, blockBodyEnd(lock, block));
|
|
273
|
+
}
|
|
274
|
+
if (keep.length > 0) {
|
|
275
|
+
lock.lines[block.start] = formatHeader(keep);
|
|
276
|
+
leftAlone.push(...keep);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
insertAt = insertAt ?? block.start;
|
|
280
|
+
for (let i = block.start; i < block.end; i++) {
|
|
281
|
+
removeLines.add(i);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let merged = [formatHeader(Array.from(specs).sort()), ...(body ?? synthesizeBody(manifest)), ""];
|
|
286
|
+
|
|
287
|
+
let output: string[] = [];
|
|
288
|
+
for (let i = 0; i < lock.lines.length; i++) {
|
|
289
|
+
if (i === insertAt) {
|
|
290
|
+
output.push(...merged);
|
|
291
|
+
}
|
|
292
|
+
if (removeLines.has(i)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
output.push(lock.lines[i]);
|
|
296
|
+
}
|
|
297
|
+
if (insertAt === undefined) {
|
|
298
|
+
// The package wasn't in the lockfile at all; append and let yarn sort it.
|
|
299
|
+
output.push(...merged);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fs.writeFileSync(lockPath, output.join(lock.eol));
|
|
303
|
+
return { specs: Array.from(specs).sort(), movedFrom, leftAlone };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The lines a block owns, excluding the blank line(s) separating it from the next block.
|
|
307
|
+
function blockBodyEnd(lock: LockFile, block: LockBlock): number {
|
|
308
|
+
let end = block.end;
|
|
309
|
+
while (end > block.start + 1 && lock.lines[end - 1].trim() === "") {
|
|
310
|
+
end--;
|
|
311
|
+
}
|
|
312
|
+
return end;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function getBlockVersion(lock: LockFile, block: LockBlock): string | undefined {
|
|
316
|
+
for (let i = block.start + 1; i < block.end; i++) {
|
|
317
|
+
let match = /^\s+version "(.*)"$/.exec(lock.lines[i]);
|
|
318
|
+
if (match) {
|
|
319
|
+
return match[1];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Build a lockfile entry for a version the lockfile has never seen, from the registry's own metadata.
|
|
326
|
+
function synthesizeBody(manifest: Manifest): string[] {
|
|
327
|
+
let tarball = manifest.dist?.tarball;
|
|
328
|
+
if (!tarball) {
|
|
329
|
+
throw new Error(`Registry response for ${manifest.version} had no dist.tarball, so no lockfile entry can be written`);
|
|
330
|
+
}
|
|
331
|
+
let resolved = tarball.replace(NPM_REGISTRY_HOST, YARN_REGISTRY_HOST);
|
|
332
|
+
if (manifest.dist?.shasum) {
|
|
333
|
+
resolved += "#" + manifest.dist.shasum;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let lines = [` version ${quoteToken(manifest.version)}`, ` resolved ${quoteToken(resolved)}`];
|
|
337
|
+
if (manifest.dist?.integrity) {
|
|
338
|
+
lines.push(` integrity ${quoteToken(manifest.dist.integrity)}`);
|
|
339
|
+
}
|
|
340
|
+
for (let section of ["dependencies", "optionalDependencies"] as const) {
|
|
341
|
+
let deps = manifest[section];
|
|
342
|
+
if (!deps || Object.keys(deps).length === 0) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
lines.push(` ${section}:`);
|
|
346
|
+
for (let name of Object.keys(deps).sort()) {
|
|
347
|
+
lines.push(` ${quoteToken(name)} ${quoteToken(deps[name])}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return lines;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function getLockedVersions(lockPath: string, packageName: string): string[] {
|
|
354
|
+
if (!fs.existsSync(lockPath)) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
let lock = readLock(lockPath);
|
|
358
|
+
let versions = new Set<string>();
|
|
359
|
+
for (let block of lock.blocks) {
|
|
360
|
+
if (!block.specs.some(spec => specNamePackage(spec) === packageName)) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
let version = getBlockVersion(lock, block);
|
|
364
|
+
if (version) {
|
|
365
|
+
versions.add(version);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return Array.from(versions);
|
|
369
|
+
}
|
|
370
|
+
|
|
166
371
|
function escapeRegExp(value: string): string {
|
|
167
372
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
168
373
|
}
|