NPM 7 workspaces - how to install new package in workspace? - node.js

If I have a NPM 7 workspace like this:
root
- submodule0
- submodule1
- submodule2
and I navigate to the submodule0 directory and run npm i somepackage it seems to "break" the workspace by creating a new package-lock.json in the submodule0 directory and installing all the dependencies there. In other words, it just does the old behavior that existed before I created the workspace.
I was hoping for a command similar to lerna where I can install a new package in submodule0 from the root. Something like:
npm i somepackage --scope submodule0
So far, the only workaround I can find is to edit the submodule0 package.json and add the somepackage manually. Then run npm i from the root. Obviously this is not ideal because I need to look up the #latest version, navigate to the subdirectory, open the package.json, etc. etc. as opposed to just typing one line in the root.

Workspace support for npm install and npm uninstall was added in npm v7.14.0. You can now just do:
npm i somepackage --workspace=submodule0
Uninstalling modules has been the biggest pain, so this is really exciting. The npm team seems to be slowly adding support to commands one by one. Follow updates here: https://github.com/npm/cli/blob/latest/CHANGELOG.md.

I'm also baffled with why npm workspaces has been released without this functionality.
My current workaround uses the add-dependencies package, which adds dependencies to a declared package.json file, whilst skipping the installation process.
npm i add-dependencies -g
Then, from top level of the monorepo, you can run:
npx add-dependencies ./submodule0/package.json somepackage && npm i
Hopefully a --workspace argument will be added to npm i soon to avoid this faff.

Please refer to the answer of mattwad above if you have NPM v7.14.0 or above
Original answer
I wasn't quite happy with the suggestions, but combined all of them to use it in a npm script without any dependencies:
{
"add": "npm install --package-lock-only --no-package-lock --prefix",
"postadd": "npm install"
}
This can be used like following: npm run add -- submodule0 somepackage

Add only into package.json
U can use this to install package only into package.json ( you don't need external dependencies )
npm i --prefix packages/test --save --package-lock-only --no-package-lock express
followed by npm i to install specified dependency into mono repository root node_modules
Lerna
Also can use lerna to use workspace name to install dependency into
package.json
{
"name": "mono",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"wsi": "function workspaceinstall() { ( scope=$1; shift; lerna exec --scope \"$scope\" -- npm install --package-lock-only --no-package-lock \"$#\") }; workspaceinstall"
},
"author": "",
"license": "ISC",
"workspaces": {
"packages": [
"packages/**"
]
}
}
lerna.json
{
"version": "1.0.0",
"npmClient": "npm",
"packages": ["packages/**"]
}
npm run wsi [workspace name] [dependency name to install]
npm run wsi #workspace/test express
npm run wsi #workspace/test express --save-prod
npm run wsi #workspace/test #types/express --save-dev
wsi script only modify package.json for provided workspace name, to actually install dependencies u have to run npm i

In my case, which is similar to yours, I deleted all dependencies from all the inner projects, deleted also the package-lock.json, and installed everything in the root.
/
node_modules
package.json >> all dependencies
package-lock.json >> the only lock file that exists in the repo
/packages
/A
package.json >> no dependencies
-- no package-lock.json
/B
package.json >> no dependencies
-- no package-lock.json
/C
package.json >> no dependencies
-- no package-lock.json
This way, the node_modules folder ONLY resides on the root, and also the package-lock.json file is in the root.
If I allowed to have each project it's own package-lock.json I started seeing installation and runtime errors (because each project could have its own node_modules and its own version of a dependency).
This is the best way I see it works.

After trying to use the npm install with the --prefix --save --package-lock-only --no-package-lock options, npm always give the the error E404 - Not Found for my own packages of the monorepo that are not yet published to a registry. So even when trying to install external packages it fails because of my current dependencies in the package.json.
To workaround this issue I ended up with a mix of the previous suggestions:
"scripts": {
"add": "add-dependencies $npm_config_scope/package.json",
"postadd": "npm i",
},
"devDependencies": {
"add-dependencies": "^1.1.0"
},
Then I can do:
npm run add --scope=packages/app express
npm run add --scope=packages/core eslint jest -D
This works fine for installing external packages. To install my own packages that lives inside the monorepo, I still have to manually edit the package.json, otherwise I get the package not found error.

