From 2d414ab7229836647d82e8241fc46a15b1722093 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Wed, 4 May 2022 21:23:45 +0300 Subject: [PATCH] Implement package manager abstraction --- .github/workflows/ci.yml | 6 +- manual/src/ornate/reference.md | 22 +- .../scalajsbundler/ExternalCommand.scala | 4 + .../scala/scalajsbundler/PackageJson.scala | 5 +- .../scala/scalajsbundler/PackageManager.scala | 252 ++++++++++++++++++ .../sbtplugin/NpmUpdateTasks.scala | 66 ++++- .../sbtplugin/PackageJsonTasks.scala | 11 +- .../sbtplugin/ScalaJSBundlerPlugin.scala | 42 ++- .../additonalNpmConfig/build.sbt | 1 + .../sbt-scalajs-bundler/npm/build.sbt | 11 + .../npm/project/plugins.sbt | 8 + .../npm/src/main/scala/example/Main.scala | 7 + .../src/sbt-test/sbt-scalajs-bundler/npm/test | 2 + .../sbt-scalajs-bundler/pnpm/build.sbt | 21 ++ .../pnpm/project/plugins.sbt | 8 + .../pnpm/src/main/scala/example/Main.scala | 7 + .../sbt-test/sbt-scalajs-bundler/pnpm/test | 2 + .../sharedconfig/build.sbt | 2 +- .../sbt-scalajs-bundler/static/build.sbt | 2 +- .../webpack-assets/build.sbt | 2 +- .../yarn-version/build.sbt | 25 ++ .../yarn-version/project/plugins.sbt | 8 + .../src/main/scala/example/Main.scala | 7 + .../sbt-scalajs-bundler/yarn-version/test | 3 + 24 files changed, 495 insertions(+), 29 deletions(-) create mode 100644 sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageManager.scala create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/build.sbt create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/project/plugins.sbt create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/src/main/scala/example/Main.scala create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/test create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/build.sbt create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/project/plugins.sbt create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/src/main/scala/example/Main.scala create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/test create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/build.sbt create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/project/plugins.sbt create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/src/main/scala/example/Main.scala create mode 100644 sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f31bbf2..ec818fd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,12 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 16.14.2 + - name: Enable Corepack + run: corepack enable - name: Setup yarn - run: npm install -g yarn@1.22.15 + run: corepack prepare yarn@1.22.15 --activate + - name: Setup pnpm + run: corepack prepare pnpm@7.0.1 --activate - name: Unit tests run: sbt test - name: Scripted tests diff --git a/manual/src/ornate/reference.md b/manual/src/ornate/reference.md index 12c8ff93..43101850 100644 --- a/manual/src/ornate/reference.md +++ b/manual/src/ornate/reference.md @@ -55,18 +55,24 @@ their execution so that they can be loaded by jsdom. You can find an example of project requiring the DOM for its tests [here](https://github.com/scalacenter/scalajs-bundler/blob/main/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/static/). -### Yarn {#yarn} +### Package managers -By default, `npm` is used to fetch the dependencies but you can use [Yarn](https://yarnpkg.com/) by setting the -`useYarn` key to `true`: +By default, `npm` is used to fetch the dependencies, but you can use [Yarn](https://yarnpkg.com/) by setting the +`jsPackageManager` key to `Yarn()`: -~~~ scala -useYarn := true -~~~ +``` scala +jsPackageManager := Yarn() +``` + +If your sbt (sub-)project directory contains a lockfile (`package-lock.json` for `npm` or `yarn.lock` for `yarn`), it will be used. Else, a new one will be created. +You should check the lockfile into source control. -If your sbt (sub-)project directory contains a `yarn.lock`, it will be used. Else, a new one will be created. You should check `yarn.lock` into source control. +Package manager behavior can be customized by passing your own [PackageManager](api:scalajsbundler.PackageManager) to the key. +You can use it to modify commands and their arguments for `npm` or `yarn`, or to set up new package managers like +[pnpm](https://pnpm.io/) (see example [here](https://github.com/scalacenter/scalajs-bundler/blob/main/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/)). -Yarn 0.22.0+ must be available on your machine. +Scalajs-bundler does not install your chosen package manager, it must be available on your machine. However, [Corepack](https://nodejs.org/api/corepack.html) +is supported - setting `Yarn.withVersion(Some("1.22.19"))` will modify underlying `package.json` with field `"packageManager": "yarn@1.22.19"`. ### Bundling Mode {#bundling-mode} diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala index 885483c6..8f9ea6b5 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala @@ -10,6 +10,7 @@ import scalajsbundler.util.Commands * * @param name Name of the command to run */ +@deprecated("Use jsPackageManager instead.") class ExternalCommand(name: String) { /** @@ -32,6 +33,7 @@ object Npm extends ExternalCommand("npm") object Yarn extends ExternalCommand("yarn") +@deprecated("Use jsPackageManager instead.") object ExternalCommand { private val yarnOptions = List("--non-interactive", "--mutex", "network") @@ -89,6 +91,7 @@ object ExternalCommand { * @param npmExtraArgs Additional arguments to pass to npm * @param npmPackages Packages to install (e.g. "webpack", "webpack@2.2.1") */ + @deprecated("Use jsPackageManager instead.") def addPackages(baseDir: File, installDir: File, useYarn: Boolean, @@ -107,6 +110,7 @@ object ExternalCommand { } } + @deprecated("Use jsPackageManager instead.") def install(baseDir: File, installDir: File, useYarn: Boolean, diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala index d8b34ab9..4f9fa07f 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala @@ -31,7 +31,8 @@ object PackageJson { currentConfiguration: Configuration, webpackVersion: String, webpackDevServerVersion: String, - webpackCliVersion: String + webpackCliVersion: String, + packageManager: PackageManager ): Unit = { val npmManifestDependencies = NpmDependencies.collectFromClasspath(fullClasspath) val dependencies = @@ -62,7 +63,7 @@ object PackageJson { val packageJson = JSON.obj( ( - additionalNpmConfig.toSeq :+ + (additionalNpmConfig.toSeq ++ packageManager.packageJsonContents.toSeq) :+ "dependencies" -> JSON.objStr(resolveDependencies(dependencies, npmResolutions, log)) :+ "devDependencies" -> JSON.objStr(resolveDependencies(devDependencies, npmResolutions, log)) ): _* diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageManager.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageManager.scala new file mode 100644 index 00000000..ea02aeb4 --- /dev/null +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageManager.scala @@ -0,0 +1,252 @@ +package scalajsbundler + +import java.io.File + +import sbt._ +import scalajsbundler.util.Commands +import scalajsbundler.util.JSON + +trait PackageManager { + + val name: String + + /** + * Runs the command `cmd` + * @param args Command arguments + * @param workingDir Working directory of the process + * @param logger Logger + */ + def run(args: String*)(workingDir: File, logger: Logger): Unit + + def install(baseDir: File, installDir: File, logger: Logger): Unit + + val packageJsonContents: Map[String, JSON] +} + +object PackageManager { + + abstract class ExternalProcess( + val name: String, + val installCommand: String, + val installArgs: Seq[String] + ) extends PackageManager { + + def run(args: String*)(workingDir: File, logger: Logger): Unit = + Commands.run(cmd ++: args, workingDir, logger) + + private val cmd = sys.props("os.name").toLowerCase match { + case os if os.contains("win") => Seq("cmd", "/c", name) + case _ => Seq(name) + } + + def install(baseDir: File, installDir: File, logger: Logger): Unit = { + this match { + case lfs: LockFileSupport => + lfs.lockFileRead(baseDir, installDir, logger) + case _ => + () + } + + run(installCommand +: installArgs: _*)(installDir, logger) + + this match { + case lfs: LockFileSupport => + lfs.lockFileWrite(baseDir, installDir, logger) + case _ => + () + } + } + } + + trait AddPackagesSupport { this: PackageManager => + + val addPackagesCommand: String + val addPackagesArgs: Seq[String] + + /** + * Locally install NPM packages + * + * @param baseDir The (sub-)project directory which contains yarn.lock + * @param installDir The directory in which to install the packages + * @param logger sbt logger + * @param npmPackages Packages to install (e.g. "webpack", "webpack@2.2.1") + */ + def addPackages(baseDir: File, + installDir: File, + logger: Logger, + )(npmPackages: String*): Unit = { + this match { + case lfs: LockFileSupport => + lfs.lockFileRead(baseDir, installDir, logger) + case _ => + () + } + + run(addPackagesCommand +: (addPackagesArgs ++ npmPackages): _*)(installDir, logger) + + this match { + case lfs: LockFileSupport => + lfs.lockFileWrite(baseDir, installDir, logger) + case _ => + () + } + } + } + + trait LockFileSupport { + val lockFileName: String + + def lockFileRead( + baseDir: File, + installDir: File, + logger: Logger + ): Unit = { + val sourceLockFile = baseDir / lockFileName + val targetLockFile = installDir / lockFileName + + if (sourceLockFile.exists()) { + logger.info("Using lockfile " + sourceLockFile) + IO.copyFile(sourceLockFile, targetLockFile) + } + } + + def lockFileWrite( + baseDir: File, + installDir: File, + logger: Logger + ): Unit = { + val sourceLockFile = baseDir / lockFileName + val targetLockFile = installDir / lockFileName + + if (targetLockFile.exists()) { + logger.debug("Wrote lockfile to " + sourceLockFile) + IO.copyFile(targetLockFile, sourceLockFile) + } + } + } + + final class Npm private ( + override val name: String, + val lockFileName: String, + override val installCommand: String, + override val installArgs: Seq[String], + val addPackagesCommand: String, + val addPackagesArgs: Seq[String], + ) extends ExternalProcess(name, installCommand, installArgs) + with LockFileSupport + with AddPackagesSupport { + + override val packageJsonContents: Map[String, JSON] = Map.empty + + private def this() = { + this( + name = "npm", + lockFileName = "package-lock.json", + installCommand = "install", + installArgs = Seq.empty, + addPackagesCommand = "install", + addPackagesArgs = Seq.empty + ) + } + + def withName(name: String): Npm = copy(name = name) + + def withLockFileName(lockFileName: String): Npm = copy(lockFileName = lockFileName) + + def withInstallCommand(installCommand: String): Npm = copy(installCommand = installCommand) + + def withInstallArgs(installArgs: Seq[String]): Npm = copy(installArgs = installArgs) + + def withAddPackagesCommand(addPackagesCommand: String): Npm = copy(addPackagesCommand = addPackagesCommand) + + def withAddPackagesArgs(addPackagesArgs: Seq[String]): Npm = copy(addPackagesArgs = addPackagesArgs) + + private def copy( + name: String = name, + lockFileName: String = lockFileName, + installCommand: String = installCommand, + installArgs: Seq[String] = installArgs, + addPackagesCommand: String = addPackagesCommand, + addPackagesArgs: Seq[String] = addPackagesArgs + ) = { + new Npm( + name, + lockFileName, + installCommand, + installArgs, + addPackagesCommand, + addPackagesArgs + ) + } + } + object Npm { + def apply() = new Npm() + } + + final class Yarn private ( + override val name: String, + val version: Option[String], + val lockFileName: String, + override val installCommand: String, + override val installArgs: Seq[String], + val addPackagesCommand: String, + val addPackagesArgs: Seq[String], + ) extends ExternalProcess(name, installCommand, installArgs) + with LockFileSupport + with AddPackagesSupport { + + override val packageJsonContents: Map[String, JSON] = + version.map(v => Map("packageManager" -> JSON.str(s"$name@$v"))).getOrElse(Map.empty) + + private def this() = { + this( + name = "yarn", + version = None, + lockFileName = "yarn.lock", + installCommand = "install", + installArgs = Seq.empty, + addPackagesCommand = "add", + addPackagesArgs = Seq.empty + ) + } + + def withName(name: String): Yarn = copy(name = name) + + def withVersion(version: Option[String]): Yarn = copy(version = version) + + def withLockFileName(lockFileName: String): Yarn = copy(lockFileName = lockFileName) + + def withInstallCommand(installCommand: String): Yarn = copy(installCommand = installCommand) + + def withInstallArgs(installArgs: Seq[String]): Yarn = copy(installArgs = installArgs) + + def withAddPackagesCommand(addPackagesCommand: String): Yarn = copy(addPackagesCommand = addPackagesCommand) + + def withAddPackagesArgs(addPackagesArgs: Seq[String]): Yarn = copy(addPackagesArgs = addPackagesArgs) + + private def copy( + name: String = name, + version: Option[String] = version, + lockFileName: String = lockFileName, + installCommand: String = installCommand, + installArgs: Seq[String] = installArgs, + addPackagesCommand: String = addPackagesCommand, + addPackagesArgs: Seq[String] = addPackagesArgs + ) = { + new Yarn( + name, + version, + lockFileName, + installCommand, + installArgs, + addPackagesCommand, + addPackagesArgs + ) + } + } + object Yarn { + val DefaultArgs: Seq[String] = Seq("--non-interactive", "--mutex", "network") + + def apply() = new Yarn() + } +} diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala index cb4d5612..b6a0d7f6 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala @@ -1,8 +1,9 @@ package scalajsbundler.sbtplugin import java.nio.file.Path -import scalajsbundler.ExternalCommand + import sbt._ +import scalajsbundler.PackageManager object NpmUpdateTasks { @@ -18,6 +19,7 @@ object NpmUpdateTasks { * @param yarnExtraArgs Additional arguments to pass to yarn * @return The written npm directory */ + @deprecated("Use overload with packageManager instead.") def npmUpdate(baseDir: File, targetDir: File, packageJsonFile: File, @@ -26,7 +28,37 @@ object NpmUpdateTasks { streams: Keys.TaskStreams, npmExtraArgs: Seq[String], yarnExtraArgs: Seq[String]): File = { - val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, useYarn, streams, npmExtraArgs, yarnExtraArgs) + npmUpdate( + baseDir, + targetDir, + packageJsonFile, + jsResources, + streams, + if (useYarn){ + PackageManager.Yarn().withInstallArgs(yarnExtraArgs).withAddPackagesArgs(yarnExtraArgs) + } else { + PackageManager.Npm().withInstallArgs(npmExtraArgs).withAddPackagesArgs(npmExtraArgs) + } + ) + } + + /** + * Uses package manager to install JavaScript resources as node packages. + * + * @param targetDir npm directory + * @param packageJsonFile Json file containing NPM dependencies + * @param jsResources A sequence of JavaScript resources + * @param streams A sbt TaskStream + * @param packageManager package manager + * @return The written npm directory + */ + def npmUpdate(baseDir: File, + targetDir: File, + packageJsonFile: File, + jsResources: Seq[(String, Path)], + streams: Keys.TaskStreams, + packageManager: PackageManager): File = { + val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, streams, packageManager) npmInstallJSResources(targetDir, jsResources, Seq.empty, streams) dir } @@ -42,6 +74,7 @@ object NpmUpdateTasks { * @param yarnExtraArgs Additional arguments to pass to yarn * @return The written npm directory */ + @deprecated("Use overload with packageManager instead.") def npmInstallDependencies(baseDir: File, targetDir: File, packageJsonFile: File, @@ -49,6 +82,33 @@ object NpmUpdateTasks { streams: Keys.TaskStreams, npmExtraArgs: Seq[String], yarnExtraArgs: Seq[String]): File = { + npmInstallDependencies( + baseDir, + targetDir, + packageJsonFile, + streams, + if (useYarn){ + PackageManager.Yarn().withInstallArgs(yarnExtraArgs).withAddPackagesArgs(yarnExtraArgs) + } else { + PackageManager.Npm().withInstallArgs(npmExtraArgs).withAddPackagesArgs(npmExtraArgs) + } + ) + } + + /** + * Runs install command of package manager. + * + * @param targetDir npm directory + * @param packageJsonFile Json file containing NPM dependencies + * @param streams A sbt TaskStream + * @param packageManager package manager + * @return The written npm directory + */ + def npmInstallDependencies(baseDir: File, + targetDir: File, + packageJsonFile: File, + streams: Keys.TaskStreams, + packageManager: PackageManager): File = { val log = streams.log val cachedActionFunction = FileFunction.cached( @@ -56,7 +116,7 @@ object NpmUpdateTasks { inStyle = FilesInfo.hash ) { _ => log.info("Updating NPM dependencies") - ExternalCommand.install(baseDir, targetDir, useYarn, log, npmExtraArgs, yarnExtraArgs) + packageManager.install(baseDir, targetDir, log) Set.empty } cachedActionFunction(Set(packageJsonFile)) diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala index e9754efd..dd7650a8 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala @@ -1,7 +1,7 @@ package scalajsbundler.sbtplugin import sbt._ - +import scalajsbundler.PackageManager import scalajsbundler.{BundlerFile, PackageJson} import scalajsbundler.util.{Caching, JSON} @@ -31,7 +31,8 @@ object PackageJsonTasks { webpackVersion: String, webpackDevServerVersion: String, webpackCliVersion: String, - streams: Keys.TaskStreams + streams: Keys.TaskStreams, + packageManager: PackageManager ): BundlerFile.PackageJson = { val hash = Seq( @@ -42,7 +43,8 @@ object PackageJsonTasks { fullClasspath.map(_.data.name).toString, webpackVersion, webpackDevServerVersion, - webpackCliVersion + webpackCliVersion, + packageManager.toString ).mkString(",") val packageJsonFile = targetDir / "package.json" @@ -63,7 +65,8 @@ object PackageJsonTasks { configuration, webpackVersion, webpackDevServerVersion, - webpackCliVersion + webpackCliVersion, + packageManager ) () } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala index fdda11e5..8d29521c 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala @@ -4,9 +4,9 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ import org.scalajs.sbtplugin.{ScalaJSPlugin, Stage} import sbt.Keys._ import sbt.{Def, _} +import scalajsbundler.PackageManager.AddPackagesSupport import scalajsbundler.{BundlerFile, NpmDependencies, Webpack, WebpackDevServer} - -import scalajsbundler.ExternalCommand.addPackages +import scalajsbundler.PackageManager import scalajsbundler.util.{JSON, ScalaJSNativeLibraries} @@ -212,6 +212,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { * * @group settings */ + @deprecated("Use jsPackageManager instead.") val npmExtraArgs = SettingKey[Seq[String]]( "npmExtraArgs", "Custom arguments for npm" @@ -405,9 +406,22 @@ object ScalaJSBundlerPlugin extends AutoPlugin { * * @group settings */ + @deprecated("Use jsPackageManager instead.") val useYarn: SettingKey[Boolean] = settingKey[Boolean]("Whether to use yarn for updates") + /** + * Sets package manager to be used for installation of npm dependencies. + * + * Defaults to `Npm`. + * + * @group settings + */ + val jsPackageManager = SettingKey[PackageManager]( + "packageManager", + "Package manager which will be used for fetching dependencies. Constructor also allows definition of extra arguments." + ) + /** * Port, on which webpack-dev-server will be launched. * @@ -428,6 +442,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { * * @group settings */ + @deprecated("Use jsPackageManager instead.") val yarnExtraArgs = SettingKey[Seq[String]]( "yarnExtraArgs", "Custom arguments for yarn" @@ -570,6 +585,14 @@ object ScalaJSBundlerPlugin extends AutoPlugin { useYarn := false, + jsPackageManager := ( + if (useYarn.value){ + PackageManager.Yarn().withInstallArgs(yarnExtraArgs.value).withAddPackagesArgs(yarnExtraArgs.value) + } else { + PackageManager.Npm().withInstallArgs(npmExtraArgs.value).withAddPackagesArgs(npmExtraArgs.value) + } + ), + ensureModuleKindIsCommonJSModule := { if (scalaJSLinkerConfig.value.moduleKind == ModuleKind.CommonJSModule) true else sys.error(s"scalaJSModuleKind must be set to ModuleKind.CommonJSModule in projects where ScalaJSBundler plugin is enabled") @@ -612,7 +635,12 @@ object ScalaJSBundlerPlugin extends AutoPlugin { if (!jsdomDir.exists()) { log.info(s"Installing jsdom in ${installDir.absolutePath}") IO.createDirectory(installDir) - addPackages(baseDir, installDir, useYarn.value, log, npmExtraArgs.value, yarnExtraArgs.value)(s"jsdom@$jsdomVersion") + jsPackageManager.value match { + case aps: AddPackagesSupport => + aps.addPackages(baseDir, installDir, log)(s"jsdom@$jsdomVersion") + case unsupported => + throw new RuntimeException(s"Package manager (${unsupported.name}) used by this module does not support adding of packages") + } } installDir } @@ -644,10 +672,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { baseDirectory.value, (crossTarget in npmUpdate).value, scalaJSBundlerPackageJson.value.file, - useYarn.value, - streams.value, - npmExtraArgs.value, - yarnExtraArgs.value), + streams.value, jsPackageManager.value), npmInstallJSResources := NpmUpdateTasks.npmInstallJSResources( (crossTarget in npmUpdate).value, @@ -667,7 +692,8 @@ object ScalaJSBundlerPlugin extends AutoPlugin { (version in webpack).value, (version in startWebpackDevServer).value, webpackCliVersion.value, - streams.value + streams.value, + jsPackageManager.value ), diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/additonalNpmConfig/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/additonalNpmConfig/build.sbt index d0929914..066c7f25 100644 --- a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/additonalNpmConfig/build.sbt +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/additonalNpmConfig/build.sbt @@ -1,4 +1,5 @@ import scalajsbundler.util.JSON._ +import scalajsbundler.Npm val checkPackageJson = taskKey[Unit]("Check that the package.json file does not contain duplicate entries for the 'react' dependency") diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/build.sbt new file mode 100644 index 00000000..a0507447 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/build.sbt @@ -0,0 +1,11 @@ +scalaVersion := "2.12.8" + +jsPackageManager := scalajsbundler.PackageManager.Npm() + +scalaJSUseMainModuleInitializer := true + +npmDependencies in Compile += "neat" -> "2.1.0" + +enablePlugins(ScalaJSBundlerPlugin) + +ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/project/plugins.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/project/plugins.sbt new file mode 100644 index 00000000..77ac06f1 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/project/plugins.sbt @@ -0,0 +1,8 @@ +val scalaJSVersion = sys.props.getOrElse("scalajs.version", sys.error("'scalajs.version' environment variable is not defined")) +val scalaJSBundlerVersion = sys.props.getOrElse("plugin.version", sys.error("'plugin.version' environment variable is not set")) + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) + +addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % scalaJSBundlerVersion) + +ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/src/main/scala/example/Main.scala b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/src/main/scala/example/Main.scala new file mode 100644 index 00000000..5e8ae3ed --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/src/main/scala/example/Main.scala @@ -0,0 +1,7 @@ +package example + +object Main { + def main(args: Array[String]): Unit = { + println("npm main") + } +} diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/test b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/test new file mode 100644 index 00000000..9c6c1a39 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/npm/test @@ -0,0 +1,2 @@ +> fastOptJS::webpack +$ exists package-lock.json diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/build.sbt new file mode 100644 index 00000000..451d1799 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/build.sbt @@ -0,0 +1,21 @@ +import scalajsbundler.PackageManager +import scalajsbundler.util.JSON + +scalaVersion := "2.12.8" + +jsPackageManager := new PackageManager.ExternalProcess("pnpm", "install", Seq.empty) + with PackageManager.LockFileSupport + with PackageManager.AddPackagesSupport { + val lockFileName: String = "pnpm-lock.yaml" + val addPackagesCommand: String = "add" + val addPackagesArgs: Seq[String] = Seq.empty + val packageJsonContents: Map[String, JSON] = Map.empty + } + +scalaJSUseMainModuleInitializer := true + +npmDependencies in Compile += "neat" -> "2.1.0" + +enablePlugins(ScalaJSBundlerPlugin) + +ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/project/plugins.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/project/plugins.sbt new file mode 100644 index 00000000..77ac06f1 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/project/plugins.sbt @@ -0,0 +1,8 @@ +val scalaJSVersion = sys.props.getOrElse("scalajs.version", sys.error("'scalajs.version' environment variable is not defined")) +val scalaJSBundlerVersion = sys.props.getOrElse("plugin.version", sys.error("'plugin.version' environment variable is not set")) + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) + +addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % scalaJSBundlerVersion) + +ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/src/main/scala/example/Main.scala b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/src/main/scala/example/Main.scala new file mode 100644 index 00000000..6aab86d7 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/src/main/scala/example/Main.scala @@ -0,0 +1,7 @@ +package example + +object Main { + def main(args: Array[String]): Unit = { + println("pnpm main") + } +} diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/test b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/test new file mode 100644 index 00000000..6a170d44 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/pnpm/test @@ -0,0 +1,2 @@ +> fastOptJS::webpack +$ exists pnpm-lock.yaml \ No newline at end of file diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/sharedconfig/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/sharedconfig/build.sbt index d4d2b773..c0b2d46b 100644 --- a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/sharedconfig/build.sbt +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/sharedconfig/build.sbt @@ -33,7 +33,7 @@ requireJsDomEnv in Test := true webpackBundlingMode in fastOptJS := BundlingMode.LibraryAndApplication() -useYarn := true +jsPackageManager := scalajsbundler.PackageManager.Yarn() // HtmlUnit does not support ECMAScript 2015 scalaJSLinkerConfig ~= { _.withESFeatures(_.withUseECMAScript2015(false)) } diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/static/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/static/build.sbt index 5498802e..478e4025 100644 --- a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/static/build.sbt +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/static/build.sbt @@ -17,7 +17,7 @@ version in installJsdom := "16.4.0" webpackBundlingMode := BundlingMode.LibraryAndApplication() -useYarn := true +jsPackageManager := scalajsbundler.PackageManager.Yarn() // HtmlUnit does not support ECMAScript 2015 scalaJSLinkerConfig ~= { _.withESFeatures(_.withUseECMAScript2015(false)) } diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/webpack-assets/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/webpack-assets/build.sbt index 3d57c472..c3cbbb6c 100644 --- a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/webpack-assets/build.sbt +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/webpack-assets/build.sbt @@ -31,7 +31,7 @@ npmDevDependencies in Compile += "mini-css-extract-plugin" -> "1.3.4" webpackDevServerPort := 7357 -useYarn := true +jsPackageManager := scalajsbundler.PackageManager.Yarn() // HtmlUnit does not support ECMAScript 2015 scalaJSLinkerConfig ~= { _.withESFeatures(_.withUseECMAScript2015(false)) } diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/build.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/build.sbt new file mode 100644 index 00000000..50add51f --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/build.sbt @@ -0,0 +1,25 @@ +scalaVersion := "2.12.8" + +lazy val yarnVersion = "1.22.16" + +jsPackageManager := scalajsbundler.PackageManager.Yarn().withVersion(Some(yarnVersion)) + +scalaJSUseMainModuleInitializer := true + +npmDependencies in Compile += "neat" -> "2.1.0" + +enablePlugins(ScalaJSBundlerPlugin) + +ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet + +TaskKey[Unit]("check-yarn-version") := { + import scala.sys.process.Process + val cmd = sys.props("os.name").toLowerCase match { + case os if os.contains("win") => "powershell yarn" + case _ => "yarn" + } + val process = Process(s"$cmd -v", new File("target/scala-2.12/scalajs-bundler/main")) + val out = (process!!) + if(out.trim != yarnVersion) sys.error(s"unexpected yarn version: ${out.trim}") + () +} diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/project/plugins.sbt b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/project/plugins.sbt new file mode 100644 index 00000000..77ac06f1 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/project/plugins.sbt @@ -0,0 +1,8 @@ +val scalaJSVersion = sys.props.getOrElse("scalajs.version", sys.error("'scalajs.version' environment variable is not defined")) +val scalaJSBundlerVersion = sys.props.getOrElse("plugin.version", sys.error("'plugin.version' environment variable is not set")) + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) + +addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % scalaJSBundlerVersion) + +ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/src/main/scala/example/Main.scala b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/src/main/scala/example/Main.scala new file mode 100644 index 00000000..41033196 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/src/main/scala/example/Main.scala @@ -0,0 +1,7 @@ +package example + +object Main { + def main(args: Array[String]): Unit = { + println("yarn-version main") + } +} diff --git a/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/test b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/test new file mode 100644 index 00000000..272ac667 --- /dev/null +++ b/sbt-scalajs-bundler/src/sbt-test/sbt-scalajs-bundler/yarn-version/test @@ -0,0 +1,3 @@ +> fastOptJS::webpack +> checkYarnVersion +$ exists yarn.lock