Skip to content

Commit

Permalink
fix: selectively import required semver functions (#511)
Browse files Browse the repository at this point in the history
By selectively importing only the functions and classes we use, we avoid bundling in the entire semver package, and shave a few kB off from the Corepack bundle.
  • Loading branch information
wojtekmaj authored Jul 12, 2024
1 parent 004c028 commit e7ad533
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 24 deletions.
4 changes: 2 additions & 2 deletions genlist.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {readFileSync} from 'fs';
import semver from 'semver';
import semverCompare from 'semver/functions/compare';

const lines = readFileSync(0, `utf8`).split(/\n/).filter(line => line);

lines.sort((a, b) => {
return semver.compare(a, b);
return semverCompare(a, b);
});

for (const version of lines)
Expand Down
10 changes: 6 additions & 4 deletions sources/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import {UsageError} from 'clipanion';
import fs from 'fs';
import path from 'path';
import process from 'process';
import semver from 'semver';
import semverRcompare from 'semver/functions/rcompare';
import semverValid from 'semver/functions/valid';
import semverValidRange from 'semver/ranges/valid';

import defaultConfig from '../config.json';

Expand Down Expand Up @@ -334,7 +336,7 @@ export class Engine {
throw new UsageError(`This package manager (${descriptor.name}) isn't supported by this corepack build`);

let finalDescriptor = descriptor;
if (!semver.valid(descriptor.range) && !semver.validRange(descriptor.range)) {
if (!semverValid(descriptor.range) && !semverValidRange(descriptor.range)) {
if (!allowTags)
throw new UsageError(`Packages managers can't be referenced via tags in this context`);

Expand Down Expand Up @@ -363,7 +365,7 @@ export class Engine {

// If the user asked for a specific version, no need to request the list of
// available versions from the registry.
if (semver.valid(finalDescriptor.range))
if (semverValid(finalDescriptor.range))
return {name: finalDescriptor.name, reference: finalDescriptor.range};

const versions = await Promise.all(Object.keys(definition.ranges).map(async range => {
Expand All @@ -374,7 +376,7 @@ export class Engine {
return versions.filter(version => semverUtils.satisfiesWithPrereleases(version, finalDescriptor.range));
}));

const highestVersion = [...new Set(versions.flat())].sort(semver.rcompare);
const highestVersion = [...new Set(versions.flat())].sort(semverRcompare);
if (highestVersion.length === 0)
return null;

Expand Down
8 changes: 5 additions & 3 deletions sources/commands/Up.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {Command, UsageError} from 'clipanion';
import semver from 'semver';
import semverMajor from 'semver/functions/major';
import semverValid from 'semver/functions/valid';
import semverValidRange from 'semver/ranges/valid';

import type {SupportedPackageManagers} from '../types';

Expand Down Expand Up @@ -33,14 +35,14 @@ export class UpCommand extends BaseCommand {
patterns: [],
});

if (!semver.valid(descriptor.range) && !semver.validRange(descriptor.range))
if (!semverValid(descriptor.range) && !semverValidRange(descriptor.range))
throw new UsageError(`The 'corepack up' command can only be used when your project's packageManager field is set to a semver version or semver range`);

const resolved = await this.context.engine.resolveDescriptor(descriptor, {useCache: false});
if (!resolved)
throw new UsageError(`Failed to successfully resolve '${descriptor.range}' to a valid ${descriptor.name} release`);

const majorVersion = semver.major(resolved.reference);
const majorVersion = semverMajor(resolved.reference);
const majorDescriptor = {name: descriptor.name as SupportedPackageManagers, range: `^${majorVersion}.0.0`};

const highestVersion = await this.context.engine.resolveDescriptor(majorDescriptor, {useCache: false});
Expand Down
21 changes: 12 additions & 9 deletions sources/corepackUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import fs from 'fs';
import type {Dir} from 'fs';
import Module from 'module';
import path from 'path';
import semver from 'semver';
import Range from 'semver/classes/range';
import SemVer from 'semver/classes/semver';
import semverLt from 'semver/functions/lt';
import semverParse from 'semver/functions/parse';
import {setTimeout as setTimeoutPromise} from 'timers/promises';

import * as engine from './Engine';
Expand Down Expand Up @@ -82,9 +85,9 @@ export async function findInstalledVersion(installTarget: string, descriptor: De
}
}

const range = new semver.Range(descriptor.range);
const range = new Range(descriptor.range);
let bestMatch: string | null = null;
let maxSV: semver.SemVer | undefined = undefined;
let maxSV: SemVer | undefined = undefined;

for await (const {name} of cacheDirectory) {
// Some dot-folders tend to pop inside directories, especially on OSX
Expand All @@ -97,7 +100,7 @@ export async function findInstalledVersion(installTarget: string, descriptor: De
// @ts-expect-error TODO: decipher why this produces an error
if (range.test(name) && maxSV?.compare(name) !== 1) {
bestMatch = name;
maxSV = new semver.SemVer(bestMatch);
maxSV = new SemVer(bestMatch);
}
}

Expand Down Expand Up @@ -193,7 +196,7 @@ async function download(installTarget: string, url: string, algo: string, binPat

export async function installVersion(installTarget: string, locator: Locator, {spec}: {spec: PackageManagerSpec}): Promise<InstallSpec> {
const locatorIsASupportedPackageManager = isSupportedPackageManagerLocator(locator);
const locatorReference = locatorIsASupportedPackageManager ? semver.parse(locator.reference)! : parseURLReference(locator);
const locatorReference = locatorIsASupportedPackageManager ? semverParse(locator.reference)! : parseURLReference(locator);
const {version, build} = locatorReference;

const installFolder = path.join(installTarget, locator.name, version);
Expand Down Expand Up @@ -326,9 +329,9 @@ export async function installVersion(installTarget: string, locator: Locator, {s
const lastKnownGood = await engine.getLastKnownGood();
const defaultVersion = engine.getLastKnownGoodFromFileContent(lastKnownGood, locator.name);
if (defaultVersion) {
const currentDefault = semver.parse(defaultVersion)!;
const downloadedVersion = locatorReference as semver.SemVer;
if (currentDefault.major === downloadedVersion.major && semver.lt(currentDefault, downloadedVersion)) {
const currentDefault = semverParse(defaultVersion)!;
const downloadedVersion = locatorReference as SemVer;
if (currentDefault.major === downloadedVersion.major && semverLt(currentDefault, downloadedVersion)) {
await engine.activatePackageManager(lastKnownGood, locator);
}
}
Expand Down Expand Up @@ -404,7 +407,7 @@ export async function runVersion(locator: Locator, installSpec: InstallSpec & {s
// Node.js segfaults when using npm@>=9.7.0 and v8-compile-cache
// $ docker run -it node:20.3.0-slim corepack npm@9.7.1 --version
// [SIGSEGV]
if (locator.name !== `npm` || semver.lt(locator.reference, `9.7.0`))
if (locator.name !== `npm` || semverLt(locator.reference, `9.7.0`))
// @ts-expect-error - No types
await import(`v8-compile-cache`);

Expand Down
9 changes: 5 additions & 4 deletions sources/semverUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import semver from 'semver';
import Range from 'semver/classes/range';
import SemVer from 'semver/classes/semver';

/**
* Returns whether the given semver version satisfies the given range. Notably
Expand All @@ -16,17 +17,17 @@ import semver from 'semver';
export function satisfiesWithPrereleases(version: string | null, range: string, loose: boolean = false): boolean {
let semverRange;
try {
semverRange = new semver.Range(range, loose);
semverRange = new Range(range, loose);
} catch (err) {
return false;
}

if (!version)
return false;

let semverVersion: semver.SemVer;
let semverVersion: SemVer;
try {
semverVersion = new semver.SemVer(version, semverRange.loose);
semverVersion = new SemVer(version, semverRange.loose);
if (semverVersion.prerelease) {
semverVersion.prerelease = [];
}
Expand Down
4 changes: 2 additions & 2 deletions sources/specUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {UsageError} from 'clipanion';
import fs from 'fs';
import path from 'path';
import semver from 'semver';
import semverValid from 'semver/functions/valid';

import {PreparedPackageManagerInfo} from './Engine';
import {NodeError} from './nodeUtils';
Expand Down Expand Up @@ -34,7 +34,7 @@ export function parseSpec(raw: unknown, source: string, {enforceExactVersion = t

const isURL = URL.canParse(range);
if (!isURL) {
if (enforceExactVersion && !semver.valid(range))
if (enforceExactVersion && !semverValid(range))
throw new UsageError(`Invalid package manager specification in ${source} (${raw}); expected a semver version${enforceExactVersion ? `` : `, range, or tag`}`);

if (!isSupportedPackageManager(name)) {
Expand Down

0 comments on commit e7ad533

Please sign in to comment.