Yarn (or npm) swap nested dependency with a fork - npm

In my package.json I have a package:
{
"somepackage": "^6.5.1"
}
This package has a dependency (from somepackage's package.json):
{
"someotherpackage": "^9.4.2"
}
I want to swap someotherpackage to one of its fork.
I know of yarn's Selective dependency resolution feature, but I guess that is only for different versions of the same package.
I could as well fork somepackage and change its dependencies manually, swapping someotherpackage to someotherpackage-fork, but if there is another way I would take that.
Thanks!

The mentioned selective dependency resolution feature is the way to go. Under the hood it seems to be resolving packages the same way dependencies does which allows us to do the following:
"resolutions": {
"**/someotherpackage": "npm:someotherpackage-fork#*"
}
You can also specify which version of your fork you want:
"resolutions": {
"**/someotherpackage": "npm:someotherpackage-fork#3.0.2"
}
I haven't confirmed if this works for Yarn version 2 yet.
Showcase: https://github.com/dimitarnestorov/yarn-resolutions-showcase

Related

Monorepo (lerna) - circular dependency

is it possible for a monorepo to have two packages which depend on one another?
E.g.
package_Dashboard
import { table } from 'package_Style'
package_Style
import { style } from 'package_Dashboard'
During development, all seems to be working more or less fine thanks to aliases/references which replace
package_Dashboard with ../package_Dashboard'.
I have to not include the dependency in one of the package.json files, because otherwise lerna warns me about a circular dependency.
When I'd publish this on npm or similar, this wouldn't work anymore (due to the missing dependency in package.json).
So how does one best solve for a case like this?
Thanks!

Why does updating Gradle break log4j imports?

I am attempting to update to Kotlin 1.4. In my build.gradle file, I have the following:
buildscript {
allprojects {
ext {
kotlin_version = "1.3.70"
ktor_version = "1.2.2"
junit_version = "5.4.2"
log4j_version = "2.11.2"
jackson_version = "2.9.9"
kafka_version = "2.3.0"
}
}
repositories {
maven {
url 'https://smartward.jfrog.io/smartward/gradle-dev'
credentials {
username = "${artifactory_user}"
password = "${artifactory_password}"
}
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.9.7"
}
}
and later on:
implementation(
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version",
"org.jetbrains.kotlin:kotlin-reflect:$kotlin_version",
"org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version",
"org.apache.logging.log4j:log4j-api:$log4j_version",
// For JSON mapping
"com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version",
"com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version",
"com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jackson_version",
"com.natpryce:konfig:1.6.10.0",
"org.apache.kafka:kafka-clients:$kafka_version",
"io.ktor:ktor-server-netty:$ktor_version",
"io.ktor:ktor-locations:$ktor_version",
"io.ktor:ktor-jackson:$ktor_version",
"io.ktor:ktor-client-core:$ktor_version",
"io.ktor:ktor-client-apache:$ktor_version",
"io.ktor:ktor-client-json:$ktor_version"
)
My first step was to change kotlin_version to be "1.4.0". When running the build script, I was informed that Gradle needed to be updated as well. I did this, changing my gradle-wrapper.properties file (diff below):
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-all.zip
This now means that some of my log4j imports no longer work. Namely:
import org.apache.logging.log4j.core.Logger
import org.apache.logging.log4j.core.config.Configurator
I have attempted reverting to Kotlin 1.3.70, without reverting the Gradle update, and the issue persists, so I suspect a problem with Gradle, or my build script, but I'm not sure why or how to fix it. I have also attempted using Gradle 6.6, with the 4.17.1 version of org.jfrog.buildinfo:build-info-extractor-gradle, but the problem persists.
Use dependencyInsight to see what's going wrong
It sounds like what's happening is that the version of log4j that ends up being used isn't the version you were expecting.
Dependency version resolution can get pretty complicated, especially when you have lots of dependencies. Different things want different versions of the same dependency, but Gradle has to pick one version that will end up on the classpath. In general, it will pick the newest version from among all the versions that have been requested.
There are two reasons I can think of that upgrading Gradle might have changed the version of log4j that ends up being used:
Something in Gradle itself could be adding a dependency on log4j, and might now be requesting a newer version than was used in the older Gradle distribution.
On the other hand, it's possible that the way version conflicts are resolved has actually subtly changed in the newer version of Gradle.
Luckily, Gradle gives you some tools to help figure out what's going on. I would suggest comparing the output of the following command both before and after updating the Gradle version.
gradle dependencyInsight --dependency log4j
This will print out a tree-like report of everything that's using log4j, and will tell you why a particular version was selected. It might take some time to understand the report, especially if it's long, but it's worth reading through it carefully.
Use platform constraints to force the correct version
Projects like log4j are made up of several artifacts (log4j-api, log4j-core, etc). The process of resolving the various transitive dependency versions in your build can end up introducing versions that don't match each other. It's important to make sure that all the artifacts have matching versions.
To help solve this, log4j provides an additional 'bill of materials' artifact, log4j-bom. BOM artifacts don't contain any code, but they specify a list of dependencies, along with the versions that should be used.
Since version 5, Gradle lets you use BOM files to suggest or enforce versions for a set of dependencies. Applying a 'platform' dependency of this sort doesn't add or remove any actual dependencies to your build, but it does influence or control the versions of the dependencies you already have.
In your case, you could add the following to your dependencies:
dependencies {
implementation enforcedPlatform("org.apache.logging.log4j:log4j-bom:$log4j_version")
}
This adds the log4j-bom as an enforcedPlatform dependency, guaranteeing that every log4j dependency used in your application will always have the version you specify. This is a powerful tool and should help make sure you don't run into problems like this in future.
As per the official documentation of Log4j you need to link to both log4j-api and log4j-core to consume the package properly.

How to determine the dependencies of older npm package versions

It appears to me that the dependency linkage on the npm site is only applicable to the current/latest version.
Is there some tidbit of information that I'm not aware of on how to determine what dependency version a package has other than by downloading it and inspecting the package.json file?
I feel like I'm wasting HOURS doing something I would expect to be much easier to do.
"It appears to me that the dependency linkage on the npm site is only applicable to the current/latest version."
Yes that's correct, www.npmjs.com will only show the dependencies for the latest version of a package.
Here are a couple of ways to discover what you want both programmatically and non-programmatically.
Programmatically:
Utilizing the npm view command with the following syntax;
npm view <pkg_name> versions --json
obtains a list of all versions available for a given package in the npm registry.
Note: The <pkg_name> part above should be substituted with the real package name.
For instance; running the following command for let's say the eslint package:
npm view eslint versions --json
prints the following to the console:
[
"0.0.4",
"0.0.5",
"0.0.6",
"0.0.7",
"0.1.0-dev",
"0.1.0",
"0.1.1",
"0.1.2",
...
]
Now we know what versions are available, let's say we want to list the dependencies for eslint version 0.1.2 we can run the following command:
npm show eslint#0.1.2 dependencies --json
This will print:
{
"optimist": "*",
"estraverse": "~1.3.0",
"esprima": "*",
"escope": "1.0.0"
}
Similarly, we can discover the devDependencies for eslint version 0.1.2 by running the following command instead:
npm show eslint#0.1.2 devDependencies --json
This will yield something like this:
{
"vows": "~0.7.0",
"sinon": "*",
"commonjs-everywhere": "~0.9.0",
"mocha": "~1.13.0",
"chai": "~1.8.1",
"grunt": "~0.4.1",
...
}
If you know that a package has a particular dependency in advance.
For instance; retrospectively after running the aforementioned command we now know that eslint version 0.1.2 has escope listed as a dependency.
So, if we wanted to know the version of escope that eslint version 0.1.2 needs, we can run the following command:
npm show eslint#0.1.2 dependencies.escope
^
Note: The package name follows the dot (.), i.e. .escope
This prints the following:
1.0.0
The non-programmatic way
I can't think of a reason why you would want to perform the following non-programmatic way instead of the aforementioned programmatic way when you have a CLI tool available to you. However, if you prefer manual tasks then here goes...
Note: YMMV using the following manual steps as it depends on how the package has been managed/maintained.
Typically, the source code of an npm package will be hosted on GitHub, so you can perform the following manual steps. This will avoid you having to download the package to inspect the package.json file.
For this we'll demonstrate for the eslint package:
Visit npmjs.com and type the name of the package in the "Search Packages" input field. We'll type eslint and hit the return key.
Next click eslint from the list of packages, which will take you to this page.
Click on the github link which typically appears on the right-hand side of the webpage and looks like this:
That will take you to the eslint repo, i.e. this one
On the Github page click the "Branch" button - which appears above the list of source code files, and looks like this:
In the pop-up panel that subsequently appears click the "Tags" button, then locate and click the version tag from the list that you want to discover it's dependencies. (Note: These tag names will typically correspond to the version released/published to npm)
This will then load the source code files in the browser for that particular release/version.
Locate the package.json file from the list of files and click it. This will load the contents of package.json in the browser, upon which you can read it and ascertain its dependencies.
Visualizing the dependency tree
I sometimes utilize this online tool https://npm.anvaka.com which can help you to visualize the complete dependency tree/graph for a given package - however it's for the latest version of a package only.
Here is the complete dependency tree/graph (many levels deep) for the latest version of eslint.

Enforcing shared dependencies in a monorepo

We have a monorepo using lerna and yarn workspaces. Multiple teams contribute packages to it and there are some common dependencies where we want to force people to use the same version.
What are the options to force all packages to use the same version of specific dependencies? Is there a way to achieve that without writing custom scripts?
I want to prevent this situation:
my-repo/
packages/
pkg-A/
package.json
"address-validator": 1.1.0
pkg-B/
package.json
"address-validator": 1.2.0
I know you can use lerna add or lerna run to add / upgrade in unison, but how to prevent an individual from unknowingly making their package unique?
I just noticed one nice solution to this problem in facebook's create-react-app. They import (all?) external dependencies in the react-dev-utils package and export them from there. Then all the other packages, like react-scripts, import dependencies from react-dev-utils.
This is nice because you only need to worry about using the latest version of one package (e.g. react-dev-utils) in order to use the latest version of all of the things you want to control. Also, it's flexible because you can override one of the dependencies by importing a different version directly.
So it could look like:
my-repo/
packages/
my-deps/
pkg1.js // <--- module.exports = require("pkg1");
package.json
"pkg1": 1.2.0
foo/
index.js // <--- const pkg1 = require("my-deps/pkg1")
package.json
"my-deps": 1.1.0

Why is dependency in package.json prefixed with #polymer?

When looking at this package.json I see two versions for sinonjs:
"dependencies": {
"#polymer/sinonjs": "^1.14.1",
...
"sinon": "^2.3.5",
...
},
What is the difference between sinon and #polymer/sinonjs?
Node packages that start with #namespace are scoped packages. Typically this means an organization that wants a standardized naming convention for all of their packages that might have common names already taken in the global namespace.
In your example the organization is Polymer who has their own published version of sinon. As to why Polymer has their own published package of Sinon you'd have to ask them. The description suggests it's a workaround to access the Bower version of Sinon. That workaround probably wont be needed once Polymer makes the jump to NPM.
SinonJS proxy repository for the BowerJS package manager