Skip to content

Commit ebfb96a

Browse files
authored
Adds legacy plugin removal behavior (#5446)
**What's the problem this PR addresses?** When using `yarn set version` with Yarn 4, the core plugins that were previously externals now conflict with the builtin ones, causing surprising errors. Fixes #4952 **How did you fix it?** Yarn will now ignore the specified list of plugins when booting. It'll then automatically remove them during the next install. **Checklist** <!--- Don't worry if you miss something, chores are automatically tested. --> <!--- This checklist exists to help you remember doing the chores when you submit a PR. --> <!--- Put an `x` in all the boxes that apply. --> - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). <!-- See https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released for more details. --> <!-- Check with `yarn version check` and fix with `yarn version check -i` --> - [x] I have set the packages that need to be released for my changes to be effective. <!-- The "Testing chores" workflow validates that your PR follows our guidelines. --> <!-- If it doesn't pass, click on it to see details as to what your PR might be missing. --> - [x] I will check that all automated PR checks pass before the PR gets reviewed.
1 parent 063be09 commit ebfb96a

File tree

7 files changed

+189
-13
lines changed

7 files changed

+189
-13
lines changed

.yarn/versions/35f2cce0.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
releases:
2+
"@yarnpkg/cli": major
3+
"@yarnpkg/core": major
4+
"@yarnpkg/plugin-essentials": major
5+
6+
declined:
7+
- "@yarnpkg/plugin-compat"
8+
- "@yarnpkg/plugin-constraints"
9+
- "@yarnpkg/plugin-dlx"
10+
- "@yarnpkg/plugin-exec"
11+
- "@yarnpkg/plugin-file"
12+
- "@yarnpkg/plugin-git"
13+
- "@yarnpkg/plugin-github"
14+
- "@yarnpkg/plugin-http"
15+
- "@yarnpkg/plugin-init"
16+
- "@yarnpkg/plugin-interactive-tools"
17+
- "@yarnpkg/plugin-link"
18+
- "@yarnpkg/plugin-nm"
19+
- "@yarnpkg/plugin-npm"
20+
- "@yarnpkg/plugin-npm-cli"
21+
- "@yarnpkg/plugin-pack"
22+
- "@yarnpkg/plugin-patch"
23+
- "@yarnpkg/plugin-pnp"
24+
- "@yarnpkg/plugin-pnpm"
25+
- "@yarnpkg/plugin-stage"
26+
- "@yarnpkg/plugin-typescript"
27+
- "@yarnpkg/plugin-version"
28+
- "@yarnpkg/plugin-workspace-tools"
29+
- "@yarnpkg/builder"
30+
- "@yarnpkg/doctor"
31+
- "@yarnpkg/extensions"
32+
- "@yarnpkg/nm"
33+
- "@yarnpkg/pnpify"
34+
- "@yarnpkg/sdks"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {xfs, ppath, Filename, npath} from '@yarnpkg/fslib';
2+
import {fs, yarn} from 'pkg-tests-core';
3+
4+
5+
describe(`Features`, () => {
6+
describe(`Legacy Plugin Removal`, () => {
7+
test(
8+
`it should remove the legacy plugins when running an install`,
9+
makeTemporaryEnv(
10+
{},
11+
async ({path, run, source}) => {
12+
const helloWorldSource = npath.toPortablePath(require.resolve(`@yarnpkg/monorepo/scripts/plugin-hello-world.js`));
13+
const fakeTypeScriptPlugin = yarn.getPluginPath(path, `@yarnpkg/plugin-typescript`);
14+
15+
await xfs.copyPromise(fakeTypeScriptPlugin, helloWorldSource);
16+
17+
await xfs.writeJsonPromise(ppath.join(path, Filename.rc), {
18+
plugins: [{
19+
path: ppath.relative(path, fakeTypeScriptPlugin),
20+
spec: `@yarnpkg/plugin-typescript`,
21+
}],
22+
});
23+
24+
await run(`install`);
25+
26+
await expect(xfs.existsPromise(fakeTypeScriptPlugin)).resolves.toEqual(false);
27+
28+
const data = await fs.readSyml(ppath.join(path, Filename.rc));
29+
expect(data).toEqual({});
30+
},
31+
),
32+
);
33+
34+
test(
35+
`it shouldn't remove other plugins`,
36+
makeTemporaryEnv(
37+
{},
38+
async ({path, run, source}) => {
39+
const helloWorldSource = npath.toPortablePath(require.resolve(`@yarnpkg/monorepo/scripts/plugin-hello-world.js`));
40+
const fakeTypeScriptPlugin = yarn.getPluginPath(path, `@yarnpkg/plugin-typescript`);
41+
const helloWorldPlugin = yarn.getPluginPath(path, `@yarnpkg/plugin-typescript`);
42+
43+
await xfs.copyPromise(fakeTypeScriptPlugin, helloWorldSource);
44+
await xfs.copyPromise(helloWorldPlugin, helloWorldSource);
45+
46+
await xfs.writeJsonPromise(ppath.join(path, Filename.rc), {
47+
plugins: [{
48+
path: ppath.relative(path, fakeTypeScriptPlugin),
49+
spec: `@yarnpkg/plugin-typescript`,
50+
}, {
51+
path: ppath.relative(path, helloWorldPlugin),
52+
spec: `@yarnpkg/plugin-hello-world`,
53+
}],
54+
});
55+
56+
await run(`install`);
57+
58+
await expect(xfs.existsPromise(helloWorldPlugin)).resolves.toEqual(false);
59+
60+
const data = await fs.readSyml(ppath.join(path, Filename.rc));
61+
expect(data).toEqual({
62+
plugins: [{
63+
path: ppath.relative(path, fakeTypeScriptPlugin),
64+
spec: `@yarnpkg/plugin-hello-world`,
65+
}],
66+
});
67+
},
68+
),
69+
);
70+
});
71+
});

packages/plugin-essentials/sources/commands/config/set.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default class ConfigSetCommand extends BaseCommand {
7777
? JSON.parse(this.value)
7878
: this.value;
7979

80-
const updateConfiguration: (patch: ((current: any) => any)) => Promise<void> =
80+
const updateConfiguration: (patch: ((current: any) => any)) => Promise<boolean> =
8181
this.home
8282
? patch => Configuration.updateHomeConfiguration(patch)
8383
: patch => Configuration.updateConfiguration(assertProjectCwd(), patch);

packages/plugin-essentials/sources/commands/config/unset.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default class ConfigUnsetCommand extends BaseCommand {
5151
if (typeof setting === `undefined`)
5252
throw new UsageError(`Couldn't find a configuration settings named "${name}"`);
5353

54-
const updateConfiguration: (patch: ((current: any) => any)) => Promise<void> =
54+
const updateConfiguration: (patch: ((current: any) => any)) => Promise<boolean> =
5555
this.home
5656
? patch => Configuration.updateHomeConfiguration(patch)
5757
: patch => Configuration.updateConfiguration(assertProjectCwd(), patch);

packages/plugin-essentials/sources/commands/install.ts

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import {BaseCommand, WorkspaceRequiredError} from '@yarnpkg/cli';
2-
import {Configuration, Cache, MessageName, Project, ReportError, StreamReport, formatUtils, InstallMode, execUtils, structUtils} from '@yarnpkg/core';
3-
import {xfs, ppath, Filename} from '@yarnpkg/fslib';
4-
import {parseSyml, stringifySyml} from '@yarnpkg/parsers';
5-
import CI from 'ci-info';
6-
import {Command, Option, Usage, UsageError} from 'clipanion';
7-
import * as t from 'typanion';
1+
import {BaseCommand, WorkspaceRequiredError} from '@yarnpkg/cli';
2+
import {Configuration, Cache, MessageName, Project, ReportError, StreamReport, formatUtils, InstallMode, execUtils, structUtils, LEGACY_PLUGINS} from '@yarnpkg/core';
3+
import {xfs, ppath, Filename, PortablePath} from '@yarnpkg/fslib';
4+
import {parseSyml, stringifySyml} from '@yarnpkg/parsers';
5+
import CI from 'ci-info';
6+
import {Command, Option, Usage, UsageError} from 'clipanion';
7+
import * as t from 'typanion';
88

99
// eslint-disable-next-line arca/no-default-export
1010
export default class YarnCommand extends BaseCommand {
@@ -242,8 +242,19 @@ export default class YarnCommand extends BaseCommand {
242242
stdout: this.context.stdout,
243243
includeFooter: false,
244244
}, async report => {
245+
let changed = false;
246+
247+
if (await autofixLegacyPlugins(configuration, immutable)) {
248+
report.reportInfo(MessageName.AUTOMERGE_SUCCESS, `Automatically removed core plugins that are now builtins 👍`);
249+
changed = true;
250+
}
251+
245252
if (await autofixMergeConflicts(configuration, immutable)) {
246253
report.reportInfo(MessageName.AUTOMERGE_SUCCESS, `Automatically fixed merge conflicts 👍`);
254+
changed = true;
255+
}
256+
257+
if (changed) {
247258
report.reportSeparator();
248259
}
249260
});
@@ -444,3 +455,48 @@ async function autofixMergeConflicts(configuration: Configuration, immutable: bo
444455

445456
return true;
446457
}
458+
459+
async function autofixLegacyPlugins(configuration: Configuration, immutable: boolean) {
460+
if (!configuration.projectCwd)
461+
return false;
462+
463+
const legacyPlugins: Array<PortablePath> = [];
464+
const yarnPluginDir = ppath.join(configuration.projectCwd, `.yarn/plugins/@yarnpkg`);
465+
466+
const changed = await Configuration.updateConfiguration(configuration.projectCwd, current => {
467+
if (!Array.isArray(current.plugins))
468+
return current;
469+
470+
const plugins = current.plugins.filter((plugin: {spec: string, path: PortablePath}) => {
471+
if (!plugin.path)
472+
return true;
473+
474+
const resolvedPath = ppath.resolve(configuration.projectCwd!, plugin.path);
475+
const isLegacy = LEGACY_PLUGINS.has(plugin.spec) && ppath.contains(yarnPluginDir, resolvedPath);
476+
477+
if (isLegacy)
478+
legacyPlugins.push(resolvedPath);
479+
480+
return !isLegacy;
481+
});
482+
483+
if (current.plugins.length === plugins.length)
484+
return current;
485+
486+
return {
487+
...current,
488+
plugins,
489+
};
490+
}, {
491+
immutable,
492+
});
493+
494+
if (!changed)
495+
return false;
496+
497+
await Promise.all(legacyPlugins.map(async pluginPath => {
498+
await xfs.removePromise(pluginPath);
499+
}));
500+
501+
return true;
502+
}

packages/yarnpkg-core/sources/Configuration.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ const isPublicRepository = GITHUB_ACTIONS && process.env.GITHUB_EVENT_PATH
3333
? !(xfs.readJsonSync(npath.toPortablePath(process.env.GITHUB_EVENT_PATH)).repository?.private ?? true)
3434
: false;
3535

36+
export const LEGACY_PLUGINS = new Set([
37+
`@yarnpkg/plugin-constraints`,
38+
`@yarnpkg/plugin-exec`,
39+
`@yarnpkg/plugin-interactive-tools`,
40+
`@yarnpkg/plugin-stage`,
41+
`@yarnpkg/plugin-typescript`,
42+
`@yarnpkg/plugin-version`,
43+
`@yarnpkg/plugin-workspace-tools`,
44+
]);
45+
3646
const IGNORED_ENV_VARIABLES = new Set([
3747
// Used by our test environment
3848
`isTestEnv`,
@@ -1208,6 +1218,9 @@ export class Configuration {
12081218
const userProvidedSpec = userPluginEntry?.spec ?? ``;
12091219
const userProvidedChecksum = userPluginEntry?.checksum ?? ``;
12101220

1221+
if (LEGACY_PLUGINS.has(userProvidedSpec))
1222+
continue;
1223+
12111224
const pluginPath = ppath.resolve(cwd, npath.toPortablePath(userProvidedPath));
12121225
if (!await xfs.existsPromise(pluginPath)) {
12131226
if (!userProvidedSpec) {
@@ -1350,7 +1363,7 @@ export class Configuration {
13501363
return projectCwd;
13511364
}
13521365

1353-
static async updateConfiguration(cwd: PortablePath, patch: {[key: string]: ((current: unknown) => unknown) | {} | undefined} | ((current: {[key: string]: unknown}) => {[key: string]: unknown})) {
1366+
static async updateConfiguration(cwd: PortablePath, patch: {[key: string]: ((current: unknown) => unknown) | {} | undefined} | ((current: {[key: string]: unknown}) => {[key: string]: unknown}), opts: {immutable?: boolean} = {}) {
13541367
const rcFilename = getRcFilename();
13551368
const configurationPath = ppath.join(cwd, rcFilename as PortablePath);
13561369

@@ -1369,7 +1382,7 @@ export class Configuration {
13691382
}
13701383

13711384
if (replacement === current) {
1372-
return;
1385+
return false;
13731386
}
13741387
} else {
13751388
replacement = current;
@@ -1401,13 +1414,15 @@ export class Configuration {
14011414
}
14021415

14031416
if (!patched) {
1404-
return;
1417+
return false;
14051418
}
14061419
}
14071420

14081421
await xfs.changeFilePromise(configurationPath, stringifySyml(replacement), {
14091422
automaticNewlines: true,
14101423
});
1424+
1425+
return true;
14111426
}
14121427

14131428
static async addPlugin(cwd: PortablePath, pluginMetaList: Array<PluginMeta>) {

packages/yarnpkg-core/sources/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import * as tgzUtils from './tgzUtils';
1212
import * as treeUtils from './treeUtils';
1313

1414
export {Cache} from './Cache';
15-
export {DEFAULT_RC_FILENAME, DEFAULT_LOCK_FILENAME, TAG_REGEXP} from './Configuration';
15+
export {DEFAULT_RC_FILENAME, DEFAULT_LOCK_FILENAME, LEGACY_PLUGINS, TAG_REGEXP} from './Configuration';
1616
export {Configuration, FormatType, ProjectLookup, SettingsType, WindowsLinkType} from './Configuration';
1717
export type {PluginConfiguration, SettingsDefinition, PackageExtensionData} from './Configuration';
1818
export type {ConfigurationValueMap, ConfigurationDefinitionMap} from './Configuration';

0 commit comments

Comments
 (0)