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
8 changes: 6 additions & 2 deletions sources/commands/InstallLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export class InstallLocalCommand extends BaseCommand {
if (resolved === null)
throw new UsageError(`Failed to successfully resolve '${descriptor.range}' to a valid ${descriptor.name} release`);

this.context.stdout.write(`Adding ${resolved.name}@${resolved.reference} to the cache...\n`);
await this.context.engine.ensurePackageManager(resolved);
const info = await this.context.engine.ensurePackageManager(resolved);
if (info.isAlreadyInstalled) {
this.context.stdout.write(`${info.locator.name}@${info.locator.reference} is already in the cache\n`);
} else {
this.context.stdout.write(`Adding ${resolved.name}@${resolved.reference} to the cache...\n`);
}
}
}
2 changes: 2 additions & 0 deletions sources/corepackUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export async function installVersion(installTarget: string, locator: Locator, {s
hash: corepackData.hash as string,
location: installFolder,
bin: corepackData.bin,
isAlreadyInstalled: true,
};
} catch (err) {
if (nodeUtils.isNodeError(err) && err.code !== `ENOENT`) {
Expand Down Expand Up @@ -374,6 +375,7 @@ export async function installVersion(installTarget: string, locator: Locator, {s
location: installFolder,
bin,
hash: serializedHash,
isAlreadyInstalled: false,
};
}

Expand Down
2 changes: 2 additions & 0 deletions sources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export interface InstallSpec {
location: string;
bin?: BinList | BinSpec;
hash: string;
/** Whether the package manager was reused from the cache without downloading it. */
isAlreadyInstalled: boolean;
}

export interface DownloadSpec {
Expand Down
120 changes: 120 additions & 0 deletions tests/InstallLocal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import {ppath, xfs, npath} from '@yarnpkg/fslib';
import process from 'node:process';
import {describe, beforeEach, it, expect} 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`;

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

describe(`InstallLocalCommand`, () => {
it.each([
[`1.22.4`, `1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18`],
[`1.22.4+sha224.0d6eecaf4d82ec12566fdd97143794d0f0c317e0d652bd4d1b305430`, `1.22.4+sha224.0d6eecaf4d82ec12566fdd97143794d0f0c317e0d652bd4d1b305430`],
])(`should report the cached version and integrity hash on repeated installs (%s)`, async (version, cachedReference) => {
await xfs.mktempPromise(async cwd => {
const manifestPath = ppath.join(cwd, `package.json`);
await xfs.writeJsonPromise(manifestPath, {packageManager: `yarn@${version}`});
const manifest = await xfs.readFilePromise(manifestPath, `utf8`);

await expect(runCli(cwd, [`install`])).resolves.toEqual({
exitCode: 0,
stdout: `Adding yarn@${version} to the cache...\n`,
stderr: ``,
});

process.env.COREPACK_ENABLE_NETWORK = `0`;
await expect(runCli(cwd, [`install`])).resolves.toEqual({
exitCode: 0,
stdout: `yarn@${cachedReference} is already in the cache\n`,
stderr: ``,
});
await expect(xfs.readFilePromise(manifestPath, `utf8`)).resolves.toBe(manifest);
expect(await xfs.existsPromise(ppath.join(npath.toPortablePath(process.env.COREPACK_HOME!), `lastKnownGood.json`))).toBe(false);
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toEqual({
exitCode: 0,
stdout: `1.22.4\n`,
stderr: ``,
});
});
});

it(`should include the stored hash when an exact devEngines version is already cached`, async () => {
await xfs.mktempPromise(async cwd => {
const version = `1.22.4+sha224.0d6eecaf4d82ec12566fdd97143794d0f0c317e0d652bd4d1b305430`;
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
devEngines: {packageManager: {name: `yarn`, version}},
});

await expect(runCli(cwd, [`install`])).resolves.toEqual({
exitCode: 0,
stdout: `Adding yarn@${version} to the cache...\n`,
stderr: ``,
});
process.env.COREPACK_ENABLE_NETWORK = `0`;
await expect(runCli(cwd, [`install`])).resolves.toEqual({
exitCode: 0,
stdout: `yarn@${version} is already in the cache\n`,
stderr: ``,
});
});
});

it(`should report cache reuse after resolving a devEngines range offline`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {packageManager: `[email protected]`});
await expect(runCli(cwd, [`install`])).resolves.toEqual({
exitCode: 0,
stdout: `Adding [email protected] to the cache...\n`,
stderr: ``,
});
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
devEngines: {packageManager: {name: `yarn`, version: `^1.0.0`}},
});
process.env.COREPACK_ENABLE_NETWORK = `0`;

await expect(runCli(cwd, [`install`])).resolves.toEqual({
exitCode: 0,
stdout: `[email protected]+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18 is already in the cache\n`,
stderr: ``,
});
});
});

it(`should report installation again after clearing the cache`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {packageManager: `[email protected]`});
const expected = {exitCode: 0, stdout: `Adding [email protected] to the cache...\n`, stderr: ``};

await expect(runCli(cwd, [`install`])).resolves.toEqual(expected);
await expect(runCli(cwd, [`cache`, `clean`])).resolves.toEqual({exitCode: 0, stdout: ``, stderr: ``});
await expect(runCli(cwd, [`install`])).resolves.toEqual(expected);
});
});

it(`should not report a cached version when the download fails integrity verification`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {packageManager: `[email protected]+sha1.deadbeef`});

// Repeat the failed install to verify that the invalid download was not cached.
for (let attempt = 0; attempt < 2; attempt++) {
const result = await runCli(cwd, [`install`]);
expect(result).toEqual({
exitCode: 1,
stdout: expect.stringContaining(`Internal Error: Mismatch hashes. Expected deadbeef, got`),
stderr: ``,
});
expect(result.stdout).not.toContain(`is already in the cache`);
expect(result.stdout).not.toContain(`Adding yarn@`);
}
});
});
});