Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ When run, this command will retrieve the latest release matching the provided
descriptor, assign it to the project's package.json file, and automatically
perform an install.

Pass `--skip-install` to update `package.json` without installing the project's
dependencies. The selected package manager is still downloaded and verified.

### `corepack up`

Retrieve the latest available version for the current major release line of
Expand All @@ -289,6 +292,9 @@ range specified in `devEngines.packageManager.version`, or fallback to the
same major line. Should you need to upgrade to a new major, use an explicit
`corepack use {name}@latest` call (or simply `corepack use {name}`).

Pass `--skip-install` to update `package.json` without installing the project's
dependencies. The selected package manager is still downloaded and verified.

## Environment Variables

- `COREPACK_DEFAULT_TO_LATEST` can be set to `0` in order to instruct Corepack
Expand Down
5 changes: 4 additions & 1 deletion sources/commands/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ export abstract class BaseCommand extends Command<Context> {
return resolvedSpecs;
}

async setAndInstallLocalPackageManager(info: PreparedPackageManagerInfo) {
async setAndInstallLocalPackageManager(info: PreparedPackageManagerInfo, {skipInstall = false}: {skipInstall?: boolean} = {}) {
const {
previousPackageManager,
} = await specUtils.setLocalPackageManager(this.context.cwd, info);

if (skipInstall)
return 0;

const command = this.context.engine.getPackageManagerSpecFor(info.locator).commands?.use ?? null;
if (command === null)
return 0;
Expand Down
11 changes: 9 additions & 2 deletions sources/commands/Up.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Command, UsageError} from 'clipanion';
import {Command, Option, UsageError} from 'clipanion';
import semverMajor from 'semver/functions/major.js';
import semverValid from 'semver/functions/valid.js';
import semverValidRange from 'semver/ranges/valid.js';
Expand All @@ -19,6 +19,9 @@ export class UpCommand extends BaseCommand {
of the package manager used in the local project, and update the project
to use it.

With \`--skip-install\`, update the package manager without installing
the project's dependencies.

Unlike \`corepack use\` this command doesn't take a package manager name
nor a version range, as it will always select the latest available
version from the same major line. Should you need to upgrade to a new
Expand All @@ -30,6 +33,10 @@ export class UpCommand extends BaseCommand {
]],
});

skipInstall = Option.Boolean(`--skip-install`, false, {
description: `Skip installing the project's dependencies`,
});