Related

Does npm-force-resolutions break the sync between package.json and package-lock.json?

This is my package.json.
"resolutions": {
"react-error-overlay": "6.0.9"
},
"scripts": {
"preinstall": "npx npm-force-resolutions",
...
}
After deleting node_modules package-lock.json, I tried npm install.
When npm install finished, I deleted node_modules again and tried npm ci.
Then, this error messages showed up.
'npm ci' can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync.
Please update your lock file with 'npm install' before continuing.
I erased resolutions and scripts.preinstall in package.json and tried it again exactly same.
Then, error messages didn't show up.
So, I'm guessing npm-force-resolutions breaks the sync between package.json and package-lock.json.
Am I right?
same thing happened to me after moving the node js from v12 to v16,what is the npm version you are using? if it's above 8.3 , you can use overrides https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides

npm install everything except named package

An extract from my Dockerfile which builds my Angular app:
# Do other stuff
RUN npm install
# next line executes ng build --prod
RUN npm run build:prod
The npm install is for build purposes. Some of the packages in devDependencies - particularly cypress - take ages to install and are not needed for the build. However, some packages in devDependencies are needed.
Can I, for example, do an npm install everything except cypress?
npm install --only=prod is not an option because some devDependencies are needed.
Thanks in advance for your thoughts.
Just a suggestion. Use install-subset, and can be installed globally with npm install -g install-subset
To use it, you build inclusion lists and exclusion lists for named installation subsets in your package.json like this:
"subsets": {
"build": {
"include": [
"babel-cli",
"dotenv"
]
},
"test": {
"exclude": [
"eslint",
"lint-rules",
"prettier"
]
}
}
Then run install-subset test
This will temporarily rewrite your package.json to not install those packages excluded, then restore it (very similar to how lerna operates), which depending on the packages can save a lot of time and bandwidth.

Prepublish not working as expected

I am testing on npm scripts to build my project dependency.
My idea comes from https://github.com/ParsePlatform/parse-server which impressed me by code in repository doesn't mean code in node_modules after npm install.
Below is my testmodule structure
src/index.js
package.json
and this is my package.json content
{
"name": "testmodule",
"version": "1.0.0",
"description": "",
"main": "lib/index.js",
"scripts": {
"build": "babel src/ -d lib/",
"prepublish": "npm run build"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.18.2"
}
}
and this is structure I expect after run npm install testmodule
node_modules/testmodule/lib/index.js
node_modules/testmodule/package.json
which is src folder should not be here.
But after I run npm install, it is exactly the same as when I push to my git repository.
Please take note that I am using GitLab in my own server.
So my questions are:
Is there anything that i'm missing to make prepublish run?
Which part of parse-server code makes the src folder and other files not there after install?
How are you running npm install?
According to the documentation on npm scripts, the prepublish script is run "BEFORE the package is published. (Also run on local npm install without any arguments.)". It seems clear that the prepublish script is only run on npm publish or npm install <local directory>.
If you are trying to install directly from your local gitlab server via a URL, this will not work - the script will not be run. The solution would be to install locally unless you're willing to open source your package & push it to the npm repository or pay for a private npm repository. This is what I have done during development of packages before they're ready to be made public.

NPM: After "npm link" module is not found

