diff --git a/.travis.yml b/.travis.yml index 6e4d548f..7d0e7e1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,57 @@ -language: java -install: true - -jdk: - - oraclejdk8 - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ - -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - - $HOME/.sonar/cache - -addons: - sonarcloud: - organization: siouan - token: - secure: "GFJCds9c7LcfVZE82Tz9t7F6bsPGDIX8KtDAC2m+mUzUWst8fVDvTBJ7bqGGWAMN6bc+Uot5WI5wGW15e87j1z3RtLkglVpoBgsyIrFLQY69BpjByGvw4010EU0ulEHsT93YjUEyugMxBV6zdsuqsP8vZCMMkl0oI+i3Lj1YlTabMKFWw3QyQlkpIoQo3C7c5puJSDTh0dBxtd7w1lvPEtZLXxqe7B8mUvqyDfMLFmU8bHIVMvrmlxsUvXD8qkCJQJy3XIr+XW8tGc9pIad9C2t8eUj9OE87yqp87eOGoQdbdFp98/GKaqBe+4rVRZFXkjfhM3ayLCDwTiWBYVoGCB0MlNz7WdGD+pUWTaYMD59yhFjSLnEM3+XBb0jqeviZEMCRH14/cDhXA74h6n+/cjzIks3pEIFpFnvK6XQVXTHA6poPvfnfylbD8dpiS51Vpf9LWvVf1fmRb0M7L3WO3Ar/QFydAL2mDF/ibSuYbvZAQ3xVLz+5XTg0QkJnGItfKxI+W/3tkVahgqXVh6BGFzCwB5kTgMjgZ6eij25i3f7N9W4j4wBxIsQXc9JEqP0ZBBafSASmC4cHUyOrTOfYfeEqDeOZA6exeVRdLPiwLL1gjjzq5ru6Ii90oCzBQHeSeOhYOvUQl9gMXduzX5cpSqHCGFpMYynmrDmsz1D94Sk=" - -script: - - ./gradlew build jacocoTestReport --scan -s - - sonar-scanner +matrix: + include: + - os: linux + dist: trusty + jdk: oraclejdk8 + script: + - ./gradlew check jacocoTestReport --scan + - sonar-scanner + - os: linux + dist: xenial + jdk: openjdk8 + - os: osx + osx_image: xcode9.4 + jdk: oraclejdk11 + +matrix: + include: + - os: linux + dist: xenial + jdk: openjdk8 + script: + - ./gradlew check jacocoTestReport --scan + - sonar-scanner + - os: linux + dist: trusty + jdk: oraclejdk8 + - os: osx + osx_image: xcode9.4 + jdk: oraclejdk11 + +language: java + +jdk: + - oraclejdk8 + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + - $HOME/.sonar/cache + +git: + depth: 3 + +addons: + sonarcloud: + organization: siouan + token: + secure: "GFJCds9c7LcfVZE82Tz9t7F6bsPGDIX8KtDAC2m+mUzUWst8fVDvTBJ7bqGGWAMN6bc+Uot5WI5wGW15e87j1z3RtLkglVpoBgsyIrFLQY69BpjByGvw4010EU0ulEHsT93YjUEyugMxBV6zdsuqsP8vZCMMkl0oI+i3Lj1YlTabMKFWw3QyQlkpIoQo3C7c5puJSDTh0dBxtd7w1lvPEtZLXxqe7B8mUvqyDfMLFmU8bHIVMvrmlxsUvXD8qkCJQJy3XIr+XW8tGc9pIad9C2t8eUj9OE87yqp87eOGoQdbdFp98/GKaqBe+4rVRZFXkjfhM3ayLCDwTiWBYVoGCB0MlNz7WdGD+pUWTaYMD59yhFjSLnEM3+XBb0jqeviZEMCRH14/cDhXA74h6n+/cjzIks3pEIFpFnvK6XQVXTHA6poPvfnfylbD8dpiS51Vpf9LWvVf1fmRb0M7L3WO3Ar/QFydAL2mDF/ibSuYbvZAQ3xVLz+5XTg0QkJnGItfKxI+W/3tkVahgqXVh6BGFzCwB5kTgMjgZ6eij25i3f7N9W4j4wBxIsQXc9JEqP0ZBBafSASmC4cHUyOrTOfYfeEqDeOZA6exeVRdLPiwLL1gjjzq5ru6Ii90oCzBQHeSeOhYOvUQl9gMXduzX5cpSqHCGFpMYynmrDmsz1D94Sk=" + +script: + - ./gradlew check jacocoTestReport --scan -s + - sonar-scanner diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c7a9eb0..6438637e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,20 +17,22 @@ directly. - Apart from your preferred IDE, no other tools is required. - Use the integrated Gradle Wrapper executable `gradlew` to execute development tasks apart from an IDE. -- It is an objective to keep the plugin independent, small. No dependencies shall be added without approbation. -- All packages, classes, methods shall have a relevant documentation. A relevant documentation provides all information -to identify the responsibility and behaviour of the class/method, such as developers don't have to inspect the code to +- It is a requirement to keep the plugin independent, small. No dependencies shall be added without approbation. +- All packages, classes, methods shall have a relevant documentation. A relevant documentation provides information to +identify the responsibility and behaviour of the class/method, such as developers don't have to inspect the code to understand how to use it. - Prefer adding relevant documentation directly in the code instead of creating an implementation document, to guarantee accessibility for developers. - Designing automated unit tests with a good coverage for classes tightly coupled with Gradle API is not a trivial task, due to the design of this API. That's why all business processes in the plugin shall remain the most independent possible, away from this API, with an appropriate level of abstraction. -- This separation between classes tightly coupled with Gradle API and classes containing business processes is -implemented with 2 packages: `org.siouan.frontendgradleplugin.tasks` and `org.siouan.frontendgradleplugin.core`. -- Unit tests shall be written for all classes. Code coverage with unit tests shall be the highest possible, to avoid -unknown behaviours at execution, and improve software maintainability, predictability, and reliability. -- Functional tests shall be written for each task class. +- This separation between classes tightly coupled with Gradle API (inheritance) and classes containing business +processes is implemented with 2 packages: `org.siouan.frontendgradleplugin.tasks` and +`org.siouan.frontendgradleplugin.core`. +- Unit tests shall be written for each class in the `org.siouan.frontendgradleplugin.tasks` package. Code coverage with +unit tests shall be the highest possible, to avoid unknown behaviours at execution, and improve software +maintainability, predictability, and reliability. +- Functional tests shall be written for each class in the `org.siouan.frontendgradleplugin.tasks` package. ### Executing functional tests from an IDE @@ -52,4 +54,20 @@ gradlew pluginUnderTestMetadata 'Fixed #'. - The automated test suite must be run and no test case must fail before any commit. +### Continuous integration + +The project relies on [Travis CI Open Source][travis] to integrate continuously every change, pull request, in the +repository. The configuration actually allows to build and test the plugin on the environments below: + +- Linux Ubuntu Xenial Xerus 16.04.6 LTS / OpenJDK 1.8.0_191 64 bits +- Linux Ubuntu Trusty Tahr 14.04.5 LTS / OracleJDK 1.8.0_151 64 bits +- Mac OS X 10.13 / OracleJDK 11.0.2 LTS 64 bits + +Ubuntu Xenial is the reference environment, used to analyze the source code with SonarCloud. By now, the plugin has been +developed on Windows 10 Home with OracleJDK 1.8.0_202 64 bits and [JetBrains IntelliJ IDEA][intellij]. + +*Note: continuous integration of Java projects on Windows is not supported by Travis yet.* + +[intellij]: (IntelliJ IDEA) [issues]: (Issues) +[travis]: (Travis CI Open Source) diff --git a/README.md b/README.md index 0a75ca27..2225f9bd 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,22 @@ # Frontend Gradle plugin -[![Latest release 1.1.1](https://img.shields.io/badge/Latest%20release-1.1.1-blue.svg)](https://github.com/Siouan/frontend-gradle-plugin/releases/tag/v1.1.1) +[![Latest release 1.1.2](https://img.shields.io/badge/Latest%20release-1.1.2-blue.svg)](https://github.com/Siouan/frontend-gradle-plugin/releases/tag/v1.1.2) [![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)](https://opensource.org/licenses/Apache-2.0) -[![Build status](https://travis-ci.org/Siouan/frontend-gradle-plugin.svg?branch=master)](https://travis-ci.org/Siouan/frontend-gradle-plugin) +[![Build status](https://travis-ci.org/Siouan/frontend-gradle-plugin.svg?branch=1.1)](https://travis-ci.org/Siouan/frontend-gradle-plugin) [![Quality gate status](https://sonarcloud.io/api/project_badges/measure?project=Siouan_frontend-gradle-plugin&metric=alert_status)](https://sonarcloud.io/dashboard?id=Siouan_frontend-gradle-plugin) [![Code coverage](https://sonarcloud.io/api/project_badges/measure?project=Siouan_frontend-gradle-plugin&metric=coverage)](https://sonarcloud.io/dashboard?id=Siouan_frontend-gradle-plugin) [![Reliability](https://sonarcloud.io/api/project_badges/measure?project=Siouan_frontend-gradle-plugin&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=Siouan_frontend-gradle-plugin) -This plugin integrates frontend build tasks into a Gradle build. It is inspired by the -[frontend-maven-plugin][frontend-maven-plugin]. - -Detailed changes for each release are documented in the [release notes][release-notes]. The project uses [semantic -versioning][semantic-versioning] for its releases. +This plugin allows to integrate a frontend NPM/Yarn build into Gradle. It is inspired by the +[frontend-maven-plugin][frontend-maven-plugin]. See the [quick start guide](#quick-start-guide) below to +install/configure the plugin, and build your frontend application. ## Summary -- [Requirements](#requirements) -- [Installation](#installation) - - [Activation](#activation) +- [Quick start guide](#quick-start-guide) + - [Requirements](#requirements) + - [Installation](#installation) - [Using Gradle DSL](#using-gradle-dsl) - [Using Gradle build script block](#using-gradle-build-script-block) - [Configuration](#configuration) @@ -26,10 +24,10 @@ versioning][semantic-versioning] for its releases. - [Typical configuration with NPM](#typical-configuration-with-npm) - [Typical configuration with Yarn](#typical-configuration-with-yarn) - [Final steps](#final-steps) - - [Install Node/NPM/Yarn/frontend dependencies](#install-nodenpmyarnfrontend-dependencies) + - [Build the frontend](#build-the-frontend) - [Use Node/NPM/Yarn apart from Gradle](#use-nodenpmyarn-apart-from-gradle) - [Tasks reference](#tasks-reference) - - [Dependencies](#dependencies) + - [Task tree](#task-tree) - [Install Node](#install-node) - [Install Yarn](#install-yarn) - [Install frontend dependencies](#install-frontend-dependencies) @@ -42,7 +40,9 @@ versioning][semantic-versioning] for its releases. - [What kind of script should I attach to the `checkFrontend` task?](#what-kind-of-script-should-i-attach-to-the-checkfrontend-task) - [Contributing][contributing] -## Requirements +## Quick start guide + +### Requirements The plugin supports: - [Gradle][gradle] 5.1+ @@ -50,9 +50,10 @@ The plugin supports: - [Node][node] 6.2.1+ - [Yarn][yarn] 1.0.0+ -## Installation +The plugin is built and tested on Linux, Mac OS, Windows. For a full list of build environments used, see the +[contributing notes][contributing]. -### Activation +### Installation 2 options are available. @@ -63,7 +64,7 @@ This is the modern and recommended approach. ```gradle // build.gradle plugins { - id 'org.siouan.frontend' version '1.1.1' + id 'org.siouan.frontend' version '1.1.2' } ``` @@ -75,10 +76,10 @@ This approach is the legacy way to resolve and apply plugins. // build.gradle buildscript { repositories { - jcenter() + url 'https://plugins.gradle.org/m2/' } dependencies { - classpath 'org.siouan:frontend-gradle-plugin:1.1.1' + classpath 'org.siouan:frontend-gradle-plugin:1.1.2' } } @@ -95,11 +96,14 @@ All settings are introduced hereafter, with default value for each property. // build.gradle frontend { // NODE SETTINGS - // Version of the distribution to download, used to build the URL to download the distribution, if not set. + // Node version, used to build the URL to download the corresponding distribution, if the 'nodeDistributionUrl' + // property is not set. nodeVersion = '10.15.3' // [Optional] Sets this property to force the download from a custom website. By default, this property is 'null', // and the plugin attempts to download the distribution compatible with the current platform from the Node website. + // The version of the distribution is expected to be the same as the one set in the 'nodeVersion' property, or this + // may lead to unexpected results. nodeDistributionUrl = 'https://nodejs.org/dist/vX.Y.Z/node-vX.Y.Z-win-x64.zip' // [Optional] Install directory where the distribution archive shall be exploded. @@ -110,22 +114,22 @@ frontend { // be downloaded and installed by the plugin. yarnEnabled = false - // Yarn version, used to build the URL to download the corresponding distribution, if not set. If a custom - // 'yarnDistributionUrl' property is set, the version of the distribution is expected to be the same as the one set - // in the 'yarnVersion' property, or this may lead to unexpected results. This property is mandatory when the - // property 'yarnEnabled' is true. + // [Optional] Yarn version, used to build the URL to download the corresponding distribution, if the + // 'yarnDistributionUrl' property is not set. This property is mandatory when the 'yarnEnabled' property is true. yarnVersion = '1.15.2' // [Optional] Sets this property to force the download from a custom website. By default, this property is 'null', // and the plugin attempts to download the distribution compatible with the current platform from the Yarn website. + // The version of the distribution is expected to be the same as the one set in the 'yarnVersion' property, or this + // may lead to unexpected results. yarnDistributionUrl = 'https://github.com/yarnpkg/yarn/releases/download/vX.Y.Z/yarn-vX.Y.Z.tar.gz' // [Optional] Install directory where the distribution archive shall be exploded. yarnInstallDirectory = "${projectDir}/yarn" // OTHER SETTINGS - // Name of the NPM/Yarn scripts (see 'package.json' file) that shall be executing depending on the Gradle lifecycle - // task. The values below are passed as argument of the 'npm' or 'yarn' executable. + // Name of the NPM/Yarn scripts (see 'package.json' file) that shall be executed depending on the Gradle lifecycle + // task. The values below are passed as argument of the 'npm' or 'yarn' executables. // [Optional] Use this property only if frontend's compiled resources are generated out of the '${project.buildDir}' // directory. Default value is . This property is directly used by the 'cleanFrontend' task. The task is @@ -133,10 +137,10 @@ frontend { cleanScript = 'run clean' // [Optional] Script called to build frontend's artifacts. Default value is . This property is directly used - // by the 'assembleFrontend' task. The task also run when the Gradle built-in 'assemble' task is run. + // by the 'assembleFrontend' task. The task is run when the Gradle built-in 'assemble' task is run. assembleScript = 'run assemble' - // [Optional] Script called to run frontend's tests. Default value is . This property is directly used by the + // [Optional] Script called to check the frontend. Default value is . This property is directly used by the // 'checkFrontend' task. The task is run when the Gradle built-in 'check' task is run. checkScript = 'run check' } @@ -159,8 +163,8 @@ frontend { ```gradle // build.gradle frontend { - yarnEnabled = true nodeVersion = '' + yarnEnabled = true yarnVersion = '' cleanScript = 'run clean' assembleScript = 'run assemble' @@ -170,17 +174,19 @@ frontend { ### Final steps -#### Install Node/NPM/Yarn/frontend dependencies +#### Build the frontend -Working with the frontend application requires the Node distribution, the Yarn distribution - if enabled, and -the frontend dependencies declared in the `package.json` file are all installed/up-to-date. If this is not the case, it -is recommended to run the `installFrontend` task. Open a terminal, and execute the following command in the project's -directory: +Now that the plugin is correctly installed and configured, open a terminal, and execute the following command in the +project's directory: ```sh -gradlew installFrontend +gradle build ``` +If the frontend application is part of a full-stack Java artifact, take a look at +[this guide](#how-to-assemble-a-frontend-and-a-java-backend-in-a-single-artifact) to assemble the frontend and the +backend together. + #### Use Node/NPM/Yarn apart from Gradle If Node/NPM/Yarn may be used apart from Gradle, it is mandatory to apply the following steps: @@ -199,9 +205,9 @@ environment variable. The plugin registers multiple tasks, some having dependencies with other, and also with Gradle lifecycle tasks defined in the [Gradle base plugin][gradle-base-plugin]. -### Dependencies +### Task tree -![Task dependencies][task-dependencies] +![Task tree][task-tree] ### Install Node @@ -279,7 +285,6 @@ Assembling the frontend before the backend shall not be difficult to setup in Gr ```sh gradlew taskTree --no-repeat assemble -:build :assemble +--- :assembleFrontend | \--- :installFrontend @@ -324,12 +329,12 @@ tasks.named('processResources').configure { ### What kind of script should I attach to the `checkFrontend` task? -The `checkFrontend` task is a dependency of the `check` task. The Gradle official documentation states that the `check` -task shall be used to `attach [...] verification tasks, such as ones that run tests [...]`. It's enough vague to let you -consider any verification task. The script mapped to the `checkFrontend` task may run either automated unit tests, or -functional tests, or a linter, or any other verification action, or even combine some or all of them. Every combination -is possible, since you can define a script in your `package.json` file that executes sequentially the actions of your -choice. +The `checkFrontend` task is attached to the lifecycle `check` task. The Gradle official documentation states that the +`check` task shall be used to `attach [...] verification tasks, such as ones that run tests [...]`. It's enough vague to +let you consider any verification task. The script mapped to the `checkFrontend` task may run either automated unit +tests, or functional tests, or a linter, or any other verification action, or even combine some or all of them. Every +combination is even possible, since you can define a script in your `package.json` file that executes sequentially the +actions of your choice. [contributing]: (Contributing to this project) [frontend-maven-plugin]: (Frontend Maven plugin) @@ -339,11 +344,10 @@ choice. [gradle-dsl]: (Gradle DSL) [gradle-java-plugin]: (Gradle Java plugin) [gradle-spring-boot-plugin]: (Gradle Spring Boot plugin) -[j2ee]: (J2EE) [jdk]: (Java Development Kit) [node]: (Node.js) [release-notes]: (Release notes) [semantic-versioning]: (Semantic versioning) [spring-boot]: (Spring Boot) -[task-dependencies]: +[task-tree]: [yarn]: (Yarn) diff --git a/build.gradle b/build.gradle index 0ca346bb..b4c36c5a 100644 --- a/build.gradle +++ b/build.gradle @@ -19,8 +19,8 @@ repositories { test { useJUnitPlatform() testLogging { + showStandardStreams = true events "passed", "skipped", "failed" - exceptionFormat = 'full' } } @@ -41,7 +41,7 @@ dependencies { } group 'org.siouan' -version '1.1.1' +version '1.1.2' description 'Integrate your frontend NPM/Yarn build into Gradle.' sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/sonar-project.properties b/sonar-project.properties index bc2a8806..0a74bed0 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.projectKey=Siouan_frontend-gradle-plugin sonar.projectName=frontend-gradle-plugin -sonar.projectVersion=1.1.1 +sonar.projectVersion=1.1.2 sonar.links.homepage=https://github.com/Siouan/frontend-gradle-plugin sonar.links.ci=https://travis-ci.org/Siouan/frontend-gradle-plugin diff --git a/src/main/java/org/siouan/frontendgradleplugin/FrontendGradlePlugin.java b/src/main/java/org/siouan/frontendgradleplugin/FrontendGradlePlugin.java index 3be6fda2..3c35fbaf 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/FrontendGradlePlugin.java +++ b/src/main/java/org/siouan/frontendgradleplugin/FrontendGradlePlugin.java @@ -94,19 +94,21 @@ public void apply(final Project project) { extension.getYarnInstallDirectory().convention(new File(project.getProjectDir(), DEFAULT_YARN_INSTALL_DIRNAME)); final TaskContainer projectTasks = project.getTasks(); - projectTasks.register(NODE_INSTALL_TASK_NAME, NodeInstallTask.class, task -> configureTask(task, extension)); + projectTasks + .register(NODE_INSTALL_TASK_NAME, NodeInstallTask.class, task -> configureNodeInstallTask(task, extension)); - projectTasks.register(YARN_INSTALL_TASK_NAME, YarnInstallTask.class, task -> configureTask(task, extension)); + projectTasks + .register(YARN_INSTALL_TASK_NAME, YarnInstallTask.class, task -> configureYarnInstallTask(task, extension)); - projectTasks.register(INSTALL_TASK_NAME, InstallTask.class, task -> configureTask(task, extension)); + projectTasks.register(INSTALL_TASK_NAME, InstallTask.class, task -> configureInstallTask(task, extension)); - projectTasks.register(CLEAN_TASK_NAME, CleanTask.class, task -> configureTask(task, extension)); + projectTasks.register(CLEAN_TASK_NAME, CleanTask.class, task -> configureCleanTask(task, extension)); projectTasks.named(GRADLE_CLEAN_TASK_NAME, task -> task.dependsOn(projectTasks.named(CLEAN_TASK_NAME))); - projectTasks.register(CHECK_TASK_NAME, CheckTask.class, task -> configureTask(task, extension)); + projectTasks.register(CHECK_TASK_NAME, CheckTask.class, task -> configureCheckTask(task, extension)); projectTasks.named(GRADLE_CHECK_TASK_NAME, task -> task.dependsOn(projectTasks.named(CHECK_TASK_NAME))); - projectTasks.register(ASSEMBLE_TASK_NAME, AssembleTask.class, task -> configureTask(task, extension)); + projectTasks.register(ASSEMBLE_TASK_NAME, AssembleTask.class, task -> configureAssembleTask(task, extension)); projectTasks.named(GRADLE_ASSEMBLE_TASK_NAME, task -> task.dependsOn(projectTasks.named(ASSEMBLE_TASK_NAME))); } @@ -116,7 +118,7 @@ public void apply(final Project project) { * @param task Task. * @param extension Plugin extension. */ - private void configureTask(final NodeInstallTask task, final FrontendExtension extension) { + private void configureNodeInstallTask(final NodeInstallTask task, final FrontendExtension extension) { task.setGroup(TASK_GROUP); task.setDescription("Downloads and installs a Node distribution."); task.getNodeVersion().set(extension.getNodeVersion()); @@ -130,7 +132,7 @@ private void configureTask(final NodeInstallTask task, final FrontendExtension e * @param task Task. * @param extension Plugin extension. */ - private void configureTask(final YarnInstallTask task, FrontendExtension extension) { + private void configureYarnInstallTask(final YarnInstallTask task, FrontendExtension extension) { task.setGroup(TASK_GROUP); task.setDescription("Downloads and installs a Yarn distribution."); task.setEnabled(extension.getYarnEnabled().get()); @@ -145,7 +147,7 @@ private void configureTask(final YarnInstallTask task, FrontendExtension extensi * @param task Task. * @param extension Plugin extension. */ - private void configureTask(final InstallTask task, FrontendExtension extension) { + private void configureInstallTask(final InstallTask task, FrontendExtension extension) { task.setGroup(TASK_GROUP); task.setDescription("Installs/updates frontend dependencies."); task.getYarnEnabled().set(extension.getYarnEnabled()); @@ -160,7 +162,7 @@ private void configureTask(final InstallTask task, FrontendExtension extension) * @param task Task. * @param extension Plugin extension. */ - private void configureTask(final CleanTask task, FrontendExtension extension) { + private void configureCleanTask(final CleanTask task, FrontendExtension extension) { task.setGroup(TASK_GROUP); task.setDescription("Cleans frontend resources outside the build directory by running a specific script."); task.getYarnEnabled().set(extension.getYarnEnabled()); @@ -176,7 +178,7 @@ private void configureTask(final CleanTask task, FrontendExtension extension) { * @param task Task. * @param extension Plugin extension. */ - private void configureTask(final CheckTask task, FrontendExtension extension) { + private void configureCheckTask(final CheckTask task, FrontendExtension extension) { task.setGroup(TASK_GROUP); task.setDescription("Checks frontend by running a specific script."); task.getYarnEnabled().set(extension.getYarnEnabled()); @@ -192,7 +194,7 @@ private void configureTask(final CheckTask task, FrontendExtension extension) { * @param task Task. * @param extension Plugin extension. */ - private void configureTask(final AssembleTask task, FrontendExtension extension) { + private void configureAssembleTask(final AssembleTask task, FrontendExtension extension) { task.setGroup(TASK_GROUP); task.setDescription("Assembles the frontend by running a specific script."); task.getYarnEnabled().set(extension.getYarnEnabled()); diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/AbstractJob.java b/src/main/java/org/siouan/frontendgradleplugin/core/AbstractTaskJob.java similarity index 88% rename from src/main/java/org/siouan/frontendgradleplugin/core/AbstractJob.java rename to src/main/java/org/siouan/frontendgradleplugin/core/AbstractTaskJob.java index 9d74cce7..d3bef65b 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/AbstractJob.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/AbstractTaskJob.java @@ -5,7 +5,7 @@ /** * Class that provides common utilities for this plugin's jobs. */ -abstract class AbstractJob { +abstract class AbstractTaskJob { protected final Task task; @@ -14,7 +14,7 @@ abstract class AbstractJob { * * @param task Task. */ - protected AbstractJob(final Task task) { + protected AbstractTaskJob(final Task task) { this.task = task; } @@ -50,9 +50,10 @@ protected void logLifecycle(final String message) { } /** - * Shortcut to log a WARN LIFECYCLE message with the task's logger. + * Shortcut to log a WARN message with the task's logger. * * @param message Message. + * @param throwable Throwable. */ protected void logWarn(final String message, final Throwable throwable) { task.getLogger().warn(formatMessage(message), throwable); diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstallJob.java b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstaller.java similarity index 71% rename from src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstallJob.java rename to src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstaller.java index cc039e4d..3c475bc4 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstallJob.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstaller.java @@ -4,38 +4,26 @@ import java.io.IOException; import java.net.URL; import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; import java.util.function.Function; import org.gradle.api.Project; -import org.gradle.api.Task; import org.gradle.api.file.FileTree; /** - * Job that downloads and installs a distribution. + * Component that downloads and installs a distribution. */ -public class DistributionInstallJob extends AbstractJob { +public class DistributionInstaller extends AbstractTaskJob { /** * Directory where the distribution shall be installed. */ - private final File installDirectory; + private final DistributionInstallerSettings settings; - /** - * Resolver of the distribution download URL. - */ - private final DistributionUrlResolver urlResolver; - - /** - * Validator of the distribution. - */ - private final DistributionValidator validator; - - public DistributionInstallJob(final Task task, final DistributionUrlResolver urlResolver, - final DistributionValidator validator, final File installDirectory) { - super(task); - this.urlResolver = urlResolver; - this.validator = validator; - this.installDirectory = installDirectory; + public DistributionInstaller(final DistributionInstallerSettings settings) { + super(settings.getTask()); + this.settings = settings; } /** @@ -55,27 +43,29 @@ public DistributionInstallJob(final Task task, final DistributionUrlResolver url * @throws DownloadException If the distribution could not be downloaded. * @throws UnsupportedDistributionArchiveException If the distribution archive is not supported, and could not be * exploded. + * @throws DistributionPostInstallException If the post-install action has failed. */ public void install() throws InvalidDistributionException, IOException, DistributionUrlResolverException, DownloadException, - UnsupportedDistributionArchiveException { + UnsupportedDistributionArchiveException, DistributionPostInstallException { final Project project = task.getProject(); checkInstallDirectory(); // Resolve the URL to download the distribution - final URL distributionUrl = urlResolver.resolve(); + final URL distributionUrl = settings.getUrlResolver().resolve(); // Download the distribution final String distributionUrlAsString = distributionUrl.toString(); logLifecycle("Downloading distribution at '" + distributionUrlAsString + "'"); final DownloaderImpl downloader = new DownloaderImpl(task.getTemporaryDir()); - final File distributionFile = new File(installDirectory, + final File distributionFile = new File(settings.getInstallDirectory(), distributionUrlAsString.substring(distributionUrlAsString.lastIndexOf('/') + 1)); downloader.download(distributionUrl, distributionFile); - if (validator != null) { - validator.validate(distributionUrl, distributionFile); + final Optional validator = settings.getValidator(); + if (validator.isPresent()) { + validator.get().validate(distributionUrl, distributionFile); } // Explode the archive @@ -103,13 +93,20 @@ public void install() logLifecycle("Removing distribution file '" + distributionFile.getAbsolutePath() + "'"); Files.delete(distributionFile.toPath()); + logLifecycle("Running post-install"); + final Optional listener = settings.getPostInstallAction(); + if (listener.isPresent()) { + listener.get().onDistributionInstalled(settings); + } + logLifecycle("Distribution installed in '" + distributionFile.getParent() + "'"); } private void checkInstallDirectory() throws IOException { - Files.createDirectories(installDirectory.toPath()); + final Path installPath = settings.getInstallDirectory().toPath(); + Files.createDirectories(installPath); - logLifecycle("Removing content in install directory '" + installDirectory.getAbsolutePath() + "'."); - Utils.deleteRecursively(installDirectory.toPath(), false); + logLifecycle("Removing content in install directory '" + installPath.toString() + "'."); + Utils.deleteRecursively(installPath, false); } } diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstallerSettings.java b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstallerSettings.java new file mode 100644 index 00000000..935d8cf7 --- /dev/null +++ b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionInstallerSettings.java @@ -0,0 +1,73 @@ +package org.siouan.frontendgradleplugin.core; + +import java.io.File; +import java.util.Optional; + +import org.gradle.api.Task; + +/** + * Settings for the distribution installer. + * + * @since 1.1.2 + */ +public class DistributionInstallerSettings { + + private final Task task; + + private final String osName; + + /** + * Directory where the distribution shall be installed. + */ + private final File installDirectory; + + /** + * Resolver of the distribution download URL. + */ + private final DistributionUrlResolver urlResolver; + + /** + * Validator of the distribution. + */ + private final DistributionValidator validator; + + /** + * Listener called once the distribution is installed. + */ + private final DistributionPostInstallAction postInstallAction; + + public DistributionInstallerSettings(final Task task, final String osName, + final DistributionUrlResolver urlResolver, final DistributionValidator validator, final File installDirectory, + final DistributionPostInstallAction postInstallAction) { + this.task = task; + this.osName = osName; + this.urlResolver = urlResolver; + this.validator = validator; + this.installDirectory = installDirectory; + this.postInstallAction = postInstallAction; + } + + public Task getTask() { + return task; + } + + public String getOsName() { + return osName; + } + + public File getInstallDirectory() { + return installDirectory; + } + + public DistributionUrlResolver getUrlResolver() { + return urlResolver; + } + + public Optional getValidator() { + return Optional.ofNullable(validator); + } + + public Optional getPostInstallAction() { + return Optional.ofNullable(postInstallAction); + } +} diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/DistributionPostInstallAction.java b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionPostInstallAction.java new file mode 100644 index 00000000..eed06ca1 --- /dev/null +++ b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionPostInstallAction.java @@ -0,0 +1,18 @@ +package org.siouan.frontendgradleplugin.core; + +/** + * Action called once a distribution has been successfully installed. + * + * @since 1.1.2 + */ +@FunctionalInterface +public interface DistributionPostInstallAction { + + /** + * Runs the action with distribution installer settings. + * + * @param settings Distribution installer settings. + * @throws DistributionPostInstallException If the action failed. + */ + void onDistributionInstalled(DistributionInstallerSettings settings) throws DistributionPostInstallException; +} diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/DistributionPostInstallException.java b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionPostInstallException.java new file mode 100644 index 00000000..04140991 --- /dev/null +++ b/src/main/java/org/siouan/frontendgradleplugin/core/DistributionPostInstallException.java @@ -0,0 +1,11 @@ +package org.siouan.frontendgradleplugin.core; + +/** + * Exception thrown when a distribution post-install action has failed. + */ +public class DistributionPostInstallException extends FrontendException { + + public DistributionPostInstallException(final Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/ExecSpecAction.java b/src/main/java/org/siouan/frontendgradleplugin/core/ExecSpecAction.java index 2d37246c..383d7701 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/ExecSpecAction.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/ExecSpecAction.java @@ -1,6 +1,7 @@ package org.siouan.frontendgradleplugin.core; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -15,13 +16,9 @@ */ public class ExecSpecAction implements Action { - public static final String SHELL_EXECUTABLE = "sh"; - public static final String CMD_EXECUTABLE = "cmd"; - public static final String NPM_EXECUTABLE = "npm"; - - public static final String YARN_EXECUTABLE = "yarn"; + public static final String CMD_RUN_EXIT_FLAG = "/c"; /** * Whether the script shall be run with Yarn instead of NPM. @@ -38,6 +35,11 @@ public class ExecSpecAction implements Action { */ private final File yarnInstallDirectory; + /** + * Name of the O/S. + */ + private final String osName; + /** * Name of the script to execute. */ @@ -49,24 +51,14 @@ public class ExecSpecAction implements Action { private final Consumer afterConfigured; /** - * Name of the O/S. + * Path to the Node executable. */ - private final String osName; + private final Path nodeExecutablePath; /** - * Builds an action to run a frontend script on the local platform. - * - * @param yarnEnabled Whether the script shall be run with Yarn instead of NPM. - * @param nodeInstallDirectory Directory where the Node distribution is installed. - * @param yarnInstallDirectory Directory where the Yarn distribution is installed. - * @param script Name of the script to execute. - * @param afterConfigured A consumer called once the exec specification has been configured. + * Path to the script executable (i.e. NPM or Yarn). */ - public ExecSpecAction(final boolean yarnEnabled, final File nodeInstallDirectory, final File yarnInstallDirectory, - final String script, final Consumer afterConfigured) { - this(yarnEnabled, nodeInstallDirectory, yarnInstallDirectory, script, afterConfigured, - System.getProperty("os.name")); - } + private final Path scriptExecutablePath; /** * Builds an action to run a frontend script on the local platform. @@ -77,15 +69,27 @@ public ExecSpecAction(final boolean yarnEnabled, final File nodeInstallDirectory * @param script Name of the script to execute. * @param afterConfigured A consumer called once the exec specification has been configured. * @param osName Name of the O/S. + * @throws ExecutableNotFoundException When an executable cannot be found (Node, NPM, Yarn). */ public ExecSpecAction(final boolean yarnEnabled, final File nodeInstallDirectory, final File yarnInstallDirectory, - final String script, final Consumer afterConfigured, final String osName) { + final String osName, final String script, final Consumer afterConfigured) + throws ExecutableNotFoundException { this.yarnEnabled = yarnEnabled; this.nodeInstallDirectory = nodeInstallDirectory; this.yarnInstallDirectory = yarnInstallDirectory; + this.osName = osName; this.script = script; this.afterConfigured = afterConfigured; - this.osName = osName; + + nodeExecutablePath = Utils.getNodeExecutablePath(nodeInstallDirectory.toPath(), osName) + .orElseThrow(ExecutableNotFoundException::newNodeExecutableNotFoundException); + if (yarnEnabled) { + scriptExecutablePath = Utils.getYarnExecutablePath(yarnInstallDirectory.toPath(), osName) + .orElseThrow(ExecutableNotFoundException::newYarnExecutableNotFoundException); + } else { + scriptExecutablePath = Utils.getNpmExecutablePath(nodeInstallDirectory.toPath(), osName) + .orElseThrow(ExecutableNotFoundException::newNpmExecutableNotFoundException); + } } /** @@ -99,34 +103,23 @@ public void execute(final ExecSpec execSpec) { final List args = new ArrayList<>(); if (Utils.isWindowsOs(osName)) { executable = CMD_EXECUTABLE; - args.add("/c"); - final String scriptExecutable; - if (yarnEnabled) { - scriptExecutable = YARN_EXECUTABLE; - } else { - scriptExecutable = NPM_EXECUTABLE; - } + args.add(CMD_RUN_EXIT_FLAG); // The command that must be executed in the terminal must be a single argument on itself (like if it was // quoted). - args.add(scriptExecutable + ' ' + script.trim()); + args.add('"' + scriptExecutablePath.toString() + "\" " + script.trim()); } else { - if (yarnEnabled) { - executable = YARN_EXECUTABLE; - } else { - executable = NPM_EXECUTABLE; - } + executable = scriptExecutablePath.toString(); args.addAll(Arrays.asList(script.trim().split("\\s+"))); } // Prepend directories containing the Node and Yarn executables to the 'PATH' environment variable. + // NPM is in the same directory than Node, do nothing for it. final Map environment = execSpec.getEnvironment(); final String pathVariable = findPathVariable(environment); - final StringBuilder pathValue = new StringBuilder(nodeInstallDirectory.getAbsolutePath()); + final StringBuilder pathValue = new StringBuilder(nodeExecutablePath.getParent().toString()); pathValue.append(File.pathSeparatorChar); if (yarnEnabled) { - pathValue.append(yarnInstallDirectory.getAbsolutePath()); - pathValue.append(File.separatorChar); - pathValue.append("bin"); + pathValue.append(scriptExecutablePath.getParent().toString()); pathValue.append(File.pathSeparatorChar); } pathValue.append((String) environment.getOrDefault(pathVariable, "")); @@ -137,25 +130,45 @@ public void execute(final ExecSpec execSpec) { afterConfigured.accept(execSpec); } + /** + * Whether Yarn is enabled and will be used at execution. + * + * @return {@code true} if Yarn is enabled. + */ public boolean isYarnEnabled() { return yarnEnabled; } + /** + * Gets the install directory of Node. + * + * @return Directory. + */ public File getNodeInstallDirectory() { return nodeInstallDirectory; } + /** + * Gets the install directory of Yarn. + * + * @return Directory. + */ public File getYarnInstallDirectory() { return yarnInstallDirectory; } + /** + * Gets the script to be executed. + * + * @return Script. + */ public String getScript() { return script; } /** - * Finds the name of the 'PATH' variable. Depending on the O/S, it may be a different value than the exact 'PATH' - * name. + * Finds the name of the 'PATH' variable. Depending on the O/S, it may be have a different case than the exact + * 'PATH' name. * * @param environment Map of environment variables. * @return The name of the 'PATH' variable. diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/ExecutableNotFoundException.java b/src/main/java/org/siouan/frontendgradleplugin/core/ExecutableNotFoundException.java new file mode 100644 index 00000000..c7550671 --- /dev/null +++ b/src/main/java/org/siouan/frontendgradleplugin/core/ExecutableNotFoundException.java @@ -0,0 +1,44 @@ +package org.siouan.frontendgradleplugin.core; + +/** + * Exception thrown when an executable cannot be found. + */ +public class ExecutableNotFoundException extends FrontendException { + + public static final String NODE = "Node"; + + public static final String NPM = "NPM"; + + public static final String YARN = "Yarn"; + + private ExecutableNotFoundException(final String executable) { + super(executable); + } + + /** + * Builds the appropriate exception when the Node executable cannot be found. + * + * @return Exception. + */ + public static ExecutableNotFoundException newNodeExecutableNotFoundException() { + return new ExecutableNotFoundException(NODE); + } + + /** + * Builds the appropriate exception when the NPM executable cannot be found. + * + * @return Exception. + */ + public static ExecutableNotFoundException newNpmExecutableNotFoundException() { + return new ExecutableNotFoundException(NPM); + } + + /** + * Builds the appropriate exception when the Yarn executable cannot be found. + * + * @return Exception. + */ + public static ExecutableNotFoundException newYarnExecutableNotFoundException() { + return new ExecutableNotFoundException(YARN); + } +} diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolver.java b/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolver.java index 82f828d7..312fc142 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolver.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolver.java @@ -26,7 +26,7 @@ public class NodeDistributionUrlResolver implements DistributionUrlResolver { * @param distributionUrl URL to download the distribution. */ public NodeDistributionUrlResolver(final String version, final String distributionUrl) { - this(version, distributionUrl, System.getProperty("os.name"), System.getProperty("os.arch")); + this(version, distributionUrl, Utils.getSystemOsName(), Utils.getSystemJvmArch()); } /** diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidator.java b/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidator.java index 8852e3a8..6311e6d4 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidator.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidator.java @@ -11,7 +11,7 @@ /** * Validates a Node distribution by comparing its SHA-256 hash against the one officially published. */ -public class NodeDistributionValidator extends AbstractJob implements DistributionValidator { +public class NodeDistributionValidator extends AbstractTaskJob implements DistributionValidator { /** * Downloader used to get the file containing all checksums for a given distribution. diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/NodePostInstallAction.java b/src/main/java/org/siouan/frontendgradleplugin/core/NodePostInstallAction.java new file mode 100644 index 00000000..0bb4ecec --- /dev/null +++ b/src/main/java/org/siouan/frontendgradleplugin/core/NodePostInstallAction.java @@ -0,0 +1,44 @@ +package org.siouan.frontendgradleplugin.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Optional; +import java.util.Set; + +/** + * Action that checks and fixes symbolic links that disappeared when the distribution was exploded. + * + * @since 1.1.2 + */ +public class NodePostInstallAction implements DistributionPostInstallAction { + + @Override + public void onDistributionInstalled(final DistributionInstallerSettings settings) + throws DistributionPostInstallException { + if (!Utils.isWindowsOs(settings.getOsName())) { + // Checks, and fixes if required, the NPM executable is a symbolic link. Due to Gradle 'tarTree' + // limitations, symbolic links are not preserved during TAR exploding. They must be restored or NPM won't + // work. + final Path installDirectory = settings.getInstallDirectory().toPath(); + final Optional npmExecutablePath = Utils.getNpmExecutablePath(installDirectory, settings.getOsName()); + if (npmExecutablePath.isPresent()) { + final Path npmExecutable = npmExecutablePath.get(); + try { + // The original link is a relative link. We must preserve it and its permissions. + final Set permissions = Files.getPosixFilePermissions(npmExecutable); + if (!Files.isSymbolicLink(npmExecutable)) { + Files.delete(npmExecutable); + Files.createSymbolicLink(npmExecutable, installDirectory.resolve("bin").relativize( + installDirectory.resolve("lib").resolve("node_modules").resolve("npm").resolve("bin") + .resolve("npm-cli.js"))); + } + Utils.setFileExecutable(npmExecutable, permissions, settings.getOsName()); + } catch (final IOException e) { + throw new DistributionPostInstallException(e); + } + } + } + } +} diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/ScriptRunJob.java b/src/main/java/org/siouan/frontendgradleplugin/core/RunScriptJob.java similarity index 53% rename from src/main/java/org/siouan/frontendgradleplugin/core/ScriptRunJob.java rename to src/main/java/org/siouan/frontendgradleplugin/core/RunScriptJob.java index 61d7fb7f..b8ca16b7 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/ScriptRunJob.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/RunScriptJob.java @@ -7,7 +7,7 @@ /** * This abstract class provides the reusable logic to run a NPM/Yarn script. */ -public class ScriptRunJob extends AbstractJob { +public class RunScriptJob extends AbstractTaskJob { /** * Whether a Yarn distribution shall be downloaded and installed. @@ -24,20 +24,39 @@ public class ScriptRunJob extends AbstractJob { */ private final File yarnInstallDirectory; + /** + * The script run by the job with NPM or Yarn. + */ private final String script; - public ScriptRunJob(final Task task, final boolean yarnEnabled, final File nodeInstallDirectory, - final File yarnInstallDirectory, final String script) { + /** + * O/S name. + */ + private final String osName; + + /** + * Builds a job to run a script. + * + * @param task Parent task. + * @param yarnEnabled Whether Yarn shall be used instead of NPM to run the script. + * @param nodeInstallDirectory Node install directory. + * @param yarnInstallDirectory Yarn install directory. + * @param script The script run by the job. + * @param osName O/S name. + */ + public RunScriptJob(final Task task, final boolean yarnEnabled, final File nodeInstallDirectory, + final File yarnInstallDirectory, final String script, final String osName) { super(task); this.yarnEnabled = yarnEnabled; this.nodeInstallDirectory = nodeInstallDirectory; this.yarnInstallDirectory = yarnInstallDirectory; this.script = script; + this.osName = osName; } - public void run() { - task.getProject() - .exec(new ExecSpecAction(yarnEnabled, nodeInstallDirectory, yarnInstallDirectory, script, execSpec -> { + public void run() throws ExecutableNotFoundException { + task.getProject().exec( + new ExecSpecAction(yarnEnabled, nodeInstallDirectory, yarnInstallDirectory, osName, script, execSpec -> { logDebug(execSpec.getEnvironment().toString()); logLifecycle( "Running '" + execSpec.getExecutable() + ' ' + String.join(" ", execSpec.getArgs()) + '\''); diff --git a/src/main/java/org/siouan/frontendgradleplugin/core/Utils.java b/src/main/java/org/siouan/frontendgradleplugin/core/Utils.java index 523d63ad..cf76db00 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/core/Utils.java +++ b/src/main/java/org/siouan/frontendgradleplugin/core/Utils.java @@ -8,6 +8,12 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Stream; /** @@ -29,16 +35,93 @@ public static void deleteRecursively(final Path rootPath, final boolean deleteRo Files.walkFileTree(rootPath, new FileDeleteVisitor(rootPath, deleteRootEnabled)); } + /** + * Gets the path of the Node executable. + * + * @param nodeInstallDirectory Node install directory. + * @param osName O/S name. + * @return The path, may be {@code null} if it was not found. + * @see #getSystemOsName() + */ + public static Optional getNodeExecutablePath(final Path nodeInstallDirectory, final String osName) { + final List possiblePaths = new ArrayList<>(); + if (isWindowsOs(osName)) { + possiblePaths.add(nodeInstallDirectory.resolve("node.exe")); + possiblePaths.add(nodeInstallDirectory.resolve("node.cmd")); + } else { + possiblePaths.add(nodeInstallDirectory.resolve("bin").resolve("node")); + } + + return possiblePaths.stream().filter(Files::exists).findFirst(); + } + + /** + * Gets the path of the NPM executable. + * + * @param nodeInstallDirectory Node install directory. + * @param osName O/S name. + * @return The path, may be {@code null} if it was not found. + * @see #getSystemOsName() + */ + public static Optional getNpmExecutablePath(final Path nodeInstallDirectory, final String osName) { + final List possiblePaths = new ArrayList<>(); + if (isWindowsOs(osName)) { + possiblePaths.add(nodeInstallDirectory.resolve("npm.cmd")); + } else { + possiblePaths.add(nodeInstallDirectory.resolve("bin").resolve("npm")); + } + + return possiblePaths.stream().filter(Files::exists).findFirst(); + } + + /** + * Gets the path of Yarn executable. + * + * @param yarnInstallDirectory Yarn install directory. + * @param osName O/S name. + * @return The path, may be {@code null} if it was not found. + * @see #getSystemOsName() + */ + public static Optional getYarnExecutablePath(final Path yarnInstallDirectory, final String osName) { + final List possiblePaths = new ArrayList<>(); + if (isWindowsOs(osName)) { + possiblePaths.add(yarnInstallDirectory.resolve("bin").resolve("yarn.cmd")); + } else { + possiblePaths.add(yarnInstallDirectory.resolve("bin").resolve("yarn")); + } + + return possiblePaths.stream().filter(Files::exists).findFirst(); + } + + /** + * Gets the current JVM architecture. + * + * @return String describing the JVM architecture. + */ + public static String getSystemJvmArch() { + return System.getProperty("os.arch"); + } + + /** + * Gets the current O/S name. + * + * @return String describing the O/S. + */ + public static String getSystemOsName() { + return System.getProperty("os.name"); + } + /** * Tells whether the given JRE architecture is a 64 bits one. * * @param jreArch JRE architecture. * @return {@code true} if the architecture is a 64 bits. + * @see #getSystemJvmArch() */ public static boolean is64BitsArch(final String jreArch) { final String jreArchLowered = jreArch.toLowerCase(); - return jreArchLowered.contains("x64") || jreArchLowered.contains("amd64") || jreArchLowered.contains("ppc") - || jreArchLowered.contains("sparc"); + return jreArchLowered.contains("x64") || jreArchLowered.contains("x86_64") || jreArchLowered.contains("amd64") + || jreArchLowered.contains("ppc") || jreArchLowered.contains("sparc"); } /** @@ -46,6 +129,7 @@ public static boolean is64BitsArch(final String jreArch) { * * @param osName OS name. * @return {@code true} if the OS name matches a Linux OS. + * @see #getSystemOsName() */ public static boolean isLinuxOs(final String osName) { return osName.toLowerCase().contains("linux"); @@ -56,6 +140,7 @@ public static boolean isLinuxOs(final String osName) { * * @param osName OS name. * @return {@code true} if the OS name matches a Mac OS. + * @see #getSystemOsName() */ public static boolean isMacOs(final String osName) { return osName.toLowerCase().contains("mac os"); @@ -66,6 +151,7 @@ public static boolean isMacOs(final String osName) { * * @param osName OS name. * @return {@code true} if the OS name matches a Windows OS. + * @see #getSystemOsName() */ public static boolean isWindowsOs(final String osName) { return osName.toLowerCase().contains("windows"); @@ -113,6 +199,50 @@ public static String removeExtension(final String filename) { return filenameWithoutExtension; } + /** + * Marks the file as executable. This method does nothing under Windows. + * + * @param path Path. + * @param osName OS name. + * @return {@code true} if the file permissions were touched, i.e. under a Non-Windows O/S, the file exists and has + * not the executable permission yet. + * @throws IOException If an I/O error occurs. + */ + public static boolean setFileExecutable(final Path path, final String osName) throws IOException { + final boolean touched; + if (!Utils.isWindowsOs(osName) && Files.exists(path) && !Files.isExecutable(path)) { + touched = setFileExecutable(path, Files.getPosixFilePermissions(path), osName); + } else { + touched = false; + } + return touched; + } + + /** + * Marks the file as executable. This method does nothing under Windows. This method allows to restore a file's + * permissions by providing the original permissions, in case they cannot be retrieved. + * + * @param path Path. + * @param originalPermissions Original file permissions. + * @param osName OS name. + * @return {@code true} if the file permissions were touched, i.e. under a Non-Windows O/S, the file exists and has + * not the executable permission yet. + * @throws IOException If an I/O error occurs. + */ + public static boolean setFileExecutable(final Path path, final Set originalPermissions, + final String osName) throws IOException { + final boolean touched; + if (!Utils.isWindowsOs(osName) && Files.exists(path) && !Files.isExecutable(path)) { + final Set newPermissions = EnumSet.copyOf(originalPermissions); + newPermissions.add(PosixFilePermission.OWNER_EXECUTE); + Files.setPosixFilePermissions(path, newPermissions); + touched = true; + } else { + touched = false; + } + return touched; + } + /** * A visitor of paths that deletes the corresponding file and/or directory after all child files are deleted. */ @@ -122,7 +252,7 @@ private static class FileDeleteVisitor extends SimpleFileVisitor { private final boolean deleteRootEnabled; - public FileDeleteVisitor(final Path rootPath, final boolean deleteRootEnabled) { + FileDeleteVisitor(final Path rootPath, final boolean deleteRootEnabled) { this.rootPath = rootPath; this.deleteRootEnabled = deleteRootEnabled; } diff --git a/src/main/java/org/siouan/frontendgradleplugin/tasks/AbstractRunScriptTask.java b/src/main/java/org/siouan/frontendgradleplugin/tasks/AbstractRunScriptTask.java index 712050a9..8844ed51 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/tasks/AbstractRunScriptTask.java +++ b/src/main/java/org/siouan/frontendgradleplugin/tasks/AbstractRunScriptTask.java @@ -5,7 +5,9 @@ import org.gradle.api.DefaultTask; import org.gradle.api.provider.Property; import org.gradle.api.tasks.TaskAction; -import org.siouan.frontendgradleplugin.core.ScriptRunJob; +import org.siouan.frontendgradleplugin.core.ExecutableNotFoundException; +import org.siouan.frontendgradleplugin.core.RunScriptJob; +import org.siouan.frontendgradleplugin.core.Utils; /** * This abstract class provides the reusable logic to run a NPM/Yarn script. Sub-classes must expose inputs and @@ -42,12 +44,14 @@ protected AbstractRunScriptTask() { /** * Executes the task. If a script has been provided, it is run with NPM/Yarn. Otherwise, the task does nothing. + * + * @throws ExecutableNotFoundException When an executable cannot be found (Node, NPM, Yarn). */ @TaskAction - public void execute() { + public void execute() throws ExecutableNotFoundException { if (script.isPresent()) { - new ScriptRunJob(this, yarnEnabled.get(), nodeInstallDirectory.get(), yarnInstallDirectory.get(), - script.get()).run(); + new RunScriptJob(this, yarnEnabled.get(), nodeInstallDirectory.get(), yarnInstallDirectory.get(), + script.get(), Utils.getSystemOsName()).run(); } } } diff --git a/src/main/java/org/siouan/frontendgradleplugin/tasks/InstallTask.java b/src/main/java/org/siouan/frontendgradleplugin/tasks/InstallTask.java index 47c69e51..fd45542a 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/tasks/InstallTask.java +++ b/src/main/java/org/siouan/frontendgradleplugin/tasks/InstallTask.java @@ -5,7 +5,7 @@ */ public class InstallTask extends AbstractPredefinedRunScriptTask { - public static final String INSTALL_SCRIPT = "install"; + private static final String INSTALL_SCRIPT = "install"; public InstallTask() { super(); diff --git a/src/main/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTask.java b/src/main/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTask.java index c9352d61..31f5656f 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTask.java +++ b/src/main/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTask.java @@ -10,7 +10,9 @@ import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; -import org.siouan.frontendgradleplugin.core.DistributionInstallJob; +import org.siouan.frontendgradleplugin.core.DistributionInstaller; +import org.siouan.frontendgradleplugin.core.DistributionInstallerSettings; +import org.siouan.frontendgradleplugin.core.DistributionPostInstallException; import org.siouan.frontendgradleplugin.core.DistributionUrlResolverException; import org.siouan.frontendgradleplugin.core.DownloadException; import org.siouan.frontendgradleplugin.core.DownloaderImpl; @@ -19,7 +21,9 @@ import org.siouan.frontendgradleplugin.core.NodeDistributionChecksumReaderImpl; import org.siouan.frontendgradleplugin.core.NodeDistributionUrlResolver; import org.siouan.frontendgradleplugin.core.NodeDistributionValidator; +import org.siouan.frontendgradleplugin.core.NodePostInstallAction; import org.siouan.frontendgradleplugin.core.UnsupportedDistributionArchiveException; +import org.siouan.frontendgradleplugin.core.Utils; /** * Task that downloads and installs a Node distribution. @@ -74,16 +78,20 @@ public Property getNodeInstallDirectory() { * @throws DownloadException If a download error occured. * @throws NoSuchAlgorithmException If the hashing algorithm used to check the distribution integrity is not * supported. + * @throws DistributionPostInstallException If the post-install action has failed. */ @TaskAction public void execute() throws IOException, InvalidDistributionException, DistributionUrlResolverException, - UnsupportedDistributionArchiveException, DownloadException, NoSuchAlgorithmException { + UnsupportedDistributionArchiveException, DownloadException, NoSuchAlgorithmException, + DistributionPostInstallException { final String version = nodeVersion.get(); final String distributionUrl = nodeDistributionUrl.getOrNull(); final File installDirectory = nodeInstallDirectory.get(); - new DistributionInstallJob(this, new NodeDistributionUrlResolver(version, distributionUrl), + final DistributionInstallerSettings settings = new DistributionInstallerSettings(this, Utils.getSystemOsName(), + new NodeDistributionUrlResolver(version, distributionUrl), new NodeDistributionValidator(this, new DownloaderImpl(getTemporaryDir()), - new NodeDistributionChecksumReaderImpl(), new FileHasherImpl(), installDirectory), installDirectory) - .install(); + new NodeDistributionChecksumReaderImpl(), new FileHasherImpl(), installDirectory), installDirectory, + new NodePostInstallAction()); + new DistributionInstaller(settings).install(); } } diff --git a/src/main/java/org/siouan/frontendgradleplugin/tasks/RunScriptTask.java b/src/main/java/org/siouan/frontendgradleplugin/tasks/RunScriptTask.java index b3d193e6..290e4183 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/tasks/RunScriptTask.java +++ b/src/main/java/org/siouan/frontendgradleplugin/tasks/RunScriptTask.java @@ -3,6 +3,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; import org.siouan.frontendgradleplugin.FrontendExtension; +import org.siouan.frontendgradleplugin.core.ExecutableNotFoundException; /** * Task provided as a type to let developers implement custom task based on it. The task does not expose Node/Yarn @@ -24,11 +25,8 @@ public Property getScript() { return script; } - /** - * Executes the task by running the script with NPM/Yarn. - */ @Override - public void execute() { + public void execute() throws ExecutableNotFoundException { final FrontendExtension extension = getProject().getExtensions().findByType(FrontendExtension.class); yarnEnabled.set(extension.getYarnEnabled()); nodeInstallDirectory.set(extension.getNodeInstallDirectory()); diff --git a/src/main/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTask.java b/src/main/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTask.java index 3731d31e..0a1a64fd 100644 --- a/src/main/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTask.java +++ b/src/main/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTask.java @@ -9,11 +9,14 @@ import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; -import org.siouan.frontendgradleplugin.core.DistributionInstallJob; +import org.siouan.frontendgradleplugin.core.DistributionInstaller; +import org.siouan.frontendgradleplugin.core.DistributionInstallerSettings; +import org.siouan.frontendgradleplugin.core.DistributionPostInstallException; import org.siouan.frontendgradleplugin.core.DistributionUrlResolverException; import org.siouan.frontendgradleplugin.core.DownloadException; import org.siouan.frontendgradleplugin.core.InvalidDistributionException; import org.siouan.frontendgradleplugin.core.UnsupportedDistributionArchiveException; +import org.siouan.frontendgradleplugin.core.Utils; import org.siouan.frontendgradleplugin.core.YarnDistributionUrlResolver; /** @@ -59,11 +62,22 @@ public Property getYarnInstallDirectory() { return yarnInstallDirectory; } + /** + * Executes the task: downloads and installs the distribution. + * + * @throws IOException If an I/O error occured. + * @throws InvalidDistributionException If the downloaded distribution is invalid. + * @throws DistributionUrlResolverException If the URL to download the distribution cannot be downloaded. + * @throws UnsupportedDistributionArchiveException If the type of the distribution is not supported. + * @throws DownloadException If a download error occured. + * @throws DistributionPostInstallException If the post-install action has failed. + */ @TaskAction public void execute() throws IOException, InvalidDistributionException, DistributionUrlResolverException, - UnsupportedDistributionArchiveException, DownloadException { - new DistributionInstallJob(this, + UnsupportedDistributionArchiveException, DownloadException, DistributionPostInstallException { + final DistributionInstallerSettings settings = new DistributionInstallerSettings(this, Utils.getSystemOsName(), new YarnDistributionUrlResolver(yarnVersion.get(), yarnDistributionUrl.getOrNull()), null, - yarnInstallDirectory.getOrNull()).install(); + yarnInstallDirectory.getOrNull(), null); + new DistributionInstaller(settings).install(); } } diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/DistributionInstallJobTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/DistributionInstallerTest.java similarity index 66% rename from src/test/java/org/siouan/frontendgradleplugin/core/DistributionInstallJobTest.java rename to src/test/java/org/siouan/frontendgradleplugin/core/DistributionInstallerTest.java index f4ca435d..ed4926ab 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/DistributionInstallJobTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/DistributionInstallerTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import java.io.File; @@ -28,12 +29,11 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; /** - * Unit tests for the {@link DistributionInstallJob} class. + * Unit tests for the {@link DistributionInstaller} class. */ -public class DistributionInstallJobTest { +public class DistributionInstallerTest { @TempDir protected File temporaryDirectory; @@ -53,8 +53,11 @@ public class DistributionInstallJobTest { @Mock private DistributionValidator validator; + @Mock + private DistributionPostInstallAction listener; + @BeforeEach - public void setUp() throws IOException { + void setUp() throws IOException { MockitoAnnotations.initMocks(this); when(task.getProject()).thenReturn(project); @@ -65,74 +68,79 @@ public void setUp() throws IOException { } @Test - public void shouldFailWhenDistributionNotFound() throws DistributionUrlResolverException, MalformedURLException { + void shouldFailWhenDistributionNotFound() throws DistributionUrlResolverException, MalformedURLException { when(urlResolver.resolve()).thenReturn(URI.create("https://pg.htrhjyt.gvdfciz/htrsdf").toURL()); - final DistributionInstallJob job = new DistributionInstallJob(task, urlResolver, null, - new File(temporaryDirectory, "install")); + final DistributionInstaller job = new DistributionInstaller( + new DistributionInstallerSettings(task, Utils.getSystemOsName(), urlResolver, null, + new File(temporaryDirectory, "install"), listener)); assertThatThrownBy(job::install).isInstanceOf(DownloadException.class); verify(urlResolver).resolve(); verifyNoMoreInteractions(urlResolver); + verifyZeroInteractions(validator, listener); } @Test - public void shouldFailWhenDistributionArchiveIsCorrupted() + void shouldFailWhenDistributionArchiveIsCorrupted() throws DistributionUrlResolverException, InvalidDistributionException { final URL distributionUrl = getClass().getClassLoader().getResource("distribution.tar.gz"); - final File installDirectory = new File(temporaryDirectory, "install"); when(urlResolver.resolve()).thenReturn(distributionUrl); final Exception expectedException = new InvalidDistributionException(""); doThrow(expectedException).when(validator).validate(eq(distributionUrl), any(File.class)); - final DistributionInstallJob job = new DistributionInstallJob(task, urlResolver, validator, - new File(temporaryDirectory, "install")); + final DistributionInstaller job = new DistributionInstaller( + new DistributionInstallerSettings(task, Utils.getSystemOsName(), urlResolver, validator, + new File(temporaryDirectory, "install"), listener)); assertThatThrownBy(job::install).isEqualTo(expectedException); verify(urlResolver).resolve(); - verifyNoMoreInteractions(urlResolver); verify(validator).validate(eq(distributionUrl), any(File.class)); - verifyNoMoreInteractions(validator); + verifyNoMoreInteractions(urlResolver, validator); + verifyZeroInteractions(listener); } @Test - public void shouldFailWhenDistributionArchiveIsNotSupported() throws DistributionUrlResolverException { + void shouldFailWhenDistributionArchiveIsNotSupported() throws DistributionUrlResolverException { final URL distributionUrl = getClass().getClassLoader().getResource("node-v10.15.1.txt"); - final File installDirectory = new File(temporaryDirectory, "install"); when(urlResolver.resolve()).thenReturn(distributionUrl); - final DistributionInstallJob job = new DistributionInstallJob(task, urlResolver, null, - new File(temporaryDirectory, "install")); + final DistributionInstaller job = new DistributionInstaller( + new DistributionInstallerSettings(task, Utils.getSystemOsName(), urlResolver, null, + new File(temporaryDirectory, "install"), listener)); assertThatThrownBy(job::install).isInstanceOf(UnsupportedDistributionArchiveException.class); verify(urlResolver).resolve(); verifyNoMoreInteractions(urlResolver); + verifyZeroInteractions(validator, listener); } @Test - public void shouldDownloadDistribution() + void shouldDownloadDistribution() throws IOException, DistributionUrlResolverException, InvalidDistributionException, - UnsupportedDistributionArchiveException, DownloadException { + UnsupportedDistributionArchiveException, DownloadException, DistributionPostInstallException { final URL distributionUrl = getClass().getClassLoader().getResource("node-v10.15.3.zip"); final String distributionUrlAsString = distributionUrl.toString(); final File installDirectory = new File(temporaryDirectory, "install"); when(urlResolver.resolve()).thenReturn(distributionUrl); - final DistributionInstallJob job = new DistributionInstallJob(task, urlResolver, null, - new File(temporaryDirectory, "install")); + final DistributionInstallerSettings settings = new DistributionInstallerSettings(task, Utils.getSystemOsName(), + urlResolver, null, new File(temporaryDirectory, "install"), listener); + final DistributionInstaller job = new DistributionInstaller(settings); // Emulate exploding distribution when(project.copy(ArgumentMatchers.>any())) - .then(invocation -> explodeArchive(invocation, distributionUrlAsString, installDirectory)); + .then(invocation -> explodeArchive(distributionUrlAsString, installDirectory)); job.install(); verify(urlResolver).resolve(); - verifyNoMoreInteractions(urlResolver); verify(project).copy(ArgumentMatchers.>any()); assertThat(installDirectory.list()).isNotEmpty(); + verify(listener).onDistributionInstalled(settings); + verifyNoMoreInteractions(urlResolver, listener); + verifyZeroInteractions(validator); } - private WorkResult explodeArchive(final InvocationOnMock invocation, final String distributionUrl, - final File installDirectory) throws IOException { + private WorkResult explodeArchive(final String distributionUrl, final File installDirectory) throws IOException { final String distributionFilename = distributionUrl.substring(distributionUrl.lastIndexOf('/') + 1); final File copyDestDirectory = new File(installDirectory, Utils.removeExtension(distributionFilename)); Files.createDirectory(copyDestDirectory.toPath()); diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/DownloaderImplTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/DownloaderImplTest.java index 00ceff41..650031ad 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/DownloaderImplTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/DownloaderImplTest.java @@ -21,7 +21,7 @@ public class DownloaderImplTest { protected File temporaryDirectory; @Test - public void shouldFailWhenResourceNotFound() throws IOException { + void shouldFailWhenResourceNotFound() throws IOException { final File resourceFile = new File(temporaryDirectory, RESOURCE_NAME); final File downloadDirectory = new File(temporaryDirectory, "download"); Files.createDirectory(downloadDirectory.toPath()); @@ -32,7 +32,7 @@ public void shouldFailWhenResourceNotFound() throws IOException { } @Test - public void shouldFailWhenDestinationFileCannotBeCreated() throws Exception { + void shouldFailWhenDestinationFileCannotBeCreated() throws Exception { final File resourceFile = new File(temporaryDirectory, RESOURCE_NAME); final File downloadDirectory = new File(temporaryDirectory, "download"); final File destinationFile = new File("/volezp/gixkkle"); @@ -45,7 +45,7 @@ public void shouldFailWhenDestinationFileCannotBeCreated() throws Exception { } @Test - public void shouldDownloadLocalResource() throws Exception { + void shouldDownloadLocalResource() throws Exception { final File resourceFile = new File(temporaryDirectory, RESOURCE_NAME); final File downloadDirectory = new File(temporaryDirectory, "download"); final File destinationDirectory = new File(temporaryDirectory, "install"); diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/ExecSpecActionTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/ExecSpecActionTest.java index 8c67fdb8..156e9ba5 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/ExecSpecActionTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/ExecSpecActionTest.java @@ -1,11 +1,15 @@ package org.siouan.frontendgradleplugin.core; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -28,15 +32,6 @@ public class ExecSpecActionTest { private static final String SCRIPT = " run script "; - private static final File NODE_INSTALL_DIRECTORY = new File("/usr/lib/node"); - - private static final File YARN_INSTALL_DIRECTORY = new File("/usr/lib/yarn"); - - private static final String NODE_INSTALL_PATH = NODE_INSTALL_DIRECTORY.getAbsolutePath() + File.pathSeparatorChar; - - private static final String YARN_INSTALL_PATH = - YARN_INSTALL_DIRECTORY.getAbsolutePath() + File.separatorChar + "bin" + File.pathSeparatorChar; - private static final String PATH_ENVIRONMENT = "/usr/bin:/usr/lib"; @TempDir @@ -52,73 +47,107 @@ public class ExecSpecActionTest { private ArgumentCaptor> argsCaptor; @BeforeEach - public void setUp() { + void setUp() { MockitoAnnotations.initMocks(this); } @Test - public void shouldConfigureNpmCommandWithWindowsCmd() { + void shouldFailBuildingActionWhenNodeExecutableCannotBeFound() { + assertThatThrownBy( + () -> new ExecSpecAction(false, temporaryDirectory, temporaryDirectory, "", SCRIPT, afterConfigured)) + .isInstanceOf(ExecutableNotFoundException.class).hasMessage(ExecutableNotFoundException.NODE); + } + + @Test + void shouldFailBuildingActionWhenNpmExecutableCannotBeFound() throws IOException { + Files.createFile(temporaryDirectory.toPath().resolve("node.exe")); + assertThatThrownBy(() -> new ExecSpecAction(false, temporaryDirectory, temporaryDirectory, "Windows NT", SCRIPT, + afterConfigured)).isInstanceOf(ExecutableNotFoundException.class) + .hasMessage(ExecutableNotFoundException.NPM); + } + + @Test + void shouldFailBuildingActionWhenYarnExecutableCannotBeFound() throws IOException { + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + Files.createFile(binDirectory.resolve("node")); + assertThatThrownBy( + () -> new ExecSpecAction(true, temporaryDirectory, temporaryDirectory, "Mac OS X", SCRIPT, afterConfigured)) + .isInstanceOf(ExecutableNotFoundException.class).hasMessage(ExecutableNotFoundException.YARN); + } + + @Test + void shouldConfigureNpmCommandWithWindowsCmd() throws ExecutableNotFoundException, IOException { + final Path nodeExecutable = Files.createFile(temporaryDirectory.toPath().resolve("node.exe")); + final Path npmExecutable = Files.createFile(temporaryDirectory.toPath().resolve("npm.cmd")); final String script = SCRIPT; final String pathEnvironment = PATH_ENVIRONMENT; - final ExecSpecAction action = new ExecSpecAction(false, NODE_INSTALL_DIRECTORY, YARN_INSTALL_DIRECTORY, script, - afterConfigured, "Windows NT"); + final ExecSpecAction action = new ExecSpecAction(false, temporaryDirectory, temporaryDirectory, "Windows NT", + script, afterConfigured); when(execSpec.getEnvironment()).thenReturn(Collections.singletonMap("Path", pathEnvironment)); action.execute(execSpec); final List expectedArgs = new ArrayList<>(); - expectedArgs.add("/c"); - expectedArgs.add(String.join(" ", ExecSpecAction.NPM_EXECUTABLE, script.trim())); - assertExecSpecWith(ExecSpecAction.CMD_EXECUTABLE, expectedArgs, pathEnvironment, true, false); + expectedArgs.add(ExecSpecAction.CMD_RUN_EXIT_FLAG); + expectedArgs.add(String.join(" ", '"' + npmExecutable.toString() + '"', script.trim())); + assertExecSpecWith(ExecSpecAction.CMD_EXECUTABLE, expectedArgs, pathEnvironment, nodeExecutable.getParent()); } @Test - public void shouldConfigureYarnCommandWithWindowsCmd() { + void shouldConfigureYarnCommandWithWindowsCmd() throws ExecutableNotFoundException, IOException { + final Path nodeExecutable = Files.createFile(temporaryDirectory.toPath().resolve("node.exe")); + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + final Path yarnExecutable = Files.createFile(binDirectory.resolve("yarn.cmd")); final String script = SCRIPT; final String pathEnvironment = PATH_ENVIRONMENT; - final ExecSpecAction action = new ExecSpecAction(true, NODE_INSTALL_DIRECTORY, YARN_INSTALL_DIRECTORY, script, - afterConfigured, "Windows NT"); + final ExecSpecAction action = new ExecSpecAction(true, temporaryDirectory, temporaryDirectory, "Windows NT", + script, afterConfigured); when(execSpec.getEnvironment()).thenReturn(Collections.singletonMap("PATH", pathEnvironment)); action.execute(execSpec); final List expectedArgs = new ArrayList<>(); - expectedArgs.add("/c"); - expectedArgs.add(String.join(" ", ExecSpecAction.YARN_EXECUTABLE, script.trim())); - assertExecSpecWith(ExecSpecAction.CMD_EXECUTABLE, expectedArgs, pathEnvironment, true, true); + expectedArgs.add(ExecSpecAction.CMD_RUN_EXIT_FLAG); + expectedArgs.add(String.join(" ", '"' + yarnExecutable.toString() + '"', script.trim())); + assertExecSpecWith(ExecSpecAction.CMD_EXECUTABLE, expectedArgs, pathEnvironment, nodeExecutable.getParent()); } @Test - public void shouldConfigureNpmCommandWithUnixShell() { + void shouldConfigureNpmCommandWithUnixShell() throws ExecutableNotFoundException, IOException { + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + final Path nodeExecutable = Files.createFile(binDirectory.resolve("node")); + final Path npmExecutable = Files.createFile(binDirectory.resolve("npm")); final String script = SCRIPT; final String pathEnvironment = PATH_ENVIRONMENT; - final ExecSpecAction action = new ExecSpecAction(false, NODE_INSTALL_DIRECTORY, YARN_INSTALL_DIRECTORY, script, - afterConfigured, "Linux"); + final ExecSpecAction action = new ExecSpecAction(false, temporaryDirectory, temporaryDirectory, "Linux", script, + afterConfigured); when(execSpec.getEnvironment()).thenReturn(Collections.singletonMap("Path", pathEnvironment)); action.execute(execSpec); - assertExecSpecWith(ExecSpecAction.NPM_EXECUTABLE, Arrays.asList(script.trim().split("\\s+")), pathEnvironment, - true, false); + assertExecSpecWith(npmExecutable.toString(), Arrays.asList(script.trim().split("\\s+")), pathEnvironment, + nodeExecutable.getParent()); } @Test - public void shouldConfigureYarnCommandWithUnixShell() { + void shouldConfigureYarnCommandWithUnixShell() throws ExecutableNotFoundException, IOException { + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + final Path nodeExecutable = Files.createFile(binDirectory.resolve("node")); + final Path yarnExecutable = Files.createFile(binDirectory.resolve("yarn")); final String script = SCRIPT; final String pathEnvironment = PATH_ENVIRONMENT; - final ExecSpecAction action = new ExecSpecAction(true, NODE_INSTALL_DIRECTORY, YARN_INSTALL_DIRECTORY, script, - afterConfigured, "Mac OS X"); + final ExecSpecAction action = new ExecSpecAction(true, temporaryDirectory, temporaryDirectory, "Mac OS X", + script, afterConfigured); when(execSpec.getEnvironment()).thenReturn(Collections.singletonMap("PATH", pathEnvironment)); action.execute(execSpec); - assertExecSpecWith(ExecSpecAction.YARN_EXECUTABLE, Arrays.asList(script.trim().split("\\s+")), pathEnvironment, - true, true); + assertExecSpecWith(yarnExecutable.toString(), Arrays.asList(script.trim().split("\\s+")), pathEnvironment, + nodeExecutable.getParent()); } private void assertExecSpecWith(final String expectedExecutable, final List expectedArgs, - final String initialPathEnvironment, final boolean nodeInstallPathIncluded, - final boolean yarnInstallPathIncluded) { + final String initialPathEnvironment, final Path nodeExecutableDirectory) { verify(execSpec).setExecutable(expectedExecutable); verify(execSpec).setArgs(argsCaptor.capture()); final List args = argsCaptor.getValue(); @@ -130,16 +159,7 @@ private void assertExecSpecWith(final String expectedExecutable, final List new FileHasherImpl().hash(new File("/dummy"))).isInstanceOf(IOException.class); } @Test - public void shouldReturnValidSha256HashWithDefaultBufferingCapacity() throws IOException, NoSuchAlgorithmException { + void shouldReturnValidSha256HashWithDefaultBufferingCapacity() throws IOException, NoSuchAlgorithmException { final Path temporaryFile = Files .write(Paths.get(temporaryDirectory.getAbsolutePath(), "file-for-hashing.txt"), DATA.getBytes()); final String hash = new FileHasherImpl().hash(temporaryFile.toFile()); diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/FrontendGradlePluginTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/FrontendGradlePluginTest.java index c8daef5a..68112675 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/FrontendGradlePluginTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/FrontendGradlePluginTest.java @@ -28,7 +28,7 @@ public class FrontendGradlePluginTest { private Project project; @BeforeEach - public void setUp() { + void setUp() { MockitoAnnotations.initMocks(this); project = ProjectBuilder.builder().build(); @@ -36,7 +36,7 @@ public void setUp() { } @Test - public void shouldRegisterTasksWithDefaultExtensionValuesWhenApplied() { + void shouldRegisterTasksWithDefaultExtensionValuesWhenApplied() { plugin.apply(project); final FrontendExtension extension = project.getExtensions().findByType(FrontendExtension.class); @@ -100,7 +100,7 @@ public void shouldRegisterTasksWithDefaultExtensionValuesWhenApplied() { } @Test - public void shouldRegisterTasksWithCustomExtensionValuesWhenApplied() { + void shouldRegisterTasksWithCustomExtensionValuesWhenApplied() { plugin.apply(project); final FrontendExtension extension = project.getExtensions().findByType(FrontendExtension.class); diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionChecksumReaderImplTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionChecksumReaderImplTest.java index 72b53218..23a68683 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionChecksumReaderImplTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionChecksumReaderImplTest.java @@ -24,14 +24,14 @@ class NodeDistributionChecksumReaderImplTest { protected File temporaryDirectory; @Test - public void shouldFailWhenChecksumFileNotReadable() { + void shouldFailWhenChecksumFileNotReadable() { assertThatThrownBy( () -> new NodeDistributionChecksumReaderImpl().readHash(new File("dummy-filename"), DISTRIBUTION_FILENAME)) .isInstanceOf(IOException.class); } @Test - public void shouldFailWhenChecksumNotFound() throws IOException { + void shouldFailWhenChecksumNotFound() throws IOException { final File checksumFile = new File(temporaryDirectory, CHECKSUM_FILENAME); Files.createFile(checksumFile.toPath()); assertThatThrownBy(() -> new NodeDistributionChecksumReaderImpl().readHash(checksumFile, DISTRIBUTION_FILENAME)) @@ -39,7 +39,7 @@ public void shouldFailWhenChecksumNotFound() throws IOException { } @Test - public void shouldReturnChecksumWhenAtEndOfFileWithoutNewLine() + void shouldReturnChecksumWhenAtEndOfFileWithoutNewLine() throws IOException, NodeDistributionChecksumNotFoundException { final String checksum = "523ab86h853e86"; final File checksumFile = new File(temporaryDirectory, CHECKSUM_FILENAME); @@ -55,7 +55,7 @@ public void shouldReturnChecksumWhenAtEndOfFileWithoutNewLine() } @Test - public void shouldReturnChecksumWhenAtEndOfFileWithNewLine() + void shouldReturnChecksumWhenAtEndOfFileWithNewLine() throws IOException, NodeDistributionChecksumNotFoundException { final String checksum = "523ab86h853e86"; final File checksumFile = new File(temporaryDirectory, CHECKSUM_FILENAME); diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolverTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolverTest.java index 7cfa97a2..b429115b 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolverTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionUrlResolverTest.java @@ -15,25 +15,24 @@ class NodeDistributionUrlResolverTest { private static final String VERSION = "3.5.2"; @Test - public void shouldFailWhenBuildingInstanceWithNullVersionAndNullDistributionUrl() { + void shouldFailWhenBuildingInstanceWithNullVersionAndNullDistributionUrl() { assertThatThrownBy(() -> new NodeDistributionUrlResolver(null, null)) .isInstanceOf(IllegalArgumentException.class); } @Test - public void shouldFailWhenResolvingWithInvalidDistributionUrl() { + void shouldFailWhenResolvingWithInvalidDistributionUrl() { assertThatThrownBy(() -> new NodeDistributionUrlResolver(null, "fot://greg:://grrg:").resolve()) .isInstanceOf(DistributionUrlResolverException.class).hasCauseInstanceOf(MalformedURLException.class); } @Test - public void shouldReturnDefaultUrlWhenResolvingWithVersionAndNoDistributionUrl() - throws DistributionUrlResolverException { + void shouldReturnDefaultUrlWhenResolvingWithVersionAndNoDistributionUrl() throws DistributionUrlResolverException { assertThat(new NodeDistributionUrlResolver(VERSION, null).resolve()).isNotNull(); } @Test - public void shouldReturnDistributionUrlWhenResolvingWithNoVersionAndDistributionUrl() + void shouldReturnDistributionUrlWhenResolvingWithNoVersionAndDistributionUrl() throws DistributionUrlResolverException { final String distributionUrl = "http://url"; assertThat(new NodeDistributionUrlResolver(null, distributionUrl).resolve().toString()) @@ -41,37 +40,37 @@ public void shouldReturnDistributionUrlWhenResolvingWithNoVersionAndDistribution } @Test - public void shouldResolveUrlWhenOsIsWindowsNTAndJreArchIsX86() throws DistributionUrlResolverException { + void shouldResolveUrlWhenOsIsWindowsNTAndJreArchIsX86() throws DistributionUrlResolverException { assertThat(new NodeDistributionUrlResolver(VERSION, null, "Windows NT", "x86").resolve().toString()) .endsWith("-win-x86.zip"); } @Test - public void shouldResolveUrlWhenOsIsWindowsNTAndJreArchIsX64() throws DistributionUrlResolverException { + void shouldResolveUrlWhenOsIsWindowsNTAndJreArchIsX64() throws DistributionUrlResolverException { assertThat(new NodeDistributionUrlResolver(VERSION, null, "Windows NT", "x64").resolve().toString()) .endsWith("-win-x64.zip"); } @Test - public void shouldResolveUrlWhenOsIsLinuxAndJreArchIsAmd64() throws DistributionUrlResolverException { + void shouldResolveUrlWhenOsIsLinuxAndJreArchIsAmd64() throws DistributionUrlResolverException { assertThat(new NodeDistributionUrlResolver(VERSION, null, "Linux", "amd64").resolve().toString()) .endsWith("-linux-x64.tar.gz"); } @Test - public void shouldFailWhenOsIsLinuxAndJreArchIsI386() { + void shouldFailWhenOsIsLinuxAndJreArchIsI386() { assertThatThrownBy(() -> new NodeDistributionUrlResolver(VERSION, null, "Linux", "i386").resolve()) .isInstanceOf(DistributionUrlResolverException.class).hasNoCause(); } @Test - public void shouldResolveUrlWhenOsIsMacAndJreArchIsPPC() throws DistributionUrlResolverException { + void shouldResolveUrlWhenOsIsMacAndJreArchIsPPC() throws DistributionUrlResolverException { assertThat(new NodeDistributionUrlResolver(VERSION, null, "Mac OS X", "ppc").resolve().toString()) .endsWith("-darwin-x64.tar.gz"); } @Test - public void shouldFailWhenOsIsSolarisAndJreArchIsSparc() { + void shouldFailWhenOsIsSolarisAndJreArchIsSparc() { assertThatThrownBy(() -> new NodeDistributionUrlResolver(VERSION, null, "Solaris", "sparc").resolve()) .isInstanceOf(DistributionUrlResolverException.class).hasNoCause(); } diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidatorTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidatorTest.java index a6b5dbea..4eff4b7e 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidatorTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/NodeDistributionValidatorTest.java @@ -58,7 +58,7 @@ class NodeDistributionValidatorTest { private NodeDistributionValidator validator; @BeforeEach - public void setUp() throws IOException { + void setUp() throws IOException { MockitoAnnotations.initMocks(this); when(task.getProject()).thenReturn(project); @@ -69,7 +69,7 @@ public void setUp() throws IOException { } @Test - public void shouldFailWhenChecksumFileDownloadFails() throws DownloadException { + void shouldFailWhenChecksumFileDownloadFails() throws DownloadException { final Exception expectedException = mock(DownloadException.class); doThrow(expectedException).when(downloader).download(any(URL.class), any(File.class)); @@ -84,7 +84,7 @@ public void shouldFailWhenChecksumFileDownloadFails() throws DownloadException { } @Test - public void shouldFailWhenChecksumFileCannotBeRead() + void shouldFailWhenChecksumFileCannotBeRead() throws DownloadException, IOException, NodeDistributionChecksumNotFoundException { final URL distributionUrl = URI.create(DISTRIBUTION_URL).toURL(); final Exception expectedException = mock(IOException.class); @@ -103,7 +103,7 @@ public void shouldFailWhenChecksumFileCannotBeRead() } @Test - public void shouldFailWhenDistributionFileCannotBeHashed() + void shouldFailWhenDistributionFileCannotBeHashed() throws DownloadException, IOException, NodeDistributionChecksumNotFoundException { final URL distributionUrl = URI.create(DISTRIBUTION_URL).toURL(); final String distributionFilename = DISTRIBUTION_FILENAME; @@ -125,7 +125,7 @@ public void shouldFailWhenDistributionFileCannotBeHashed() } @Test - public void shouldFailWhenDistributionFileHashIsInvalid() + void shouldFailWhenDistributionFileHashIsInvalid() throws DownloadException, IOException, NodeDistributionChecksumNotFoundException { final URL distributionUrl = URI.create(DISTRIBUTION_URL).toURL(); final String distributionFilename = DISTRIBUTION_FILENAME; @@ -147,7 +147,7 @@ public void shouldFailWhenDistributionFileHashIsInvalid() } @Test - public void shouldReturnWhenDistributionFileIsValid() + void shouldReturnWhenDistributionFileIsValid() throws DownloadException, IOException, NodeDistributionChecksumNotFoundException, InvalidDistributionException { final URL distributionUrl = URI.create(DISTRIBUTION_URL).toURL(); final String distributionFilename = DISTRIBUTION_FILENAME; diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/NodePostInstallActionTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/NodePostInstallActionTest.java new file mode 100644 index 00000000..81f8a947 --- /dev/null +++ b/src/test/java/org/siouan/frontendgradleplugin/core/NodePostInstallActionTest.java @@ -0,0 +1,88 @@ +package org.siouan.frontendgradleplugin.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.EnumSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; + +/** + * Unit tests for the {@link NodePostInstallAction} class. + */ +class NodePostInstallActionTest { + + @TempDir + protected File temporaryDirectory; + + @Test + void shouldDoNothingUnderWindows() throws DistributionPostInstallException { + final NodePostInstallAction action = new NodePostInstallAction(); + final DistributionInstallerSettings settings = new DistributionInstallerSettings(null, "Windows NT", null, null, + null, action); + + action.onDistributionInstalled(settings); + } + + @Test + void shouldDoNothingIfNpmExecutableNotFound() throws DistributionPostInstallException { + final NodePostInstallAction action = new NodePostInstallAction(); + final DistributionInstallerSettings settings = new DistributionInstallerSettings(null, "Linux", null, null, + temporaryDirectory, action); + + action.onDistributionInstalled(settings); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void shouldSetTargetExecutableWhenNpmExecutableIsSymbolicLink() + throws DistributionPostInstallException, IOException { + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + final Set permissions = EnumSet + .of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); + final Path targetFile = Files + .createFile(binDirectory.resolve("target"), PosixFilePermissions.asFileAttribute(permissions)); + Files.createSymbolicLink(binDirectory.resolve("npm"), targetFile); + final NodePostInstallAction action = new NodePostInstallAction(); + final DistributionInstallerSettings settings = new DistributionInstallerSettings(null, "Mac OS X", null, null, + temporaryDirectory, action); + + action.onDistributionInstalled(settings); + + final Set newPermissions = Files.getPosixFilePermissions(targetFile); + assertThat(newPermissions).hasSize(permissions.size() + 1).containsAll(permissions) + .contains(PosixFilePermission.OWNER_EXECUTE); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + void shouldRestoreSymbolicLinkWhenNpmExecutableIsFile() throws DistributionPostInstallException, IOException { + final Path targetDirectory = Files.createDirectories( + temporaryDirectory.toPath().resolve("lib").resolve("node_modules").resolve("npm").resolve("bin")); + Files.createFile(targetDirectory.resolve("npm-cli.js")); + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + final Set permissions = EnumSet + .of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); + final Path npmExecutable = Files + .createFile(binDirectory.resolve("npm"), PosixFilePermissions.asFileAttribute(permissions)); + final NodePostInstallAction action = new NodePostInstallAction(); + final DistributionInstallerSettings settings = new DistributionInstallerSettings(null, "Linux", null, null, + temporaryDirectory, action); + + action.onDistributionInstalled(settings); + + assertThat(Files.isSymbolicLink(npmExecutable)).isTrue(); + final Set newPermissions = Files.getPosixFilePermissions(npmExecutable); + assertThat(newPermissions).hasSize(permissions.size() + 1).containsAll(permissions) + .contains(PosixFilePermission.OWNER_EXECUTE); + } +} diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/ScriptRunJobTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/RunScriptJobTest.java similarity index 76% rename from src/test/java/org/siouan/frontendgradleplugin/core/ScriptRunJobTest.java rename to src/test/java/org/siouan/frontendgradleplugin/core/RunScriptJobTest.java index 4440b51d..4de30bf1 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/ScriptRunJobTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/RunScriptJobTest.java @@ -7,6 +7,9 @@ import static org.mockito.Mockito.when; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.gradle.api.Project; import org.gradle.api.Task; @@ -14,19 +17,23 @@ import org.gradle.process.ExecResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Unit tests for the {@link ScriptRunJob} class. + * Unit tests for the {@link RunScriptJob} class. */ -class ScriptRunJobTest { +class RunScriptJobTest { private static final String TASK_NAME = "task"; private static final String SCRIPT = "script"; + @TempDir + protected File temporaryDirectory; + @Mock private Task task; @@ -40,7 +47,7 @@ class ScriptRunJobTest { private ExecResult result; @BeforeEach - public void setUp() { + void setUp() { MockitoAnnotations.initMocks(this); when(task.getProject()).thenReturn(project); @@ -48,12 +55,14 @@ public void setUp() { } @Test - public void shouldRunScript() { + void shouldRunScript() throws ExecutableNotFoundException, IOException { + Files.createFile(temporaryDirectory.toPath().resolve("node.exe")); + final Path binDirectory = Files.createDirectory(temporaryDirectory.toPath().resolve("bin")); + Files.createFile(binDirectory.resolve("yarn.cmd")); final boolean yarnEnabled = true; - final File nodeInstallDirectory = new File("node"); - final File yarnInstallDirectory = new File("yarn"); final String script = SCRIPT; - final ScriptRunJob job = new ScriptRunJob(task, true, nodeInstallDirectory, yarnInstallDirectory, script); + final RunScriptJob job = new RunScriptJob(task, true, temporaryDirectory, temporaryDirectory, script, + "Windows NT"); final ExecResult execResult = mock(ExecResult.class); when(project.exec(any(ExecSpecAction.class))).thenReturn(execResult); when(execResult.rethrowFailure()).thenReturn(execResult); @@ -61,7 +70,7 @@ public void shouldRunScript() { job.run(); verify(project) - .exec(argThat(new ExecSpecActionMatcher(yarnEnabled, nodeInstallDirectory, yarnInstallDirectory, script))); + .exec(argThat(new ExecSpecActionMatcher(yarnEnabled, temporaryDirectory, temporaryDirectory, script))); } private static class ExecSpecActionMatcher implements ArgumentMatcher { diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/UtilsTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/UtilsTest.java index f390408b..dc70bc9b 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/UtilsTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/UtilsTest.java @@ -1,8 +1,11 @@ package org.siouan.frontendgradleplugin.core; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -18,14 +21,24 @@ class UtilsTest { protected File temporaryDirectory; @Test - public void shouldFailWhenSourceDirectoryIsNotAValidDirectory() { + void shouldFailWhenSourceDirectoryIsNotAValidDirectory() { assertThatThrownBy(() -> Utils.moveFiles(new File(temporaryDirectory, "src"), temporaryDirectory)) .isInstanceOf(IllegalArgumentException.class); } @Test - public void shouldFailWhenDestDirectoryIsNotAValidDirectory() { + void shouldFailWhenDestDirectoryIsNotAValidDirectory() { assertThatThrownBy(() -> Utils.moveFiles(temporaryDirectory, new File(temporaryDirectory, "dest"))) .isInstanceOf(IllegalArgumentException.class); } + + @Test + void shouldNotTouchFilePermissionsWhenOsIsWindows() throws IOException { + assertThat(Utils.setFileExecutable(Paths.get("afile"), "Windows NT")).isFalse(); + } + + @Test + void shouldNotTouchFilePermissionsWhenFileNotFound() throws IOException { + assertThat(Utils.setFileExecutable(Paths.get("afile"), "Linux")).isFalse(); + } } diff --git a/src/test/java/org/siouan/frontendgradleplugin/core/YarnDistributionUrlResolverTest.java b/src/test/java/org/siouan/frontendgradleplugin/core/YarnDistributionUrlResolverTest.java index ff5e779b..d8528e6f 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/core/YarnDistributionUrlResolverTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/core/YarnDistributionUrlResolverTest.java @@ -13,20 +13,19 @@ class YarnDistributionUrlResolverTest { @Test - public void shouldReturnDefaultUrlWhenResolvingWithVersionAndNoDistributionUrl() - throws DistributionUrlResolverException { + void shouldReturnDefaultUrlWhenResolvingWithVersionAndNoDistributionUrl() throws DistributionUrlResolverException { assertThat(new YarnDistributionUrlResolver("3.65.2", null).resolve()).isNotNull(); } @Test - public void shouldFailWhenDistributionUrlIsInvalid() { + void shouldFailWhenDistributionUrlIsInvalid() { final String distributionUrl = "siouan://test"; assertThatThrownBy(() -> new YarnDistributionUrlResolver(null, distributionUrl).resolve()) .isInstanceOf(DistributionUrlResolverException.class).hasCauseInstanceOf(MalformedURLException.class); } @Test - public void shouldReturnDistributionUrlWhenResolvingWithNoVersionAndDistributionUrl() + void shouldReturnDistributionUrlWhenResolvingWithNoVersionAndDistributionUrl() throws DistributionUrlResolverException { final String distributionUrl = "http://url"; assertThat(new YarnDistributionUrlResolver(null, distributionUrl).resolve().toString()) diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/AssembleTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/AssembleTaskFuncTest.java index 91723c14..17c0e71a 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/AssembleTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/AssembleTaskFuncTest.java @@ -19,7 +19,9 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link AssembleTask} integration in a Gradle build. + * Functional tests to verify the {@link AssembleTask} integration in a Gradle build. Test cases uses fake Node/Yarn + * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call + * the 'node' executable with the same arguments. */ class AssembleTaskFuncTest { @@ -27,11 +29,12 @@ class AssembleTaskFuncTest { protected File projectDirectory; @Test - public void shouldAssembleFrontendWithNpmOrYarn() throws IOException, URISyntaxException { + void shouldAssembleFrontendWithNpmOrYarn() throws IOException, URISyntaxException { Files.copy(new File(getClass().getClassLoader().getResource("package-npm.json").toURI()).toPath(), projectDirectory.toPath().resolve("package.json")); final Map properties = new HashMap<>(); properties.put("nodeVersion", "10.15.3"); + properties.put("nodeDistributionUrl", getClass().getClassLoader().getResource("node-v10.15.3.zip").toString()); properties.put("assembleScript", "run assemble"); FunctionalTestHelper.createBuildFile(projectDirectory, properties); @@ -54,6 +57,8 @@ public void shouldAssembleFrontendWithNpmOrYarn() throws IOException, URISyntaxE projectDirectory.toPath().resolve("package.json"), StandardCopyOption.REPLACE_EXISTING); properties.put("yarnEnabled", true); properties.put("yarnVersion", "1.15.2"); + properties + .put("yarnDistributionUrl", getClass().getClassLoader().getResource("yarn-v1.15.2.tar.gz").toString()); FunctionalTestHelper.createBuildFile(projectDirectory, properties); final BuildResult result3 = runGradle(projectDirectory, FrontendGradlePlugin.GRADLE_ASSEMBLE_TASK_NAME); diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/CheckTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/CheckTaskFuncTest.java index 3d90d378..21928525 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/CheckTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/CheckTaskFuncTest.java @@ -19,7 +19,9 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link CheckTask} integration in a Gradle build. + * Functional tests to verify the {@link CheckTask} integration in a Gradle build. Test cases uses fake Node/Yarn + * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call + * the 'node' executable with the same arguments. */ class CheckTaskFuncTest { @@ -27,11 +29,12 @@ class CheckTaskFuncTest { protected File projectDirectory; @Test - public void shouldCheckFrontendWithNpmOrYarn() throws IOException, URISyntaxException { + void shouldCheckFrontendWithNpmOrYarn() throws IOException, URISyntaxException { Files.copy(new File(getClass().getClassLoader().getResource("package-npm.json").toURI()).toPath(), projectDirectory.toPath().resolve("package.json")); final Map properties = new HashMap<>(); properties.put("nodeVersion", "10.15.3"); + properties.put("nodeDistributionUrl", getClass().getClassLoader().getResource("node-v10.15.3.zip").toString()); properties.put("checkScript", "run check"); FunctionalTestHelper.createBuildFile(projectDirectory, properties); @@ -54,6 +57,8 @@ public void shouldCheckFrontendWithNpmOrYarn() throws IOException, URISyntaxExce projectDirectory.toPath().resolve("package.json"), StandardCopyOption.REPLACE_EXISTING); properties.put("yarnEnabled", true); properties.put("yarnVersion", "1.15.2"); + properties + .put("yarnDistributionUrl", getClass().getClassLoader().getResource("yarn-v1.15.2.tar.gz").toString()); FunctionalTestHelper.createBuildFile(projectDirectory, properties); final BuildResult result3 = runGradle(projectDirectory, FrontendGradlePlugin.GRADLE_CHECK_TASK_NAME); diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/CleanTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/CleanTaskFuncTest.java index bbc7d118..82a63ae7 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/CleanTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/CleanTaskFuncTest.java @@ -19,7 +19,9 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link CleanTask} integration in a Gradle build. + * Functional tests to verify the {@link CleanTask} integration in a Gradle build. Test cases uses fake Node/Yarn + * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call + * the 'node' executable with the same arguments. */ class CleanTaskFuncTest { @@ -27,11 +29,12 @@ class CleanTaskFuncTest { protected File projectDirectory; @Test - public void shouldCleanFrontendWithNpmOrYarn() throws IOException, URISyntaxException { + void shouldCleanFrontendWithNpmOrYarn() throws IOException, URISyntaxException { Files.copy(new File(getClass().getClassLoader().getResource("package-npm.json").toURI()).toPath(), projectDirectory.toPath().resolve("package.json")); final Map properties = new HashMap<>(); properties.put("nodeVersion", "10.15.3"); + properties.put("nodeDistributionUrl", getClass().getClassLoader().getResource("node-v10.15.3.zip").toString()); properties.put("cleanScript", "run clean"); FunctionalTestHelper.createBuildFile(projectDirectory, properties); @@ -54,6 +57,8 @@ public void shouldCleanFrontendWithNpmOrYarn() throws IOException, URISyntaxExce projectDirectory.toPath().resolve("package.json"), StandardCopyOption.REPLACE_EXISTING); properties.put("yarnEnabled", true); properties.put("yarnVersion", "1.15.2"); + properties + .put("yarnDistributionUrl", getClass().getClassLoader().getResource("yarn-v1.15.2.tar.gz").toString()); FunctionalTestHelper.createBuildFile(projectDirectory, properties); final BuildResult result3 = runGradle(projectDirectory, FrontendGradlePlugin.GRADLE_CLEAN_TASK_NAME); diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/InstallTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/InstallTaskFuncTest.java index 1e52ae03..2dca4341 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/InstallTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/InstallTaskFuncTest.java @@ -19,7 +19,9 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link InstallTask} integration in a Gradle build. + * Functional tests to verify the {@link InstallTask} integration in a Gradle build. Test cases uses fake Node/Yarn + * distributions, to avoid the download overhead. The 'yarn' and 'npm' executables in these distributions simply call + * the 'node' executable with the same arguments. */ class InstallTaskFuncTest { @@ -27,11 +29,12 @@ class InstallTaskFuncTest { protected File projectDirectory; @Test - public void shouldInstallFrontendWithNpmOrYarn() throws IOException, URISyntaxException { + void shouldInstallFrontendWithNpmOrYarn() throws IOException, URISyntaxException { Files.copy(new File(getClass().getClassLoader().getResource("package-npm.json").toURI()).toPath(), projectDirectory.toPath().resolve("package.json")); final Map properties = new HashMap<>(); properties.put("nodeVersion", "10.15.3"); + properties.put("nodeDistributionUrl", getClass().getClassLoader().getResource("node-v10.15.3.zip").toString()); FunctionalTestHelper.createBuildFile(projectDirectory, properties); final BuildResult result1 = runGradle(projectDirectory, FrontendGradlePlugin.INSTALL_TASK_NAME); @@ -51,6 +54,8 @@ public void shouldInstallFrontendWithNpmOrYarn() throws IOException, URISyntaxEx projectDirectory.toPath().resolve("package.json"), StandardCopyOption.REPLACE_EXISTING); properties.put("yarnEnabled", true); properties.put("yarnVersion", "1.15.2"); + properties + .put("yarnDistributionUrl", getClass().getClassLoader().getResource("yarn-v1.15.2.tar.gz").toString()); FunctionalTestHelper.createBuildFile(projectDirectory, properties); final BuildResult result3 = runGradle(projectDirectory, FrontendGradlePlugin.INSTALL_TASK_NAME); diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTaskFuncTest.java index 9ba10b61..b4ed76de 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/NodeInstallTaskFuncTest.java @@ -18,7 +18,8 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link NodeInstallTask} integration in a Gradle build. + * Functional tests to verify the {@link NodeInstallTask} integration in a Gradle build. Test cases uses a fake Node + * distribution, to avoid the download overhead and because the 'node' and 'npm' executables are never called. */ class NodeInstallTaskFuncTest { @@ -26,7 +27,7 @@ class NodeInstallTaskFuncTest { protected File projectDirectory; @Test - public void shouldFailInstallingNodeWhenVersionIsNotSet() throws IOException { + void shouldFailInstallingNodeWhenVersionIsNotSet() throws IOException { FunctionalTestHelper.createBuildFile(projectDirectory, Collections.emptyMap()); final BuildResult result = runGradleAndExpectFailure(projectDirectory, @@ -36,7 +37,7 @@ public void shouldFailInstallingNodeWhenVersionIsNotSet() throws IOException { } @Test - public void shouldFailInstallingNodeWhenDistributionCannotBeDownloadedWithUnknownVersion() throws IOException { + void shouldFailInstallingNodeWhenDistributionCannotBeDownloadedWithUnknownVersion() throws IOException { FunctionalTestHelper.createBuildFile(projectDirectory, Collections.singletonMap("nodeVersion", "0.76.34")); final BuildResult result = runGradleAndExpectFailure(projectDirectory, @@ -46,7 +47,7 @@ public void shouldFailInstallingNodeWhenDistributionCannotBeDownloadedWithUnknow } @Test - public void shouldFailInstallingNodeWhenDistributionCannotBeDownloadedWithInvalidUrl() throws IOException { + void shouldFailInstallingNodeWhenDistributionCannotBeDownloadedWithInvalidUrl() throws IOException { final Map properties = new HashMap<>(); properties.put("nodeVersion", "10.15.3"); properties.put("nodeDistributionUrl", "protocol://domain/unknown"); @@ -59,7 +60,7 @@ public void shouldFailInstallingNodeWhenDistributionCannotBeDownloadedWithInvali } @Test - public void shouldInstallNodeFirstAndNothingMoreSecondly() throws IOException { + void shouldInstallNodeFirstAndNothingMoreSecondly() throws IOException { final Map properties = new HashMap<>(); properties.put("nodeVersion", "10.15.3"); properties.put("nodeDistributionUrl", getClass().getClassLoader().getResource("node-v10.15.3.zip").toString()); diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/RunScriptTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/RunScriptTaskFuncTest.java index 4be26312..248ee26a 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/RunScriptTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/RunScriptTaskFuncTest.java @@ -19,7 +19,8 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link RunScriptTask} integration in a Gradle build. + * Functional tests to verify the {@link RunScriptTask} integration in a Gradle build. This functional test is the only + * one that uses real Node/Yarn distributions. */ class RunScriptTaskFuncTest { @@ -27,7 +28,7 @@ class RunScriptTaskFuncTest { protected File projectDirectory; @Test - public void shouldRunScriptFrontendWithNpmOrYarn() throws IOException, URISyntaxException { + void shouldRunScriptFrontendWithNpmOrYarn() throws IOException, URISyntaxException { Files.copy(new File(getClass().getClassLoader().getResource("package-npm.json").toURI()).toPath(), projectDirectory.toPath().resolve("package.json")); final Map properties = new HashMap<>(); @@ -37,7 +38,7 @@ public void shouldRunScriptFrontendWithNpmOrYarn() throws IOException, URISyntax customTaskDefinition.append(customTaskName); customTaskDefinition.append("', org.siouan.frontendgradleplugin.tasks.RunScriptTask) {\n"); customTaskDefinition.append("dependsOn tasks.named('installFrontend')\n"); - customTaskDefinition.append("script = 'run test'\n"); + customTaskDefinition.append("script = 'run another-script'\n"); customTaskDefinition.append("}\n"); FunctionalTestHelper.createBuildFile(projectDirectory, properties, customTaskDefinition.toString()); diff --git a/src/test/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTaskFuncTest.java b/src/test/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTaskFuncTest.java index 0eab5f2b..eb7a6ef3 100644 --- a/src/test/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTaskFuncTest.java +++ b/src/test/java/org/siouan/frontendgradleplugin/tasks/YarnInstallTaskFuncTest.java @@ -18,7 +18,8 @@ import org.siouan.frontendgradleplugin.util.FunctionalTestHelper; /** - * Functional tests to verify the {@link YarnInstallTask} integration in a Gradle build. + * Functional tests to verify the {@link YarnInstallTask} integration in a Gradle build. Test cases uses a fake Yarn + * distribution, to avoid the download overhead and because the 'yarn' executable is never called. */ class YarnInstallTaskFuncTest { @@ -26,7 +27,7 @@ class YarnInstallTaskFuncTest { protected File projectDirectory; @Test - public void shouldSkipInstallWhenYarnIsNotEnabled() throws IOException { + void shouldSkipInstallWhenYarnIsNotEnabled() throws IOException { FunctionalTestHelper.createBuildFile(projectDirectory, Collections.emptyMap()); final BuildResult result = runGradle(projectDirectory, FrontendGradlePlugin.YARN_INSTALL_TASK_NAME); @@ -35,7 +36,7 @@ public void shouldSkipInstallWhenYarnIsNotEnabled() throws IOException { } @Test - public void shouldFailInstallingYarnWhenVersionIsNotSet() throws IOException { + void shouldFailInstallingYarnWhenVersionIsNotSet() throws IOException { FunctionalTestHelper.createBuildFile(projectDirectory, Collections.singletonMap("yarnEnabled", true)); final BuildResult result = runGradleAndExpectFailure(projectDirectory, @@ -45,7 +46,7 @@ public void shouldFailInstallingYarnWhenVersionIsNotSet() throws IOException { } @Test - public void shouldFailInstallingYarnWhenDistributionCannotBeDownloadedWithUnknownVersion() throws IOException { + void shouldFailInstallingYarnWhenDistributionCannotBeDownloadedWithUnknownVersion() throws IOException { final Map properties = new HashMap<>(); properties.put("yarnEnabled", true); properties.put("yarnVersion", "0.56.3"); @@ -58,7 +59,7 @@ public void shouldFailInstallingYarnWhenDistributionCannotBeDownloadedWithUnknow } @Test - public void shouldFailInstallingYarnWhenDistributionCannotBeDownloadedWithInvalidUrl() throws IOException { + void shouldFailInstallingYarnWhenDistributionCannotBeDownloadedWithInvalidUrl() throws IOException { final Map properties = new HashMap<>(); properties.put("yarnEnabled", true); properties.put("yarnVersion", "1.15.2"); @@ -72,7 +73,7 @@ public void shouldFailInstallingYarnWhenDistributionCannotBeDownloadedWithInvali } @Test - public void shouldInstallYarnFirstAndNothingMoreSecondly() throws IOException { + void shouldInstallYarnFirstAndNothingMoreSecondly() throws IOException { final Map properties = new HashMap<>(); properties.put("yarnEnabled", true); properties.put("yarnVersion", "1.15.2"); diff --git a/src/test/resources/SHASUMS256.txt b/src/test/resources/SHASUMS256.txt index 3c8a9824..99f34053 100644 --- a/src/test/resources/SHASUMS256.txt +++ b/src/test/resources/SHASUMS256.txt @@ -1,2 +1,2 @@ 8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85 node-v10.15.1.txt -c416e40cbdcad0639002f54d1866857c33ff0da890602d22f2455953be43288a node-v10.15.3.zip +02b2013b65c7bff02ae7bc2158e70f71e9b4e53b88f747a6e9582e3d1399065a node-v10.15.3.zip diff --git a/src/test/resources/node-v10.15.3.zip b/src/test/resources/node-v10.15.3.zip index 62087ec9..6b92c1bb 100644 Binary files a/src/test/resources/node-v10.15.3.zip and b/src/test/resources/node-v10.15.3.zip differ diff --git a/src/test/resources/package-npm.json b/src/test/resources/package-npm.json index 84ff9a6a..062bf892 100644 --- a/src/test/resources/package-npm.json +++ b/src/test/resources/package-npm.json @@ -10,7 +10,7 @@ "dependencies": { }, "scripts": { - "another-script": "echo 'Another frontend script executed'", + "another-script": "node -v && npm -v && echo 'Another frontend script executed' && npm run check", "assemble": "echo 'Frontend assembled'", "check": "npm run lint && npm run test", "clean": "echo 'Frontend cleaned'", diff --git a/src/test/resources/package-yarn.json b/src/test/resources/package-yarn.json index d9d5455a..3af7131a 100644 --- a/src/test/resources/package-yarn.json +++ b/src/test/resources/package-yarn.json @@ -10,7 +10,7 @@ "dependencies": { }, "scripts": { - "another-script": "echo 'Another frontend script executed'", + "another-script": "node -v && yarn -v && echo 'Another frontend script executed' && yarn run check", "assemble": "echo 'Frontend assembled'", "check": "yarn run lint && yarn run test", "clean": "echo 'Frontend cleaned'", diff --git a/src/test/resources/yarn-v1.15.2.tar.gz b/src/test/resources/yarn-v1.15.2.tar.gz index 218d3044..ee84ba45 100644 Binary files a/src/test/resources/yarn-v1.15.2.tar.gz and b/src/test/resources/yarn-v1.15.2.tar.gz differ diff --git a/task-dependencies.png b/task-dependencies.png deleted file mode 100644 index da9a9841..00000000 Binary files a/task-dependencies.png and /dev/null differ diff --git a/task-dependencies.graphml b/task-tree.graphml similarity index 97% rename from task-dependencies.graphml rename to task-tree.graphml index f2b8a046..a86cf884 100644 --- a/task-dependencies.graphml +++ b/task-tree.graphml @@ -17,7 +17,7 @@ - + Gradle Base plugin @@ -39,10 +39,10 @@ - + - installNode + installYarn @@ -50,7 +50,7 @@ - + assembleFrontend @@ -61,7 +61,7 @@ - + checkFrontend @@ -72,7 +72,7 @@ - + cleanFrontend @@ -83,7 +83,7 @@ - + clean @@ -94,7 +94,7 @@ - + check @@ -105,7 +105,7 @@ - + assemble @@ -116,7 +116,7 @@ - + runScriptFrontend @@ -127,10 +127,10 @@ - + - installYarn + installNode @@ -138,7 +138,7 @@ - + installFrontend diff --git a/task-tree.png b/task-tree.png new file mode 100644 index 00000000..44aa375e Binary files /dev/null and b/task-tree.png differ