async execute() {
const [descriptor] = await this.resolvePatternsToDescriptors({
patterns: [],
Expand All @@ -52,6 +59,6 @@ export class UpCommand extends BaseCommand {
this.context.stdout.write(`Installing ${highestVersion.name}@${highestVersion.reference} in the project...\n`);

const packageManagerInfo = await this.context.engine.ensurePackageManager(highestVersion);
await this.setAndInstallLocalPackageManager(packageManagerInfo);
await this.setAndInstallLocalPackageManager(packageManagerInfo, {skipInstall: this.skipInstall});
}
}
9 changes: 8 additions & 1 deletion sources/commands/Use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export class UseCommand extends BaseCommand {
When run, this command will retrieve the latest release matching the
provided descriptor, assign it to the project's package.json file, and
automatically perform an install.

With \`--skip-install\`, update the package manager without installing
the project's dependencies.
`,
examples: [[
`Configure the project to use the latest Yarn release`,
Expand All @@ -22,6 +25,10 @@ export class UseCommand extends BaseCommand {

pattern = Option.String();

skipInstall = Option.Boolean(`--skip-install`, false, {
description: `Skip installing the project's dependencies`,
});

async execute() {
const [descriptor] = await this.resolvePatternsToDescriptors({
patterns: [this.pattern],
Expand All @@ -34,6 +41,6 @@ export class UseCommand extends BaseCommand {
this.context.stdout.write(`Installing ${resolved.name}@${resolved.reference} in the project...\n`);

const packageManagerInfo = await this.context.engine.ensurePackageManager(resolved);
await this.setAndInstallLocalPackageManager(packageManagerInfo);
await this.setAndInstallLocalPackageManager(packageManagerInfo, {skipInstall: this.skipInstall});
}
}
74 changes: 74 additions & 0 deletions tests/SkipInstall.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {npath, ppath, xfs} from '@yarnpkg/fslib';
import {beforeEach, describe, expect, it} from 'vitest';

import {runCli} from './_runCli.ts';

beforeEach(async () => {
const home = await xfs.mktempPromise();
process.env.COREPACK_HOME = npath.fromPortablePath(home);
process.env.COREPACK_DEFAULT_TO_LATEST = `0`;
process.env.AUTH_TYPE = `COREPACK_NPM_TOKEN`;
process.env.TEST_INTEGRITY = `valid`;

return async () => {
await xfs.removePromise(home, {recursive: true});
};
});

describe.each([`use`, `up`])(`%s --skip-install`, command => {
const args = command === `use` ? [`use`, `pnpm@latest`] : [`up`];

it.each([undefined, `--skip-install`, `--no-skip-install`])(`updates the manifest with option %j`, async option => {
const options = option ? [option] : [];
await xfs.mktempPromise(async cwd => {
const manifestPath = ppath.join(cwd, `package.json`);
await xfs.writeJsonPromise(manifestPath, {packageManager: `pnpm@1.0.0`});

await expect(runCli(cwd, [...args, ...options], true)).resolves.toEqual({
exitCode: 0,
stdout: `Installing pnpm@1.9998.9999 in the project...\n${options.includes(`--skip-install`) ? `` : `\npnpm: Hello from custom registry\n`}`,
stderr: ``,
});
await expect(xfs.readJsonPromise(manifestPath)).resolves.toMatchObject({
packageManager: expect.stringMatching(/^pnpm@1\.9998\.9999\+sha512\./),
});

// The package manager itself is installed even when dependencies aren't.
process.env.COREPACK_ENABLE_NETWORK = `0`;
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toEqual({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});

it(`still verifies downloads before updating the manifest`, async () => {
process.env.TEST_INTEGRITY = `invalid_signature`;
await xfs.mktempPromise(async cwd => {
const manifestPath = ppath.join(cwd, `package.json`);
const manifest = {packageManager: `pnpm@1.0.0`};
await xfs.writeJsonPromise(manifestPath, manifest);

await expect(runCli(cwd, [...args, `--skip-install`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`),
});
await expect(xfs.readJsonPromise(manifestPath)).resolves.toEqual(manifest);
});
});
});

it(`use --skip-install still enforces devEngines constraints`, async () => {
await xfs.mktempPromise(async cwd => {
const manifestPath = ppath.join(cwd, `package.json`);
const manifest = {devEngines: {packageManager: {name: `pnpm`, version: `2.x`}}};
await xfs.writeJsonPromise(manifestPath, manifest);

await expect(runCli(cwd, [`use`, `pnpm@latest`, `--skip-install`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: expect.stringContaining(`does not match the devEngines specification (pnpm@2.x)`),
});
await expect(xfs.readJsonPromise(manifestPath)).resolves.toEqual(manifest);
});
});
2 changes: 1 addition & 1 deletion tests/Use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe(`UseCommand`, () => {
await expect(runCli(cwd, [`use`, `yarn@1.22.4`])).resolves.toMatchObject({
exitCode: 1,
stderr: ``,
stdout: `Installing yarn@1.22.4 in the project...\nUsage Error: The requested version of yarn@1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18 does not match the devEngines specification (yarn@2.x)\n\n$ corepack use <pattern>\n`,
stdout: `Installing yarn@1.22.4 in the project...\nUsage Error: The requested version of yarn@1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18 does not match the devEngines specification (yarn@2.x)\n\n$ corepack use [--skip-install] <pattern>\n`,
});

// Should accept setting to a compatible version:
Expand Down