Skip to content

Commit 0b635f7

Browse files
committed
Implement package manager abstraction
1 parent 8bd7b4f commit 0b635f7

File tree

23 files changed

+261
-198
lines changed

23 files changed

+261
-198
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ jobs:
2020
node-version: 16.14.2
2121
- name: Setup yarn
2222
run: npm install -g [email protected]
23+
- name: Setup pnpm
24+
run: npm install -g [email protected]
2325
- name: Unit tests
2426
run: sbt test
2527
- name: Scripted tests

sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala

Lines changed: 0 additions & 126 deletions
This file was deleted.

sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ object PackageJson {
3131
currentConfiguration: Configuration,
3232
webpackVersion: String,
3333
webpackDevServerVersion: String,
34-
webpackCliVersion: String
34+
webpackCliVersion: String,
35+
packageManager: PackageManager
3536
): Unit = {
3637
val npmManifestDependencies = NpmDependencies.collectFromClasspath(fullClasspath)
3738
val dependencies =
@@ -62,7 +63,7 @@ object PackageJson {
6263
val packageJson =
6364
JSON.obj(
6465
(
65-
additionalNpmConfig.toSeq :+
66+
(additionalNpmConfig.toSeq ++ packageManager.packageJsonContents.toSeq) :+
6667
"dependencies" -> JSON.objStr(resolveDependencies(dependencies, npmResolutions, log)) :+
6768
"devDependencies" -> JSON.objStr(resolveDependencies(devDependencies, npmResolutions, log))
6869
): _*
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package scalajsbundler
2+
3+
import java.io.File
4+
5+
import sbt._
6+
import scalajsbundler.util.Commands
7+
import scalajsbundler.util.JSON
8+
9+
abstract class PackageManager {
10+
11+
val name: String
12+
val installCommand: String
13+
val installArgs: Seq[String]
14+
15+
/**
16+
* Runs the command `cmd`
17+
* @param args Command arguments
18+
* @param workingDir Working directory of the process
19+
* @param logger Logger
20+
*/
21+
protected def run(args: String*)(workingDir: File, logger: Logger): Unit =
22+
Commands.run(cmd ++: args, workingDir, logger)
23+
24+
private val cmd = sys.props("os.name").toLowerCase match {
25+
case os if os.contains("win") => Seq("cmd", "/c", name)
26+
case _ => Seq(name)
27+
}
28+
29+
def install(baseDir: File,
30+
installDir: File,
31+
logger: Logger): Unit = {
32+
this match {
33+
case lfs: LockFileSupport =>
34+
lfs.lockFileRead(baseDir, installDir, logger)
35+
case _ =>
36+
()
37+
}
38+
39+
run(installCommand +: installArgs: _*)(installDir, logger)
40+
41+
this match {
42+
case lfs: LockFileSupport =>
43+
lfs.lockFileWrite(baseDir, installDir, logger)
44+
case _ =>
45+
()
46+
}
47+
}
48+
49+
val packageJsonContents: Map[String, JSON]
50+
}
51+
52+
trait AddPackagesSupport { this: PackageManager =>
53+
54+
val addPackagesCommand: String
55+
val addPackagesArgs: Seq[String]
56+
57+
/**
58+
* Locally install NPM packages
59+
*
60+
* @param baseDir The (sub-)project directory which contains yarn.lock
61+
* @param installDir The directory in which to install the packages
62+
* @param logger sbt logger
63+
* @param npmPackages Packages to install (e.g. "webpack", "[email protected]")
64+
*/
65+
def addPackages(baseDir: File,
66+
installDir: File,
67+
logger: Logger,
68+
)(npmPackages: String*): Unit = {
69+
this match {
70+
case lfs: LockFileSupport =>
71+
lfs.lockFileRead(baseDir, installDir, logger)
72+
case _ =>
73+
()
74+
}
75+
76+
run(addPackagesCommand +: (addPackagesArgs ++ npmPackages): _*)(installDir, logger)
77+
78+
this match {
79+
case lfs: LockFileSupport =>
80+
lfs.lockFileWrite(baseDir, installDir, logger)
81+
case _ =>
82+
()
83+
}
84+
}
85+
}
86+
87+
trait LockFileSupport {
88+
val lockFileName: String
89+
90+
def lockFileRead(
91+
baseDir: File,
92+
installDir: File,
93+
logger: Logger
94+
): Unit = {
95+
val sourceLockFile = baseDir / lockFileName
96+
val targetLockFile = installDir / lockFileName
97+
98+
if (sourceLockFile.exists()) {
99+
logger.info("Using lockfile " + sourceLockFile)
100+
IO.copyFile(sourceLockFile, targetLockFile)
101+
}
102+
}
103+
104+
def lockFileWrite(
105+
baseDir: File,
106+
installDir: File,
107+
logger: Logger
108+
): Unit = {
109+
val sourceLockFile = baseDir / lockFileName
110+
val targetLockFile = installDir / lockFileName
111+
112+
if (targetLockFile.exists()) {
113+
logger.debug("Wrote lockfile to " + sourceLockFile)
114+
IO.copyFile(targetLockFile, sourceLockFile)
115+
}
116+
}
117+
}
118+
119+
case class Npm(
120+
name: String = "npm",
121+
lockFileName: String = "package-lock.json",
122+
installCommand: String = "install",
123+
installArgs: Seq[String] = Seq.empty,
124+
addPackagesCommand: String = "install",
125+
addPackagesArgs: Seq[String] = Seq.empty,
126+
) extends PackageManager
127+
with LockFileSupport
128+
with AddPackagesSupport {
129+
override val packageJsonContents: Map[String, JSON] = Map.empty
130+
}
131+
132+
case class Yarn(
133+
name: String = "yarn",
134+
version: Option[String] = None,
135+
lockFileName: String = "yarn.lock",
136+
installCommand: String = "install",
137+
installArgs: Seq[String] = Yarn.DefaultArgs,
138+
addPackagesCommand: String = "add",
139+
addPackagesArgs: Seq[String] = Yarn.DefaultArgs
140+
) extends PackageManager
141+
with LockFileSupport
142+
with AddPackagesSupport {
143+
override val packageJsonContents: Map[String, JSON] =
144+
version.map(v => Map("packageManager" -> JSON.str(s"$name@$v"))).getOrElse(Map.empty)
145+
}
146+
object Yarn {
147+
val DefaultArgs: Seq[String] = Seq("--non-interactive", "--mutex", "network")
148+
}
149+
150+
case class Pnpm(
151+
name: String = "pnpm",
152+
version: Option[String] = None,
153+
lockFileName: String = "pnpm-lock.yaml",
154+
installCommand: String = "install",
155+
installArgs: Seq[String] = Seq.empty,
156+
addPackagesCommand: String = "add",
157+
addPackagesArgs: Seq[String] = Seq.empty
158+
) extends PackageManager
159+
with LockFileSupport
160+
with AddPackagesSupport {
161+
override val packageJsonContents: Map[String, JSON] =
162+
version.map(v => Map("packageManager" -> JSON.str(s"$name@$v"))).getOrElse(Map.empty)
163+
}

sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package scalajsbundler.sbtplugin
22

33
import java.nio.file.Path
4-
import scalajsbundler.ExternalCommand
4+
55
import sbt._
6+
import scalajsbundler.PackageManager
67

78
object NpmUpdateTasks {
89

@@ -21,12 +22,10 @@ object NpmUpdateTasks {
2122
def npmUpdate(baseDir: File,
2223
targetDir: File,
2324
packageJsonFile: File,
24-
useYarn: Boolean,
2525
jsResources: Seq[(String, Path)],
2626
streams: Keys.TaskStreams,
27-
npmExtraArgs: Seq[String],
28-
yarnExtraArgs: Seq[String]): File = {
29-
val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, useYarn, streams, npmExtraArgs, yarnExtraArgs)
27+
packageManager: PackageManager): File = {
28+
val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, streams, packageManager)
3029
npmInstallJSResources(targetDir, jsResources, Seq.empty, streams)
3130
dir
3231
}
@@ -45,18 +44,16 @@ object NpmUpdateTasks {
4544
def npmInstallDependencies(baseDir: File,
4645
targetDir: File,
4746
packageJsonFile: File,
48-
useYarn: Boolean,
4947
streams: Keys.TaskStreams,
50-
npmExtraArgs: Seq[String],
51-
yarnExtraArgs: Seq[String]): File = {
48+
packageManager: PackageManager): File = {
5249
val log = streams.log
5350
val cachedActionFunction =
5451
FileFunction.cached(
5552
streams.cacheDirectory / "scalajsbundler-npm-install",
5653
inStyle = FilesInfo.hash
5754
) { _ =>
5855
log.info("Updating NPM dependencies")
59-
ExternalCommand.install(baseDir, targetDir, useYarn, log, npmExtraArgs, yarnExtraArgs)
56+
packageManager.install(baseDir, targetDir, log)
6057
Set.empty
6158
}
6259
cachedActionFunction(Set(packageJsonFile))

0 commit comments

Comments
 (0)