From 568d7a5e1c4bab3f002f6d60af0624c1db6a31a1 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Fri, 12 Sep 2025 13:56:57 +0300 Subject: [PATCH] feat(plugins): import and remove hooks added hooks for plugins after they have been imported and before they are removed. closes #6896 Signed-off-by: Hellgren Heikki --- .yarn/versions/71a206d9.yml | 35 +++++++++++++++++++ .../sources/commands/plugin/import.test.ts | 3 ++ .../sources/commands/plugin/remove.test.ts | 3 ++ .../sources/commands/plugin/import.ts | 7 ++-- .../sources/commands/plugin/import/sources.ts | 24 ++++++------- .../sources/commands/plugin/remove.ts | 10 +++--- packages/yarnpkg-core/sources/Plugin.ts | 10 ++++++ scripts/plugin-hello-world.js | 12 +++++++ 8 files changed, 85 insertions(+), 19 deletions(-) create mode 100644 .yarn/versions/71a206d9.yml diff --git a/.yarn/versions/71a206d9.yml b/.yarn/versions/71a206d9.yml new file mode 100644 index 000000000000..1c54494a6086 --- /dev/null +++ b/.yarn/versions/71a206d9.yml @@ -0,0 +1,35 @@ +releases: + "@yarnpkg/core": patch + "@yarnpkg/plugin-essentials": patch + +declined: + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-exec" + - "@yarnpkg/plugin-file" + - "@yarnpkg/plugin-git" + - "@yarnpkg/plugin-github" + - "@yarnpkg/plugin-http" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-jsr" + - "@yarnpkg/plugin-link" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/cli" + - "@yarnpkg/doctor" + - "@yarnpkg/extensions" + - "@yarnpkg/nm" + - "@yarnpkg/pnpify" + - "@yarnpkg/sdks" diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/import.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/import.test.ts index c85f6535bc7c..bfc7f645b344 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/import.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/import.test.ts @@ -47,6 +47,9 @@ describe(`Commands`, () => { await run(`plugin`, `import`, pluginUrl); + // Check for post import script output + await expect(xfs.existsPromise(ppath.join(path, `post_import.txt`))).resolves.toEqual(true); + await expect(xfs.existsPromise(ppath.join(path, mockPluginPath))).resolves.toEqual(true); await expect(fs.readSyml(ppath.join(path, Filename.rc))).resolves.toEqual({ httpsCaFilePath, diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/remove.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/remove.test.ts index 0d13e127a993..bd7074f07202 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/remove.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/plugin/remove.test.ts @@ -33,6 +33,9 @@ describe(`Commands`, () => { await run(`plugin`, `remove`, `@yarnpkg/plugin-hello-world`); await expect(xfs.existsPromise(helloWorldPlugin)).resolves.toEqual(false); + // Check for pre remove script output + await expect(xfs.existsPromise(ppath.join(path, `pre_remove.txt`))).resolves.toEqual(true); + await expect(fs.readSyml(ppath.join(path, Filename.rc))).resolves.toEqual({ plugins: [{ path: ppath.relative(path, helloUniversePlugin), diff --git a/packages/plugin-essentials/sources/commands/plugin/import.ts b/packages/plugin-essentials/sources/commands/plugin/import.ts index 47e0a4842923..d0589d612ae5 100644 --- a/packages/plugin-essentials/sources/commands/plugin/import.ts +++ b/packages/plugin-essentials/sources/commands/plugin/import.ts @@ -1,5 +1,5 @@ import {BaseCommand} from '@yarnpkg/cli'; -import {PluginMeta} from '@yarnpkg/core/sources/Plugin'; +import {Hooks, PluginMeta} from '@yarnpkg/core/sources/Plugin'; import {Configuration, MessageName, Project, ReportError, StreamReport, Report} from '@yarnpkg/core'; import {YarnVersion, formatUtils, httpUtils, structUtils, hashUtils} from '@yarnpkg/core'; import {PortablePath, npath, ppath, xfs} from '@yarnpkg/fslib'; @@ -54,13 +54,12 @@ export default class PluginImportCommand extends BaseCommand { async execute() { const configuration = await Configuration.find(this.context.cwd, this.context.plugins); + const {project} = await Project.find(configuration, this.context.cwd); const report = await StreamReport.start({ configuration, stdout: this.context.stdout, }, async report => { - const {project} = await Project.find(configuration, this.context.cwd); - let pluginSpec: string; let pluginBuffer: Buffer; if (this.name.match(/^\.{0,2}[\\/]/) || npath.isAbsolute(this.name)) { @@ -116,6 +115,8 @@ export default class PluginImportCommand extends BaseCommand { await savePlugin(pluginSpec, pluginBuffer, {checksum: this.checksum, project, report}); }); + await configuration.triggerHook((hooks: Hooks) => hooks.pluginPostImport, project); + return report.exitCode(); } } diff --git a/packages/plugin-essentials/sources/commands/plugin/import/sources.ts b/packages/plugin-essentials/sources/commands/plugin/import/sources.ts index ae18b96e6012..3e30010ec924 100644 --- a/packages/plugin-essentials/sources/commands/plugin/import/sources.ts +++ b/packages/plugin-essentials/sources/commands/plugin/import/sources.ts @@ -1,13 +1,13 @@ -import {BaseCommand} from '@yarnpkg/cli'; -import {structUtils, hashUtils, Report, CommandContext, YarnVersion} from '@yarnpkg/core'; -import {Configuration, MessageName, Project, ReportError, StreamReport} from '@yarnpkg/core'; -import {PortablePath, npath, ppath, xfs, Filename} from '@yarnpkg/fslib'; -import {Command, Option, Usage} from 'clipanion'; -import {tmpdir} from 'os'; +import {BaseCommand} from '@yarnpkg/cli'; +import {structUtils, hashUtils, Report, CommandContext, YarnVersion, Hooks} from '@yarnpkg/core'; +import {Configuration, MessageName, Project, ReportError, StreamReport} from '@yarnpkg/core'; +import {PortablePath, npath, ppath, xfs, Filename} from '@yarnpkg/fslib'; +import {Command, Option, Usage} from 'clipanion'; +import {tmpdir} from 'os'; -import {prepareRepo, runWorkflow} from '../../set/version/sources'; -import {savePlugin} from '../import'; -import {getAvailablePlugins} from '../list'; +import {prepareRepo, runWorkflow} from '../../set/version/sources'; +import {savePlugin} from '../import'; +import {getAvailablePlugins} from '../list'; const buildWorkflow = ({pluginName, noMinify}: {noMinify: boolean, pluginName: string}, target: PortablePath) => [ [`yarn`, `build:${pluginName}`, ...noMinify ? [`--no-minify`] : [], `|`], @@ -60,7 +60,7 @@ export default class PluginImportSourcesCommand extends BaseCommand { async execute() { const configuration = await Configuration.find(this.context.cwd, this.context.plugins); - + const {project} = await Project.find(configuration, this.context.cwd); const target = typeof this.installPath !== `undefined` ? ppath.resolve(this.context.cwd, npath.toPortablePath(this.installPath)) : ppath.resolve(npath.toPortablePath(tmpdir()), `yarnpkg-sources`, hashUtils.makeHash(this.repository).slice(0, 6) as Filename); @@ -69,8 +69,6 @@ export default class PluginImportSourcesCommand extends BaseCommand { configuration, stdout: this.context.stdout, }, async report => { - const {project} = await Project.find(configuration, this.context.cwd); - const ident = structUtils.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/, `@yarnpkg/plugin-`)); const identStr = structUtils.stringifyIdent(ident); const data = await getAvailablePlugins(configuration, YarnVersion); @@ -85,6 +83,8 @@ export default class PluginImportSourcesCommand extends BaseCommand { await buildAndSavePlugin(pluginSpec, this, {project, report, target}); }); + await configuration.triggerHook((hooks: Hooks) => hooks.pluginPostImport, project); + return report.exitCode(); } } diff --git a/packages/plugin-essentials/sources/commands/plugin/remove.ts b/packages/plugin-essentials/sources/commands/plugin/remove.ts index 83215089ff49..8b1cf958d616 100644 --- a/packages/plugin-essentials/sources/commands/plugin/remove.ts +++ b/packages/plugin-essentials/sources/commands/plugin/remove.ts @@ -1,7 +1,7 @@ -import {BaseCommand} from '@yarnpkg/cli'; -import {Configuration, MessageName, Project, StreamReport, formatUtils, structUtils} from '@yarnpkg/core'; -import {PortablePath, ppath, xfs} from '@yarnpkg/fslib'; -import {Command, Option, Usage, UsageError} from 'clipanion'; +import {BaseCommand} from '@yarnpkg/cli'; +import {Configuration, MessageName, Project, StreamReport, formatUtils, structUtils, Hooks} from '@yarnpkg/core'; +import {PortablePath, ppath, xfs} from '@yarnpkg/fslib'; +import {Command, Option, Usage, UsageError} from 'clipanion'; // eslint-disable-next-line arca/no-default-export export default class PluginRemoveCommand extends BaseCommand { @@ -32,6 +32,8 @@ export default class PluginRemoveCommand extends BaseCommand { const configuration = await Configuration.find(this.context.cwd, this.context.plugins); const {project} = await Project.find(configuration, this.context.cwd); + await configuration.triggerHook((hooks: Hooks) => hooks.pluginPreRemove, project); + const report = await StreamReport.start({ configuration, stdout: this.context.stdout, diff --git a/packages/yarnpkg-core/sources/Plugin.ts b/packages/yarnpkg-core/sources/Plugin.ts index 1dc93934d303..2341400664c2 100644 --- a/packages/yarnpkg-core/sources/Plugin.ts +++ b/packages/yarnpkg-core/sources/Plugin.ts @@ -182,6 +182,16 @@ export interface Hooks { cleanGlobalArtifacts?: ( configuration: Configuration, ) => Promise; + + /** + * Called after the plugin has been imported. + */ + pluginPostImport?: (project: Project) => Promise; + + /** + * Called before the plugin gets removed from the project. + */ + pluginPreRemove?: (project: Project) => Promise; } export type Plugin = { diff --git a/scripts/plugin-hello-world.js b/scripts/plugin-hello-world.js index 9d91b6083ff3..dd4f6c5221bd 100644 --- a/scripts/plugin-hello-world.js +++ b/scripts/plugin-hello-world.js @@ -35,6 +35,18 @@ module.exports = { commands: [ HelloWorldCommand, ], + hooks: { + pluginPostImport: project => { + const {xfs, ppath} = require(`@yarnpkg/fslib`); + const path = ppath.join(project.cwd, `post_import.txt`); + xfs.writeFileSync(path, `Hello, World!`); + }, + pluginPreRemove: project => { + const {xfs, ppath} = require(`@yarnpkg/fslib`); + const path = ppath.join(project.cwd, `pre_remove.txt`); + xfs.writeFileSync(path, `Goodbye, World!`); + }, + }, }; }, };