Skip to content

Commit

Permalink
Implement package manager abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrdom committed May 15, 2022
1 parent 8bd7b4f commit 0b635f7
Show file tree
Hide file tree
Showing 23 changed files with 261 additions and 198 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
node-version: 16.14.2
- name: Setup yarn
run: npm install -g [email protected]
- name: Setup pnpm
run: npm install -g [email protected]
- name: Unit tests
run: sbt test
- name: Scripted tests
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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))
): _*
Expand Down
163 changes: 163 additions & 0 deletions sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageManager.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package scalajsbundler

import java.io.File

import sbt._
import scalajsbundler.util.Commands
import scalajsbundler.util.JSON

abstract class PackageManager {

val name: String
val installCommand: String
val installArgs: Seq[String]

/**
* Runs the command `cmd`
* @param args Command arguments
* @param workingDir Working directory of the process
* @param logger Logger
*/
protected 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 _ =>
()
}
}

val packageJsonContents: Map[String, JSON]
}

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", "[email protected]")
*/
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)
}
}
}

case class Npm(
name: String = "npm",
lockFileName: String = "package-lock.json",
installCommand: String = "install",
installArgs: Seq[String] = Seq.empty,
addPackagesCommand: String = "install",
addPackagesArgs: Seq[String] = Seq.empty,
) extends PackageManager
with LockFileSupport
with AddPackagesSupport {
override val packageJsonContents: Map[String, JSON] = Map.empty
}

case class Yarn(
name: String = "yarn",
version: Option[String] = None,
lockFileName: String = "yarn.lock",
installCommand: String = "install",
installArgs: Seq[String] = Yarn.DefaultArgs,
addPackagesCommand: String = "add",
addPackagesArgs: Seq[String] = Yarn.DefaultArgs
) extends PackageManager
with LockFileSupport
with AddPackagesSupport {
override val packageJsonContents: Map[String, JSON] =
version.map(v => Map("packageManager" -> JSON.str(s"$name@$v"))).getOrElse(Map.empty)
}
object Yarn {
val DefaultArgs: Seq[String] = Seq("--non-interactive", "--mutex", "network")
}

case class Pnpm(
name: String = "pnpm",
version: Option[String] = None,
lockFileName: String = "pnpm-lock.yaml",
installCommand: String = "install",
installArgs: Seq[String] = Seq.empty,
addPackagesCommand: String = "add",
addPackagesArgs: Seq[String] = Seq.empty
) extends PackageManager
with LockFileSupport
with AddPackagesSupport {
override val packageJsonContents: Map[String, JSON] =
version.map(v => Map("packageManager" -> JSON.str(s"$name@$v"))).getOrElse(Map.empty)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package scalajsbundler.sbtplugin

import java.nio.file.Path
import scalajsbundler.ExternalCommand

import sbt._
import scalajsbundler.PackageManager

object NpmUpdateTasks {

Expand All @@ -21,12 +22,10 @@ object NpmUpdateTasks {
def npmUpdate(baseDir: File,
targetDir: File,
packageJsonFile: File,
useYarn: Boolean,
jsResources: Seq[(String, Path)],
streams: Keys.TaskStreams,
npmExtraArgs: Seq[String],
yarnExtraArgs: Seq[String]): File = {
val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, useYarn, streams, npmExtraArgs, yarnExtraArgs)
packageManager: PackageManager): File = {
val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, streams, packageManager)
npmInstallJSResources(targetDir, jsResources, Seq.empty, streams)
dir
}
Expand All @@ -45,18 +44,16 @@ object NpmUpdateTasks {
def npmInstallDependencies(baseDir: File,
targetDir: File,
packageJsonFile: File,
useYarn: Boolean,
streams: Keys.TaskStreams,
npmExtraArgs: Seq[String],
yarnExtraArgs: Seq[String]): File = {
packageManager: PackageManager): File = {
val log = streams.log
val cachedActionFunction =
FileFunction.cached(
streams.cacheDirectory / "scalajsbundler-npm-install",
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))
Expand Down
Loading

0 comments on commit 0b635f7

Please sign in to comment.