From 5b7e30e35903a485221f9d7136db68c0b183e047 Mon Sep 17 00:00:00 2001 From: Steve Storey Date: Wed, 12 Jun 2024 21:55:34 +0100 Subject: [PATCH 1/4] Support corepack. This adds some basic support for using corepack to manage the tooling via the 'packageManager' field in the package.json. Here we add the ability to download a version of it along with a node version, along with the ability to execute command via corepack such as 'pnpm install' or 'yarn install' depending on the selected package manager. Later we ought to work out how to use the version packaged with the node runtime, but this was inspired by the pnpm download which is downloaded separately, and so this is too. --- .../plugins/frontend/mojo/CorepackMojo.java | 54 ++++ .../mojo/InstallNodeAndCorepackMojo.java | 104 +++++++ .../m2e/lifecycle-mapping-metadata.xml | 2 + .../frontend/lib/CorepackInstaller.java | 271 ++++++++++++++++++ .../plugins/frontend/lib/CorepackRunner.java | 15 + .../frontend/lib/FrontendPluginFactory.java | 8 + .../frontend/lib/NodeExecutorConfig.java | 7 + 7 files changed, 461 insertions(+) create mode 100644 frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java create mode 100644 frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java create mode 100644 frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackRunner.java diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java new file mode 100644 index 00000000..e5bd1bb0 --- /dev/null +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/CorepackMojo.java @@ -0,0 +1,54 @@ +package com.github.eirslett.maven.plugins.frontend.mojo; + +import java.io.File; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.sonatype.plexus.build.incremental.BuildContext; + +import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.TaskRunnerException; + +@Mojo(name="corepack", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) +public final class CorepackMojo extends AbstractFrontendMojo { + + /** + * corepack arguments. Default is "enable". + */ + @Parameter(defaultValue = "enable", property = "frontend.corepack.arguments", required = false) + private String arguments; + + @Parameter(property = "session", defaultValue = "${session}", readonly = true) + private MavenSession session; + + @Component + private BuildContext buildContext; + + @Component(role = SettingsDecrypter.class) + private SettingsDecrypter decrypter; + + /** + * Skips execution of this mojo. + */ + @Parameter(property = "skip.corepack", defaultValue = "${skip.corepack}") + private boolean skip; + + @Override + protected boolean skipExecution() { + return this.skip; + } + + @Override + public synchronized void execute(FrontendPluginFactory factory) throws TaskRunnerException { + File packageJson = new File(workingDirectory, "package.json"); + if (buildContext == null || buildContext.hasDelta(packageJson) || !buildContext.isIncremental()) { + factory.getCorepackRunner().execute(arguments, environmentVariables); + } else { + getLog().info("Skipping corepack install as package.json unchanged"); + } + } +} diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java new file mode 100644 index 00000000..62ad433a --- /dev/null +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java @@ -0,0 +1,104 @@ +package com.github.eirslett.maven.plugins.frontend.mojo; + +import com.github.eirslett.maven.plugins.frontend.lib.CorepackInstaller; +import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; +import com.github.eirslett.maven.plugins.frontend.lib.InstallationException; +import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.settings.crypto.SettingsDecrypter; +import org.apache.maven.settings.Server; + +@Mojo(name="install-node-and-corepack", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, threadSafe = true) +public final class InstallNodeAndCorepackMojo extends AbstractFrontendMojo { + + /** + * Where to download Node.js binary from. Defaults to https://nodejs.org/dist/ + */ + @Parameter(property = "nodeDownloadRoot", required = false) + private String nodeDownloadRoot; + + /** + * Where to download corepack binary from. Defaults to https://registry.npmjs.org/corepack/-/ + */ + @Parameter(property = "corepackDownloadRoot", required = false, defaultValue = CorepackInstaller.DEFAULT_COREPACK_DOWNLOAD_ROOT) + private String corepackDownloadRoot; + + /** + * The version of Node.js to install. IMPORTANT! Most Node.js version names start with 'v', for example 'v0.10.18' + */ + @Parameter(property="nodeVersion", required = true) + private String nodeVersion; + + /** + * The version of corepack to install. Note that the version string can optionally be prefixed with + * 'v' (i.e., both 'v1.2.3' and '1.2.3' are valid). + */ + @Parameter(property = "corepackVersion", required = true) + private String corepackVersion; + + /** + * Server Id for download username and password + */ + @Parameter(property = "serverId", defaultValue = "") + private String serverId; + + @Parameter(property = "session", defaultValue = "${session}", readonly = true) + private MavenSession session; + + /** + * Skips execution of this mojo. + */ + @Parameter(property = "skip.installnodecorepack", defaultValue = "${skip.installnodecorepack}") + private boolean skip; + + @Component(role = SettingsDecrypter.class) + private SettingsDecrypter decrypter; + + @Override + protected boolean skipExecution() { + return this.skip; + } + + @Override + public void execute(FrontendPluginFactory factory) throws InstallationException { + ProxyConfig proxyConfig = MojoUtils.getProxyConfig(session, decrypter); + String resolvedNodeDownloadRoot = getNodeDownloadRoot(); + String resolvedCorepackDownloadRoot = getCorepackDownloadRoot(); + Server server = MojoUtils.decryptServer(serverId, session, decrypter); + if (null != server) { + factory.getNodeInstaller(proxyConfig) + .setNodeVersion(nodeVersion) + .setNodeDownloadRoot(resolvedNodeDownloadRoot) + .setUserName(server.getUsername()) + .setPassword(server.getPassword()) + .install(); + factory.getCorepackInstaller(proxyConfig) + .setCorepackVersion(corepackVersion) + .setCorepackDownloadRoot(resolvedCorepackDownloadRoot) + .setUserName(server.getUsername()) + .setPassword(server.getPassword()) + .install(); + } else { + factory.getNodeInstaller(proxyConfig) + .setNodeVersion(nodeVersion) + .setNodeDownloadRoot(resolvedNodeDownloadRoot) + .install(); + factory.getCorepackInstaller(proxyConfig) + .setCorepackVersion(this.corepackVersion) + .setCorepackDownloadRoot(resolvedCorepackDownloadRoot) + .install(); + } + } + + private String getNodeDownloadRoot() { + return nodeDownloadRoot; + } + + private String getCorepackDownloadRoot() { + return corepackDownloadRoot; + } +} diff --git a/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml index 08e3a4e5..34941f64 100644 --- a/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml +++ b/frontend-maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml @@ -4,6 +4,7 @@ + install-node-and-corepack install-node-and-npm install-node-and-pnpm install-node-and-yarn @@ -20,6 +21,7 @@ + corepack npm npx pnpm diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java new file mode 100644 index 00000000..4dbef0f9 --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java @@ -0,0 +1,271 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; + +public class CorepackInstaller { + + private static final String VERSION = "version"; + + public static final String DEFAULT_COREPACK_DOWNLOAD_ROOT = "https://registry.npmjs.org/corepack/-/"; + + private static final Object LOCK = new Object(); + + private String corepackVersion, corepackDownloadRoot, userName, password; + + private final Logger logger; + + private final InstallConfig config; + + private final ArchiveExtractor archiveExtractor; + + private final FileDownloader fileDownloader; + + CorepackInstaller(InstallConfig config, ArchiveExtractor archiveExtractor, FileDownloader fileDownloader) { + this.logger = LoggerFactory.getLogger(getClass()); + this.config = config; + this.archiveExtractor = archiveExtractor; + this.fileDownloader = fileDownloader; + } + + public CorepackInstaller setNodeVersion(String nodeVersion) { + return this; + } + + public CorepackInstaller setCorepackVersion(String corepackVersion) { + this.corepackVersion = corepackVersion; + return this; + } + + public CorepackInstaller setCorepackDownloadRoot(String corepackDownloadRoot) { + this.corepackDownloadRoot = corepackDownloadRoot; + return this; + } + + public CorepackInstaller setUserName(String userName) { + this.userName = userName; + return this; + } + + public CorepackInstaller setPassword(String password) { + this.password = password; + return this; + } + + public void install() throws InstallationException { + // use static lock object for a synchronized block + synchronized (LOCK) { + if (this.corepackDownloadRoot == null || this.corepackDownloadRoot.isEmpty()) { + this.corepackDownloadRoot = DEFAULT_COREPACK_DOWNLOAD_ROOT; + } + if (!corepackIsAlreadyInstalled()) { + installCorepack(); + } + + if (this.config.getPlatform().isWindows()) { + linkExecutableWindows(); + } else { + linkExecutable(); + } + } + } + + private boolean corepackIsAlreadyInstalled() { + try { + final File corepackPackageJson = new File( + this.config.getInstallDirectory() + Utils.normalize("/node/node_modules/corepack/package.json")); + if (corepackPackageJson.exists()) { + HashMap data = new ObjectMapper().readValue(corepackPackageJson, HashMap.class); + if (data.containsKey(VERSION)) { + final String foundCorepackVersion = data.get(VERSION).toString(); + if (foundCorepackVersion.equals(this.corepackVersion.replaceFirst("^v", ""))) { + this.logger.info("corepack {} is already installed.", foundCorepackVersion); + return true; + } else { + this.logger.info("corepack {} was installed, but we need version {}", foundCorepackVersion, + this.corepackVersion); + return false; + } + } else { + this.logger.info("Could not read corepack version from package.json"); + return false; + } + } else { + return false; + } + } catch (IOException ex) { + throw new RuntimeException("Could not read package.json", ex); + } + } + + private void installCorepack() throws InstallationException { + try { + this.logger.info("Installing corepack version {}", this.corepackVersion); + String corepackVersionClean = this.corepackVersion.replaceFirst("^v(?=[0-9]+)", ""); + final String downloadUrl = this.corepackDownloadRoot + "corepack-" + corepackVersionClean + ".tgz"; + + CacheDescriptor cacheDescriptor = new CacheDescriptor("corepack", corepackVersionClean, "tar.gz"); + + File archive = this.config.getCacheResolver().resolve(cacheDescriptor); + + downloadFileIfMissing(downloadUrl, archive, this.userName, this.password); + + File installDirectory = getNodeInstallDirectory(); + File nodeModulesDirectory = new File(installDirectory, "node_modules"); + + // We need to delete the existing corepack directory first so we clean out any old files, and + // so we can rename the package directory below. + File oldDirectory = new File(installDirectory, "corepack"); + File corepackDirectory = new File(nodeModulesDirectory, "corepack"); + try { + if (oldDirectory.isDirectory()) { + FileUtils.deleteDirectory(oldDirectory); + } + FileUtils.deleteDirectory(corepackDirectory); + } catch (IOException e) { + this.logger.warn("Failed to delete existing corepack installation."); + } + + File packageDirectory = new File(nodeModulesDirectory, "package"); + try { + extractFile(archive, nodeModulesDirectory); + } catch (ArchiveExtractionException e) { + if (e.getCause() instanceof EOFException) { + // https://github.com/eirslett/frontend-maven-plugin/issues/794 + // The downloading was probably interrupted and archive file is incomplete: + // delete it to retry from scratch + this.logger.error("The archive file {} is corrupted and will be deleted. " + + "Please try the build again.", archive.getPath()); + archive.delete(); + if (packageDirectory.exists()) { + FileUtils.deleteDirectory(packageDirectory); + } + } + + throw e; + } + + // handles difference between old and new download root (nodejs.org/dist/npm and + // registry.npmjs.org) + // see https://github.com/eirslett/frontend-maven-plugin/issues/65#issuecomment-52024254 + if (packageDirectory.exists() && !corepackDirectory.exists()) { + if (!packageDirectory.renameTo(corepackDirectory)) { + this.logger.warn("Cannot rename corepack directory, making a copy."); + FileUtils.copyDirectory(packageDirectory, corepackDirectory); + } + } + + this.logger.info("Installed corepack locally."); + + } catch (DownloadException e) { + throw new InstallationException("Could not download corepack", e); + } catch (ArchiveExtractionException e) { + throw new InstallationException("Could not extract the corepack archive", e); + } catch (IOException e) { + throw new InstallationException("Could not copy corepack", e); + } + } + + private void linkExecutable() throws InstallationException{ + File nodeInstallDirectory = getNodeInstallDirectory(); + File corepackExecutable = new File(nodeInstallDirectory, "corepack"); + + if (corepackExecutable.exists()) { + this.logger.info("Existing corepack executable found, skipping linking."); + return; + } + + NodeExecutorConfig executorConfig = new InstallNodeExecutorConfig(this.config); + File corepackJsExecutable = executorConfig.getCorepackPath(); + + if (!corepackJsExecutable.exists()) { + throw new InstallationException("Could not link to corepack executable, no corepack installation found."); + } + + this.logger.info("No corepack executable found, creating symbolic link to {}.", corepackJsExecutable.toPath()); + + try { + Files.createSymbolicLink(corepackExecutable.toPath(), corepackJsExecutable.toPath()); + } catch (IOException e) { + throw new InstallationException("Could not create symbolic link for corepack executable.", e); + } + } + + private void linkExecutableWindows() throws InstallationException{ + File nodeInstallDirectory = getNodeInstallDirectory(); + File corepackExecutable = new File(nodeInstallDirectory, "corepack.cmd"); + + if (corepackExecutable.exists()) { + this.logger.info("Existing corepack executable found, skipping linking."); + return; + } + + NodeExecutorConfig executorConfig = new InstallNodeExecutorConfig(this.config); + File corepackJsExecutable = executorConfig.getCorepackPath(); + + if (!corepackJsExecutable.exists()) { + throw new InstallationException("Could not link to corepack executable, no corepack installation found."); + } + + this.logger.info("No corepack executable found, creating proxy script to {}.", corepackJsExecutable.toPath()); + + Path nodePath = executorConfig.getNodePath().toPath(); + Path relativeNodePath = nodeInstallDirectory.toPath().relativize(nodePath); + Path relativeCorepackPath = nodeInstallDirectory.toPath().relativize(corepackJsExecutable.toPath()); + + // Create a script that will proxy any commands passed into it to the corepack executable. + String scriptContents = new StringBuilder() + .append(":: Created by frontend-maven-plugin, please don't edit manually.\r\n") + .append("@ECHO OFF\r\n") + .append("\r\n") + .append("SETLOCAL\r\n") + .append("\r\n") + .append(String.format("SET \"NODE_EXE=%%~dp0\\%s\"\r\n", relativeNodePath)) + .append(String.format("SET \"COREPACK_CLI_JS=%%~dp0\\%s\"\r\n", relativeCorepackPath)) + .append("\r\n") + .append("\"%NODE_EXE%\" \"%COREPACK_CLI_JS%\" %*") + .toString(); + + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(corepackExecutable)); + writer.write(scriptContents); + writer.close(); + } catch (IOException e) { + throw new InstallationException("Could not create proxy script for corepack executable.", e); + } + } + + private File getNodeInstallDirectory() { + File installDirectory = new File(this.config.getInstallDirectory(), NodeInstaller.INSTALL_PATH); + if (!installDirectory.exists()) { + this.logger.debug("Creating install directory {}", installDirectory); + installDirectory.mkdirs(); + } + return installDirectory; + } + + private void extractFile(File archive, File destinationDirectory) throws ArchiveExtractionException { + this.logger.info("Unpacking {} into {}", archive, destinationDirectory); + this.archiveExtractor.extract(archive.getPath(), destinationDirectory.getPath()); + } + + private void downloadFileIfMissing(String downloadUrl, File destination, String userName, String password) + throws DownloadException { + if (!destination.exists()) { + downloadFile(downloadUrl, destination, userName, password); + } + } + + private void downloadFile(String downloadUrl, File destination, String userName, String password) + throws DownloadException { + this.logger.info("Downloading {} to {}", downloadUrl, destination); + this.fileDownloader.download(downloadUrl, destination.getPath(), userName, password); + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackRunner.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackRunner.java new file mode 100644 index 00000000..91fed6ca --- /dev/null +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackRunner.java @@ -0,0 +1,15 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +public interface CorepackRunner extends NodeTaskRunner {} + +final class DefaultCorepackRunner extends NodeTaskExecutor implements CorepackRunner { + static final String TASK_NAME = "corepack"; + + public DefaultCorepackRunner(NodeExecutorConfig config) { + super(config, TASK_NAME, config.getCorepackPath().getAbsolutePath()); + + if (!config.getCorepackPath().exists()) { + setTaskLocation(config.getCorepackPath().getAbsolutePath()); + } + } +} diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java index 07c86062..2b30f182 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/FrontendPluginFactory.java @@ -32,6 +32,10 @@ public NPMInstaller getNPMInstaller(ProxyConfig proxy) { return new NPMInstaller(getInstallConfig(), new DefaultArchiveExtractor(), new DefaultFileDownloader(proxy)); } + public CorepackInstaller getCorepackInstaller(ProxyConfig proxy) { + return new CorepackInstaller(getInstallConfig(), new DefaultArchiveExtractor(), new DefaultFileDownloader(proxy)); + } + public PnpmInstaller getPnpmInstaller(ProxyConfig proxy) { return new PnpmInstaller(getInstallConfig(), new DefaultArchiveExtractor(), new DefaultFileDownloader(proxy)); } @@ -56,6 +60,10 @@ public NpmRunner getNpmRunner(ProxyConfig proxy, String npmRegistryURL) { return new DefaultNpmRunner(getExecutorConfig(), proxy, npmRegistryURL); } + public CorepackRunner getCorepackRunner() { + return new DefaultCorepackRunner(getExecutorConfig()); + } + public PnpmRunner getPnpmRunner(ProxyConfig proxyConfig, String npmRegistryUrl) { return new DefaultPnpmRunner(getExecutorConfig(), proxyConfig, npmRegistryUrl); } diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeExecutorConfig.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeExecutorConfig.java index af8bb2ef..80c743b1 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeExecutorConfig.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/NodeExecutorConfig.java @@ -7,6 +7,7 @@ public interface NodeExecutorConfig { File getNpmPath(); File getPnpmPath(); File getPnpmCjsPath(); + File getCorepackPath(); File getNpxPath(); File getInstallDirectory(); @@ -21,6 +22,7 @@ final class InstallNodeExecutorConfig implements NodeExecutorConfig { private static final String NPM = NodeInstaller.INSTALL_PATH + "/node_modules/npm/bin/npm-cli.js"; private static final String PNPM = NodeInstaller.INSTALL_PATH + "/node_modules/pnpm/bin/pnpm.js"; private static final String PNPM_CJS = NodeInstaller.INSTALL_PATH + "/node_modules/pnpm/bin/pnpm.cjs"; + private static final String COREPACK = NodeInstaller.INSTALL_PATH + "/node_modules/corepack/dist/corepack.js"; private static final String NPX = NodeInstaller.INSTALL_PATH + "/node_modules/npm/bin/npx-cli.js"; private final InstallConfig installConfig; @@ -51,6 +53,11 @@ public File getPnpmCjsPath() { return new File(installConfig.getInstallDirectory() + Utils.normalize(PNPM_CJS)); } + @Override + public File getCorepackPath() { + return new File(installConfig.getInstallDirectory() + Utils.normalize(COREPACK)); + } + @Override public File getNpxPath() { return new File(installConfig.getInstallDirectory() + Utils.normalize(NPX)); From c63726c0d08373083e21999c8eb979dde9cbe7fc Mon Sep 17 00:00:00 2001 From: Steve Storey Date: Wed, 12 Jun 2024 22:06:00 +0100 Subject: [PATCH 2/4] Update the README with the details of how to use corepack. --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/README.md b/README.md index 51cfba12..cec10f28 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,11 @@ to see how it should be set up: https://github.com/eirslett/frontend-maven-plugi - [Installing node and npm](#installing-node-and-npm) - [Installing node and yarn](#installing-node-and-yarn) + - [Installing node and corepack](#installing-node-and-corepack) - Running - [npm](#running-npm) - [yarn](#running-yarn) + - [corepack](#running-corepack) - [bower](#running-bower) - [grunt](#running-grunt) - [gulp](#running-gulp) @@ -171,6 +173,45 @@ https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plu ``` +### Installing node and corepack + +You can choose to let corepack manage the package manager version in use. Node is +downloaded from `https://nodejs.org/dist`, and corepack currently comes from +`https://repository.npmjs.org`, extracted and put into a `node` folder created +in your installation directory. + +Node/corepack and any package managers will only be "installed" locally to your project. +It will not be installed globally on the whole system (and it will not interfere with any +Node/corepack installations already present). + +Have a look at the example `POM` to see how it should be set up with corepack: +https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plugin/src/it/corepack-integration/pom.xml + + +```xml + + ... + + + install-node-and-corepack + + install-node-and-corepack + + + generate-resources + + + v20.12.2 + v0.25.2 + + + http://myproxy.example.org/nodejs/ + + http://myproxy.example.org/corepack/ + + +``` + ### Running npm All node packaged modules will be installed in the `node_modules` folder in your [working directory](#working-directory). @@ -274,6 +315,55 @@ Also you can set a registry using a tag `npmRegistryURL` ``` +### Running corepack + +If your `packageManager` specifies `yarn`, then you'll want to have something like: + + +```xml + + install + + corepack + + + yarn install + + + + build + + corepack + + + yarn build + + +``` + +and if you're using `pnpm` instead, you'll want something like + +```xml + + install + + corepack + + + pnpm install + + + + build + + corepack + + + pnpm build + + +``` + ### Running bower All bower dependencies will be installed in the `bower_components` folder in your working directory. From 2093a3d2f98876c359f4410c3cbf2e55d72c320e Mon Sep 17 00:00:00 2001 From: Steve Storey Date: Wed, 12 Jun 2024 22:11:58 +0100 Subject: [PATCH 3/4] Add a corepack integration example. --- .../src/it/corepack-integration/.yarnrc.yml | 3 ++ .../src/it/corepack-integration/package.json | 8 +++ .../src/it/corepack-integration/pom.xml | 49 +++++++++++++++++++ .../src/it/corepack-integration/verify.groovy | 6 +++ 4 files changed, 66 insertions(+) create mode 100644 frontend-maven-plugin/src/it/corepack-integration/.yarnrc.yml create mode 100644 frontend-maven-plugin/src/it/corepack-integration/package.json create mode 100644 frontend-maven-plugin/src/it/corepack-integration/pom.xml create mode 100644 frontend-maven-plugin/src/it/corepack-integration/verify.groovy diff --git a/frontend-maven-plugin/src/it/corepack-integration/.yarnrc.yml b/frontend-maven-plugin/src/it/corepack-integration/.yarnrc.yml new file mode 100644 index 00000000..0cd45f54 --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-integration/.yarnrc.yml @@ -0,0 +1,3 @@ +enableGlobalCache: false + +nodeLinker: node-modules diff --git a/frontend-maven-plugin/src/it/corepack-integration/package.json b/frontend-maven-plugin/src/it/corepack-integration/package.json new file mode 100644 index 00000000..3519bdcf --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-integration/package.json @@ -0,0 +1,8 @@ +{ + "name": "example", + "version": "0.0.1", + "dependencies": { + "less": "~3.0.2" + }, + "packageManager": "yarn@4.1.1" +} diff --git a/frontend-maven-plugin/src/it/corepack-integration/pom.xml b/frontend-maven-plugin/src/it/corepack-integration/pom.xml new file mode 100644 index 00000000..27b6ff4f --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-integration/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.github.eirslett + example + 0 + pom + + + + + com.github.eirslett + frontend-maven-plugin + + @project.version@ + + + target + + + + + + install node and corepack + + install-node-and-corepack + + + v20.12.2 + 0.25.2 + + + + + yarn install + + corepack + + + yarn install --no-immutable + + + + + + + + diff --git a/frontend-maven-plugin/src/it/corepack-integration/verify.groovy b/frontend-maven-plugin/src/it/corepack-integration/verify.groovy new file mode 100644 index 00000000..1355cd8e --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-integration/verify.groovy @@ -0,0 +1,6 @@ +assert new File(basedir, 'target/node').exists() : "Node was not installed in the custom install directory"; +assert new File(basedir, 'node_modules').exists() : "Node modules were not installed in the base directory"; +assert new File(basedir, 'node_modules/less/package.json').exists() : "Less dependency has not been installed successfully"; + +String buildLog = new File(basedir, 'build.log').text +assert buildLog.contains('BUILD SUCCESS') : 'build was not successful' From 10f946cdc01abf4d7638eb31d94f84337781ab5d Mon Sep 17 00:00:00 2001 From: Steve Storey Date: Sun, 11 Aug 2024 12:15:38 +0100 Subject: [PATCH 4/4] Support using the corepack version provided with Node. In most uses, users will want to ues the version of corepack provided with the NodeJS version they are using, and the plugin now supports this mode of usage by default if no corepack version is explicitly provided. --- README.md | 12 +++-- .../corepack-provided-integration/.yarnrc.yml | 3 ++ .../package.json | 8 ++++ .../it/corepack-provided-integration/pom.xml | 48 +++++++++++++++++++ .../verify.groovy | 6 +++ .../mojo/InstallNodeAndCorepackMojo.java | 47 +++++++++--------- .../frontend/lib/CorepackInstaller.java | 5 ++ 7 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 frontend-maven-plugin/src/it/corepack-provided-integration/.yarnrc.yml create mode 100644 frontend-maven-plugin/src/it/corepack-provided-integration/package.json create mode 100644 frontend-maven-plugin/src/it/corepack-provided-integration/pom.xml create mode 100644 frontend-maven-plugin/src/it/corepack-provided-integration/verify.groovy diff --git a/README.md b/README.md index cec10f28..908678b2 100644 --- a/README.md +++ b/README.md @@ -176,16 +176,19 @@ https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plu ### Installing node and corepack You can choose to let corepack manage the package manager version in use. Node is -downloaded from `https://nodejs.org/dist`, and corepack currently comes from -`https://repository.npmjs.org`, extracted and put into a `node` folder created -in your installation directory. +downloaded from `https://nodejs.org/dist`, and corepack either comes provided with +Node, or will currently be downloaded from `https://repository.npmjs.org`, extracted +and put into a `node` folder created in your installation directory. Node/corepack and any package managers will only be "installed" locally to your project. It will not be installed globally on the whole system (and it will not interfere with any Node/corepack installations already present). Have a look at the example `POM` to see how it should be set up with corepack: +https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plugin/src/it/corepack-provided-integration/pom.xml +or https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plugin/src/it/corepack-integration/pom.xml +if you need to override the version of corepack in use. ```xml @@ -202,6 +205,9 @@ https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plu v20.12.2 + + v0.25.2 diff --git a/frontend-maven-plugin/src/it/corepack-provided-integration/.yarnrc.yml b/frontend-maven-plugin/src/it/corepack-provided-integration/.yarnrc.yml new file mode 100644 index 00000000..0cd45f54 --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-provided-integration/.yarnrc.yml @@ -0,0 +1,3 @@ +enableGlobalCache: false + +nodeLinker: node-modules diff --git a/frontend-maven-plugin/src/it/corepack-provided-integration/package.json b/frontend-maven-plugin/src/it/corepack-provided-integration/package.json new file mode 100644 index 00000000..3519bdcf --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-provided-integration/package.json @@ -0,0 +1,8 @@ +{ + "name": "example", + "version": "0.0.1", + "dependencies": { + "less": "~3.0.2" + }, + "packageManager": "yarn@4.1.1" +} diff --git a/frontend-maven-plugin/src/it/corepack-provided-integration/pom.xml b/frontend-maven-plugin/src/it/corepack-provided-integration/pom.xml new file mode 100644 index 00000000..d831f966 --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-provided-integration/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + com.github.eirslett + example + 0 + pom + + + + + com.github.eirslett + frontend-maven-plugin + + @project.version@ + + + target + + + + + + install node and corepack + + install-node-and-corepack + + + v20.12.2 + + + + + yarn install + + corepack + + + yarn install --no-immutable + + + + + + + + diff --git a/frontend-maven-plugin/src/it/corepack-provided-integration/verify.groovy b/frontend-maven-plugin/src/it/corepack-provided-integration/verify.groovy new file mode 100644 index 00000000..1355cd8e --- /dev/null +++ b/frontend-maven-plugin/src/it/corepack-provided-integration/verify.groovy @@ -0,0 +1,6 @@ +assert new File(basedir, 'target/node').exists() : "Node was not installed in the custom install directory"; +assert new File(basedir, 'node_modules').exists() : "Node modules were not installed in the base directory"; +assert new File(basedir, 'node_modules/less/package.json').exists() : "Less dependency has not been installed successfully"; + +String buildLog = new File(basedir, 'build.log').text +assert buildLog.contains('BUILD SUCCESS') : 'build was not successful' diff --git a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java index 62ad433a..a8873261 100644 --- a/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java +++ b/frontend-maven-plugin/src/main/java/com/github/eirslett/maven/plugins/frontend/mojo/InstallNodeAndCorepackMojo.java @@ -3,6 +3,7 @@ import com.github.eirslett.maven.plugins.frontend.lib.CorepackInstaller; import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; import com.github.eirslett.maven.plugins.frontend.lib.InstallationException; +import com.github.eirslett.maven.plugins.frontend.lib.NodeInstaller; import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugins.annotations.Component; @@ -36,8 +37,10 @@ public final class InstallNodeAndCorepackMojo extends AbstractFrontendMojo { /** * The version of corepack to install. Note that the version string can optionally be prefixed with * 'v' (i.e., both 'v1.2.3' and '1.2.3' are valid). + * + * If not provided, then the corepack version bundled with Node will be used. */ - @Parameter(property = "corepackVersion", required = true) + @Parameter(property = "corepackVersion", required = false, defaultValue = "provided") private String corepackVersion; /** @@ -68,30 +71,30 @@ public void execute(FrontendPluginFactory factory) throws InstallationException ProxyConfig proxyConfig = MojoUtils.getProxyConfig(session, decrypter); String resolvedNodeDownloadRoot = getNodeDownloadRoot(); String resolvedCorepackDownloadRoot = getCorepackDownloadRoot(); + + // Setup the installers + NodeInstaller nodeInstaller = factory.getNodeInstaller(proxyConfig); + nodeInstaller.setNodeVersion(nodeVersion) + .setNodeDownloadRoot(resolvedNodeDownloadRoot); + if ("provided".equals(corepackVersion)) { + // This causes the node installer to copy over the whole + // node_modules directory including the corepack module + nodeInstaller.setNpmVersion("provided"); + } + CorepackInstaller corepackInstaller = factory.getCorepackInstaller(proxyConfig); + corepackInstaller.setCorepackVersion(corepackVersion) + .setCorepackDownloadRoot(resolvedCorepackDownloadRoot); + + // If pplicable, configure authentication details Server server = MojoUtils.decryptServer(serverId, session, decrypter); if (null != server) { - factory.getNodeInstaller(proxyConfig) - .setNodeVersion(nodeVersion) - .setNodeDownloadRoot(resolvedNodeDownloadRoot) - .setUserName(server.getUsername()) - .setPassword(server.getPassword()) - .install(); - factory.getCorepackInstaller(proxyConfig) - .setCorepackVersion(corepackVersion) - .setCorepackDownloadRoot(resolvedCorepackDownloadRoot) - .setUserName(server.getUsername()) - .setPassword(server.getPassword()) - .install(); - } else { - factory.getNodeInstaller(proxyConfig) - .setNodeVersion(nodeVersion) - .setNodeDownloadRoot(resolvedNodeDownloadRoot) - .install(); - factory.getCorepackInstaller(proxyConfig) - .setCorepackVersion(this.corepackVersion) - .setCorepackDownloadRoot(resolvedCorepackDownloadRoot) - .install(); + nodeInstaller.setUserName(server.getUsername()).setPassword(server.getPassword()); + corepackInstaller.setUserName(server.getUsername()).setPassword(server.getPassword()); } + + // Perform the installation + nodeInstaller.install(); + corepackInstaller.install(); } private String getNodeDownloadRoot() { diff --git a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java index 4dbef0f9..ce7fa8c8 100644 --- a/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java +++ b/frontend-plugin-core/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/CorepackInstaller.java @@ -82,6 +82,11 @@ private boolean corepackIsAlreadyInstalled() { final File corepackPackageJson = new File( this.config.getInstallDirectory() + Utils.normalize("/node/node_modules/corepack/package.json")); if (corepackPackageJson.exists()) { + if ("provided".equals(this.corepackVersion)) { + // Since we don't know which version it should be, we must assume that we have + // correctly setup the packaged version + return true; + } HashMap data = new ObjectMapper().readValue(corepackPackageJson, HashMap.class); if (data.containsKey(VERSION)) { final String foundCorepackVersion = data.get(VERSION).toString();