I'm developing two modules for NodeJS, first one named aligator and second one aligator-methods. Second one depends on first one to work. I'm developing these two modules at the same time and I want to global link aligator so I can use it like it is on npm registry and I just installed it globally. To do this NPM documentation says that I need to use npm link but it's not working.
File package.json of module aligator:
{
"name": "aligator",
"version": "0.0.1",
"description": "",
"main": "index.js",
"private": true,
"directories": {
"doc": "docs",
"example": "examples",
"test": "spec"
},
"scripts": {
"test": "gulp jasmine"
},
"license": "MIT",
"devDependencies": {
"gulp": "^3.6.2",
"gulp-jasmine": "^0.2.0",
"gulp-jshint": "^1.6.1",
"gulp-rename": "^1.2.0",
"jasmine-node": "^1.14.3"
},
"dependencies": {
"bluebird": "^1.2.4",
"lodash": "^2.4.1",
"mathjs": "^0.22.0"
}
}
File package.json of module aligator-methods:
{
"name": "aligator-methods",
"version": "0.0.1",
"description": "",
"main": "index.js",
"private": true,
"directories": {
"doc": "docs",
"example": "examples",
"test": "jasmine"
},
"scripts": {
"test": "gulp jasmine"
},
"author": "",
"license": "MIT",
"devDependencies": {
"gulp": "^3.6.2",
"gulp-jasmine": "^0.2.0",
"gulp-jshint": "^1.6.1",
"gulp-rename": "^1.2.0",
"jasmine-node": "^1.14.3"
},
"dependencies": {
"lodash": "^2.4.1",
"mathjs": "^0.22.0",
"aligator": "^0.0.1"
}
}
First of all I linked the module globally:
$ cd ~/aligator
$ npm link
/usr/local/lib/node_modules/aligator -> /Users/roc/aligator
This if I'm not mistaken has created a global reference of my module aligator and now I can use this module from everywhere I want in the computer.
Then I went to the other module and tried to install the dependency but it gave me this output:
$ cd ~/aligator-methods
$ npm install
npm ERR! 404 404 Not Found: aligator
npm ERR! 404
npm ERR! 404 'aligator' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it
npm ERR! 404 It was specified as a dependency of 'aligator-methods'
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, or http url, or git url.
npm ERR! System Darwin 13.2.0
npm ERR! command "node" "/usr/local/bin/npm" "install"
npm ERR! cwd /Users/roc/aligator-methods
npm ERR! node -v v0.10.28
npm ERR! npm -v 1.4.16
npm ERR! code E404
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /Users/roc/aligator-methods/npm-debug.log
npm ERR! not ok code 0
I even tried to link it directly with:
$ cd ~/aligator-methods
$ npm link aligator
/Users/roc/aligator-methods/node_modules/aligator -> /usr/local/lib/node_modules/aligator -> /Users/roc/aligator
But it didn't work either.
Any thoughts on what it is that could be happening? I read somewhere that maybe it had something to do with my installation of node and npm because it was made by Homebrew and so sometimes I need to use sudo, it seemed unlikely but I tried what they proposed and It didn't work either.
The problem was that the main property of package.json was pointing to a non-existing file. It seems that the problem can happen due to multiple reasons so be sure to take a look at other answers.
I ran into this issue because of NVM, I was running one version of node for the dependency and another for the dependant.
Deleting package-lock.json then running npm install again resolved the issue for me.
When you first run npm link from the aligator directory, you create a link from your global node_modules directory to aligator. Then when you run the npm link aligator from the aligator-methods directory, you link aligator from your locally installed node_modules to the original source (as the output shows in your example above). Once this is done, there shouldn't be a need to install anymore since it's already "installed". What errors are you seeing after you run the npm link aligator command?
If you just want to install a dependency from a local directory, you might just try using npm install instead. For example:
$ cd ~/aligator-methods
$ npm install ../aligator
My issue ended up being that repo A was using npm and repo B was using yarn, so I needed to run yarn link in repo B in order to pull it in via npm link package-name into repo A.
What worked for me was to:
Delete the node_modules in both the dependency and the consumer module.
Run npm unlink --no-save [dependency-module]
re-link with the 2-link commands as per npm-link
Now I am able to fully test my unpublished module locally.
Additionally, there is an npm pack command which can help you test your unpublished modules, although not quite as robust.
npm-pack
Fix for my version of this issue; in npm v5.3.0, I removed node_modules from repo I was linking into another project.
I found out that after npm v3 they try to put all node_modules dependencies into one node_modules directory (one in your project) to flatten the structure as much as possible (http://codetunnel.io/npm-5-changes-to-npm-link/).
Be sure to check the main in package.json.
This serves as the entry of your package. This is a slight detail that took me a long time.
For me this happened when I decreased the version number of my local package from 0.1.0 to 0.0.1. And in the projects where I linked to this package I was still using the higher version number. Updating dependencies in package.json fixed it.
Check tsconfig moduleResolution
If like me, you happened to change the tsconfig module from es5 to esnext or something, then the moduleResolution default may have changed.
Without moduleResolution being set to "node", typescript will not resolve node_modules packages.
You can read on the Compiler Options page about how the default value depends on the value of module, whose default in turn depends on target — but probably set it to "node" explicitly.
I had a similar issue, and I had to perform the following steps to solve it:
In the library:
Setup the libraries that are generating issues as peerDependencies in the package.json instead of dependencies or devDependencies, e.g. in my case react:
"peerDependencies": {
"react": "^16.8.6",
...
}
run npm install
build the library (in my case, with a rollup -c npm script)
In my main app:
change the version of my library to point to my local project with a relative path in package.json, e.g.
"dependencies": {
"my-library": "file:../../libraries/my-library",
...
}
Add resolve.symlinks = false to my main app's webpack configuration
Add --preserve-symlinks-main and --preserve-symlinks to my package.json start script, e.g:
"scripts": {
"build": "set WEBPACK_CONFIG_FILE=all&& webpack",
"start": "set WEBPACK_CONFIG_FILE=all&& webpack && node --preserve-symlinks-main --preserve-symlinks dist/server.js",
}
run npm install
run npm run start
May be trivial, but worth mentioning:
npm link <module-name> must be executed after npm install (if needed) was executed in the <module-name> folder.
That is, unless the linked module is already present in <module-name> folder's package.json, in which case the order won't matter, because it means that the linked module is actually installed solely by npm install (as demonstrated here), and there's no need for linking using npm link - which is not the subject of this question.
The reason is that npm link <module-name> simply creates a symlink (or a folder shortcut, in Windows) to the linked package, so that executing npm install afterwards just deletes it.
To summarize, this is the order of execution:
Replacing the OP aligator with exporter and aligator-methods with importer for easier grasp
⚡ cd exporter
⚡ npm install <-- if needed, execute here, though it can also be executed after `npm link`
⚡ npm link
⚡ cd importer
⚡ npm install <-- if needed, must be executed here
⚡ npm link exporter
Bonus: A full minimal ES modules example of exporter and importer can be found here.
I know this is an old post, but in my case the issue was that I had renamed my package directory name, but the package.json "name" was still set to the old name.
for example, my directory name was package-name but the actual "name" found in package.json was package-name-b".
running yarn link would create a link called "package-name-b".
I then tried to run yarn link package-name since I was using the directory name. When I switched it to yarn link package-name-b, it worked.
I ran npm run build on the local package (dependency) and that worked for me
I was unable to import from my linked package because I simply forgot to prepend ./ to my module exports in my top level index.ts file:
export * from './utilities'
Took a while to figure that one out.
When using peerDependency
I'm developing two packages, stejs, and stejs-loader. stejs-loader has stejs as a peerDependency. When I ran npm link stejs-loader and npm link stejs in my project I was getting an error that stejs-loader couldn't find stejs. I got it fixed by running npm link stejs in the directory of stejs-loader.

Local dependency in package.json

I want to do something like this, so npm install also installs the package.json of ../somelocallib or more importantly its dependencies.
"dependencies": {
"express": "*",
"../somelocallib": "*"
}
npm >= 2.0.0
This feature was implemented in the version 2.0.0 of npm. Local paths can be saved using npm install -S or npm install --save, using any of these forms:
../foo/bar
~/foo/bar
./foo/bar
/foo/bar
Example package.json:
{
"name": "baz",
"dependencies": {
"bar": "file:../foo/bar"
}
}
npm ls:
app#0.0.1 /private/tmp/app
└── somelocallib#0.0.1 -> /private/tmp/somelocallib
npm < 2.0.0
Put somelocallib as dependency in your package.json as normal:
"dependencies": {
"somelocallib": "0.0.x"
}
Then run npm link ../somelocallib and npm will install the version you're working on as a symlink.
Reference: link(1)
It is now possible to specify local Node module installation paths in your package.json directly. From the docs:
Local Paths
As of version 2.0.0 you can provide a path to a local directory that contains a package. Local paths can be saved using npm install -S or npm install --save, using any of these forms:
../foo/bar
~/foo/bar
./foo/bar
/foo/bar
in which case they will be normalized to a relative path and added to your package.json. For example:
{
"name": "baz",
"dependencies": {
"bar": "file:../foo/bar"
}
}
This feature is helpful for local offline development and creating tests that require npm installing where you don't want to hit an external server, but should not be used when publishing packages to the public registry.
This works for me.
Place the following in your package.json file
"scripts": {
"preinstall": "npm install ../my-own-module/"
}
This is how you will add local dependencies:
npm install file:src/assets/js/FILE_NAME
Add it to package.json from NPM:
npm install --save file:src/assets/js/FILE_NAME
Directly add to package.json like this:
....
"angular2-autosize": "1.0.1",
"angular2-text-mask": "8.0.2",
"animate.css": "3.5.2",
"LIBRARY_NAME": "file:src/assets/js/FILE_NAME"
....
If you want to further automate this, because you are checking your module into version control, and don't want to rely upon devs remembering to npm link, you can add this to your package.json "scripts" section:
"scripts": {
"postinstall": "npm link ../somelocallib",
"postupdate": "npm link ../somelocallib"
}
This feels beyond hacky, but it seems to "work". Got the tip from this npm issue:
https://github.com/npm/npm/issues/1558#issuecomment-12444454
Master project
Here is the package.json you will use for the master project:
"dependencies": {
"express": "*",
"somelocallib": "file:./somelocallib"
}
There, ./somelocallib is the reference to the library folder as relative to the master project package.json.
Reference: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#local-paths
Sub project
Handle your library dependencies.
In addition to running npm install, you will need to run (cd node_modules/somelocallib && npm install).
This is a known bug with NPM.
Reference: https://github.com/npm/npm/issues/1341 (seeking a more up-to-date reference)
Notes for Docker
Check in your master package.lock and your somelocallib/package.lock into your source code manager.
Then in your Dockerfile use:
FROM node:10
WORKDIR /app
# ...
COPY ./package.json ./package-lock.json ./
COPY somelocallib somelocallib
RUN npm ci
RUN (cd node_modules/zkp-utils/ && npm ci)
# ...
I use parenthesis in my (cd A && B) constructs to make the operation idempotent.
Two steps for a complete local development:
Provide the path to the local directory that contains the package.
{
"name": "baz",
"dependencies": {
"bar": "file:../foo/bar"
}
}
Symlink the package folder
cd ~/projects/node-redis # go into the package directory
npm link # creates global link
cd ~/projects/node-bloggy # go into some other package directory.
npm link redis # link-install the package
Here in 2020, working on a Windows 10, I tried with
"dependencies": {
"some-local-lib": "file:../../folderY/some-local-lib"
...
}
Then doing a npm install. The result is that a shortcut to the folder is created in node-modules.
This doesn't work. You need a hard link - which windows support, but
you have to do something extra in windows to create a hard symlink.
Since I don't really want a hard link, I tried using an url instead:
"dependencies": {
"some-local-lib": "file:///D:\\folderX\\folderY\\some-local-lib.tar"
....
}
And this works nicely.
The tar (you have to tar the stuff in the library's build / dist folder) gets extracted to a real folder in node-modules, and you can import like everything else.
Obviously the tar part is a bit annoying, but since 'some-local-lib' is a library (which has to be build anyway), I prefer this solution to creating a hard link or installing a local npm.
I know that npm install ../somelocallib works.
However, I don't know whether or not the syntax you show in the question will work from package.json...
Unfortunately, doc seems to only mention URL as a dependency.
Try file:///.../...tar.gz, pointing to a zipped local lib... and tell us if it works.
With yarn it can be done as
yarn add file:../somelocallib
Curious.....at least on Windows (my npm is 3.something) I needed to do:
"dependencies": {
"body-parser": "^1.17.1",
"module1": "../module1",
"module2": "../module2",
When I did an npm install ../module1 --save it resulted in absolute paths and not relative per the documentation.
I messed around a little more and determined that ../xxx was sufficient.
Specifically, I have the local node modules checked out to say d:\build\module1, d:\build\module2 and my node project (application) in d:\build\nodeApp.
To 'install', I:
d:\build\module1> rmdir "./node_modules" /q /s && npm install
d:\build\module2> rmdir "./node_modules" /q /s && npm install
d:\build\nodeApp> rmdir "./node_modules" /q /s && npm install
module1's package.json has a dependency of "module2": "../module2"; module2 has no local dependency; nodeApp has dependencies "module1": "../module1" and "module2": "../module2".
Not sure if this only works for me since all 3 folders (module1, module2 and nodeApp) sit on that same level.......
This worked for me: first, make sure the npm directories have the right user
sudo chown -R myuser ~/.npm
sudo chown -R myuser /usr/local/lib/node_modules
Then your in your package.json link the directory
"scripts": {
"preinstall": "npm ln mylib ../../path/to/mylib"
},
"dependencies": {
"mylib" : "*"
}
Actually, as of npm 2.0, there is support now local paths (see here).
There is great yalc that helps to manage local packages. It helped me with local lib that I later deploy. Just pack project with .yalc directory (with or without /node_modules). So just do:
npm install -g yalc
in directory lib/$ yalc publish
in project:
project/$ yalc add lib
project/$ npm install
that's it.
When You want to update stuff:
lib/$ yalc push //this will updated all projects that use your "lib"
project/$ npm install
Pack and deploy with Docker
tar -czvf <compresedFile> <directories and files...>
tar -czvf app.tar .yalc/ build/ src/ package.json package-lock.json
Note: Remember to add .yalc directory.
inDocker:
FROM node:lts-alpine3.9
ADD app.tar /app
WORKDIR /app
RUN npm install
CMD [ "node", "src/index.js" ]
I wanted to use a set of local dependencies written in TypeScript, and none of the answers here worked for me. npm install would simply refuse to build the dependencies.
I had to resort to using tsconfig.json to add the packages to my project without marking them as dependencies. My usecase is further complicated by the fact that some dependencies depend on each other, and I wanted all of them to come from the local folder.
Here is my solution:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"#tiptap/*": [
"tiptap/packages/*/src"
]
}
}
}
In the example above, I have a local project subfolder named tiptap/ and there are many packages in tiptap/packages/*. The "paths" option will rewrite all #tiptap/foo imports into ./tiptap/packages/foo/src, across both my own files and the files in tiptap/.
It's not a good solution, but it is the only thing that worked for me.
use workspaces
the disadvantage using the file:../path/to/your-library is that you either have to npm install or using npm link in order to to the changes to take effect in the packages that import your package.
if you using pnpm: a better solution is using workspace: protocol: workspace:../path/to/your-library. it will symlink the directory to your node_modules rather than copying it, so any changes at the source immediately take effect.
for example:
...
"dependencies": {
...
"my-package": "workspace:../../dist"
},
note: this solution is intended to be used in a workspace, so you may need to create pnpm-workspace.yaml (even an empty one) file in the root of your project.
In 2021 you need to use it like:
npm i my-pkg#file:./path-to-my-pkg.js
# To remove it later
npm un my-pkg
Use .js in the end if its file OR path to folder if its complete package with package.json.
Usage
const myPkg = require('my-pkg')
That works like charm!
Complete local development guide for yarn users:
First add dependency to your main project:
cd main-project
yarn add file:../path/to/your-library
Next, if you want to avoid re-building this dependency every time you change it's source:
cd your-library
yarn link
This will register a link to your-library. Next, use the link you just created in your main project.
cd main-project
yarn link your-library
Now every time you change code in your-library, you don't need to rebuild it and it will automatically be included in your main-project. Yarn link works by creating symlinks in your node_modules folder, read more about it here: https://classic.yarnpkg.com/lang/en/docs/cli/link/
Using Module Alias
Install the module-alias package:
npm i --save module-alias
Add paths to your package.json like this:
{ "_moduleAliases": { "#lib": "app/lib", "#models": "app/models" } }
In your entry-point file, before any require() calls:
require('module-alias/register')
You can now require files like this:
const Article = require('#models/article');

Resources