diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index 4128db090fe..e0ed4e50b09 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.gradle.tasks.USING_JS_IR_BACKEND_MESSAGE import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.jsCompilerType import org.jetbrains.kotlin.gradle.util.normalizePath +import org.junit.Test import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.condition.DisabledIf @@ -1040,4 +1041,15 @@ class GeneralKotlin2JsGradlePluginIT : KGPBaseTest() { build("checkIfLastModifiedNotNow", "--rerun-tasks") } } + + @DisplayName("Yarn.lock persistence") + @GradleTest + fun testYarnLockStore() = with(transformProjectWithPluginsDsl("cleanTask")) { + + build("assemble") { + assertSuccessful() + assertFileExists("kotlin-yarn.lock") + assert(fileInWorkingDir("kotlin-yarn.lock").readText() == fileInWorkingDir("build/js/yarn.lock").readText()) + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt index 8ae6895fac4..e32b472ab6d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt @@ -7,13 +7,11 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.attributes.Usage import org.gradle.api.plugins.BasePlugin -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages -import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName -import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin +import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies +import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask import org.jetbrains.kotlin.gradle.tasks.CleanDataTask @@ -48,7 +46,8 @@ open class YarnPlugin : Plugin { task.mustRunAfter(rootClean) } - tasks.named(KotlinNpmInstallTask.NAME).configure { + val kotlinNpmInstall = tasks.named(KotlinNpmInstallTask.NAME) + kotlinNpmInstall.configure { it.dependsOn(rootPackageJson) it.dependsOn(setupTask) } @@ -57,6 +56,56 @@ open class YarnPlugin : Plugin { it.cleanableStoreProvider = provider { yarnRootExtension.requireConfigured().cleanableStore } it.description = "Clean unused local yarn version" } + + val packageJsonUmbrella = nodeJs + .packageJsonUmbrellaTaskProvider + + yarnRootExtension.rootPackageJsonTaskProvider.configure { + it.dependsOn(packageJsonUmbrella) + } + + project.allprojects + .forEach { + it.tasks.implementing(RequiresNpmDependencies::class).all {} + } + + val storeYarnLock = tasks.register("kotlinStoreYarnLock") { + it.dependsOn(kotlinNpmInstall) + val yarnLock = nodeJs.rootPackageDir.resolve("yarn.lock") + + it.doLast { + copy { copy -> + copy.from(yarnLock) + copy.rename { yarnRootExtension.lockFileName } + copy.into(yarnRootExtension.lockFileDirectory) + } + } + + it.inputs.file(yarnLock).withPropertyName("inputFile") + it.outputs.file(yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName)).withPropertyName("outputFile") + } + + val restoreYarnLock = tasks.register("kotlinRestoreYarnLock") { + val lockFile = yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName) + it.onlyIf { + lockFile.exists() + } + it.doLast { + copy { copy -> + copy.from(lockFile) + copy.rename { "yarn.lock" } + copy.into(nodeJs.rootPackageDir) + } + } + + it.inputs.file(lockFile).withPropertyName("inputFile") + it.outputs.file(nodeJs.rootPackageDir.resolve("yarn.lock")).withPropertyName("outputFile") + } + + kotlinNpmInstall.configure { + it.dependsOn(restoreYarnLock) + it.finalizedBy(storeYarnLock) + } } companion object { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt index ba1825a8377..6fdf50caac4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore +import java.io.File open class YarnRootExtension( @Transient @@ -38,6 +39,8 @@ open class YarnRootExtension( var command by Property("yarn") var download by Property(true) + var lockFileName by Property("kotlin-yarn.lock") + var lockFileDirectory: File by Property(project.rootDir) val yarnSetupTaskProvider: TaskProvider get() = project.tasks