Install node_modules to vendor - node.js

How can I install npm modules locally for each project to vendor/node_modules and make package.json file see them.
I don't want to move package.json to vendor folder
I have bower and in .bowerrc I specify the bower_components path - that is super easy.
How can I do that with npm ?
I`ve read the docs, npmrc docs, some questions here, googled, wasted more than an hour - still no luck. This is ridiculously hard for such an easy task.
I don't care about minuses, just tell me how to do that finally.

Frustrated by the fact that there seems to be no built in way to install into a node_modules folder in an arbitrary subfolder, I came up with a sneaky solution using the two following scripts:
preinstall.js
var fs = require("fs");
try
{
fs.mkdirSync("./app/node_modules/");
}
catch(e)
{
}
try
{
if(process.platform.indexOf("win32") !== -1)
{
fs.symlinkSync("./app/node_modules/","./node_modules/","junction");
}
else
{
fs.symlinkSync("./app/node_modules/","./node_modules","dir");
}
}
catch(e){}
postinstall.js
var fs = require("fs");
try
{
if(process.platform.indexOf("win32") !== -1)
{
fs.unlinkSync("./node_modules/");
}
else
{
fs.unlinkSync("./node_modules");
}
}
catch(e){}
All you need to do is use them in your package.json file by adding them to the scripts option:
"scripts": {
"preinstall": "node preinstall.js",
"postinstall": "node postinstall.js"
},
So, the big question is: what does it do?
Well, when you call npm install the preinstall.js script fires which creates a node_modules in the subfolder you want. Then it creates a symlink or (shortcut in Windows) from the node_modules that npm expects to the real node_modules.
Then npm installs all the dependencies.
Finally, once all the dependencies are installed, the postinstall.js script fires which removes the symlink!
Here's a handy gist with all that you need.

You cannot, not using built-in npm functionality.
This discussion on the npm github repository covers the issue. It's also being addressed in this answer which is part of their FAQ.
You can still do the installs "manually" by copying modules into your /vendor directory and then calling them using the require("./vendor/whatever") syntax...but that means each require needs to use your new custom location. There are a few ways you could handle this but they all mean you are doing extra work in your source to accomodate the custom location.

Related

How to disable local package dependency for `npm install --prefix`?

I have a project with at its root the following simplified package.json file:
package.json
{
"name": "parent-project",
"dependencies": {
...
}
}
In a subfolder that we'll call child-project, another package.json resides:
child-project / package.json
{
"name": "child-project",
"dependencies": {
...
}
}
Some code that I depend on uses the command npm --prefix ./child-project install to install dependencies in child-project. However, this always has the undesirable side-effect of altering child-project/package.json like so:
{
"name": "child-project",
"dependencies": {
...
"parent-project": "file:.." // <- I don't want this!
}
}
When I execute cd ./child-project && npm install all is fine and child-project/package.json remains untouched, so my hunch is that it has to do with --prefix but documentation on --prefix is very obscure.
Is there a way to disable this behaviour and prevent NPM from altering the child-project/package.json?
Seems like there are some issues with the --prefix.
However, I couldn't find any issue while I was testing with different modules which have sub-packages.
Hence, according to one of the npm contributors, it is going to be refactored or removed in a future release, which means it is not the best choice to use it at least for now. So I recommend you go through the traditional way and avoid using --prefix.
I think we're gonna close this as a wontfix as --prefix is not the best flag to be promoting. We do eventually want to refactor or remove it but for now we should at least not be promoting it.

Node global module getting current run directory

I'm trying to create a node module that has the ability to be installed globally using npm install -g mymodulename. I've got everything in the module working fine if I run node index.js in the directory of the module, but now I want to make it so that I can publish it to NPM, and it can be installed and run from any directory.
There is some code in my module that looks at certain files in the directory that it is run in. I'm finding that when I do npm install -g ./ and then go into a different directory for a test, then run my-module-command, the relative path that it is reading is from that of where my module got installed (i.e. /usr/local/bin/my-module), not the directory that I'm running it in.
How can my module that is installed globally know where it is being run from? To give an example, I am trying to read the package.json file in the directory I'm in. And it is reading the package.json file of /usr/local/bin/my-module/package.json
I've tried:
__dirname
process.args[1]
process.cwd()
And just calling straight to require('./package.json') directly and none of those work.
Edit here's some code that's breaking in index.js:
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var currentDir = path.dirname(require.main.filename);
fs.exists(`${currentDir}/node_modules`, function(dir) {
if (!dir) throw 'node_modules does not exist';
// do stuff
});
In my package.json:
...
"bin": {
"my-module": "./index.js"
},
...
I try to do npm install -g ./ in the project directory, and then I cd into a different directory called /Users/me/Projects/different-project, where another npm project is, and run my-module, and I get node_modules does not exist. When I log out currentDir, I get /usr/local/lib/node_modules/my-module where I'm expecting is to see /Users/me/Projects/different-project.
Have you tried using ./ at the start of your file path? That should give you the current working directory (or calling process.cwd() would work too).
In your case, your code would look like:
fs.exists(`./node_modules`, function(dir) {
if (!dir) throw 'node_modules does not exist';
// do stuff
});
I can see some comments already mention this. Maybe I'm misunderstanding the question? I just had a case where I needed a global module to get the directory I'm running the script from and what I suggested above worked like a charm.

NPM - package.json#engines | How to specify Python?

I need to specify a python version on my package.json.
Can I simple do:
{ engines: { "python": "2.7.11" } } ?
Putting "engines": { "python": "2.7.11" } won't cause any issues (as far as I am aware) in your package.json, but it won't actually do anything either.
Determining an appropriate way to do this depends on particulars of your project. If it's about Python code you've written, you can check the version within the Python script itself. If it's about a build step in package.json, you can perhaps test as part of the build step.
As of NPM 7.x (same is valid for legacy NPM 6.x), the only valid entries for "engines" field in package.json, are the "node" version, and the "npm" version.
Furthermore, this is not a hard constraint unless you use "engine-strict" as well, as stated by the NPM docs:
Unless the user has set the engine-strict config flag, this field is advisory only and will only produce warnings when your package is installed as a dependency.
Your requirement, expecting a specific python version, is more related to an environment requirement than to your Node/NPM environment.
You can achieve this by implementing a "postinstall" NPM script that can result an error if the desired version is not found:
{
"scripts": {
"postinstall": "node ./check-python.js"
}
}
This script will be executed by NPM automatically after npm install. You could also use "preinstall" instead.
Consider using it on your "build" or "prebuild" scripts as well, based on your requirements. See more details on NPM pre- and post- scripts in the docs.
Then, your check-python.js script could be something like:
const { exec } = require('child_process');
const EXPECTED_PYTHON_VERSION = "2.7.11";
exec('python -c "import platform; print(platform.python_version())"',
function(err, stdout, stderr) {
const currentPythonVersion = stdout.toString();
if(currentPythonVersion !== EXPECTED_PYTHON_VERSION) {
throw new Error(`Expected Python version '${EXPECTED_PYTHON_VERSION}' but found '${currentPythonVersion}'. Please fix your Python installation.`);
}
});

npm install does not seem to get all dependencies

My package.json looks like this (name/description/etc. omitted).
{
"dependencies": {
"express": "3.3.4",
"jade": "0.34.x",
"mongoose": "3.6.x"
},
"devDependencies": {
"vows": "0.7.x"
}
}
I used express on the repository and ran the auto-generated node app.js. This worked, but when I used curl http://localhost:port I got the error "Cannot find module character-parser." I ran npm install character-parser and then I got "Cannot find module transformers." This happened a few more times, but after I installed all of them the app started working.
I thought that npm install was supposed to install dependencies recursively. This also worries me because I obviously want the package.json to be usable when the app is deployed.
Here's a simple script to collect the dependencies in ./node_modules:
var fs = require("fs");
function main() {
fs.readdir("./node_modules", function (err, dirs) {
if (err) {
console.log(err);
return;
}
dirs.forEach(function(dir){
if (dir.indexOf(".") !== 0) {
var packageJsonFile = "./node_modules/" + dir + "/package.json";
if (fs.existsSync(packageJsonFile)) {
fs.readFile(packageJsonFile, function (err, data) {
if (err) {
console.log(err);
}
else {
var json = JSON.parse(data);
console.log('"'+json.name+'": "' + json.version + '",');
}
});
}
}
});
});
}
For one project I'm working on, the output looks like this:
"progress": "0.1.0",
"request": "2.11.4",
If you remember to remove the comma from the last entry, you can copy and paste the output.
I got this exact error while I was npm installing for https://github.com/HenrikJoreteg/humanjs-sample-app/
I'm on a Windows machine, so I suspected that it was an issue with the odd restrictions that Windows has on the number of characters in a file path.
I resolved this by shorting my base path to a three character folder name in my root (in this case going from C:\projects\humanjs-sample-app to C:\hjs). When I re-ran npm install everything worked. I'm not happy with that resolution. I shouldn't have to worry about my base path name being too long. This is one of the reasons that people often don't do node development on Windows machines.
An alternate resolution is to develop on Linux or Mac, if you aren't already. That's probably my long term strategy.
When you run npm install <name-of-package> it will install the package to your node_modules folder, but will not add it as a dependency. In order to install the package and save it as a dependency in your package.json, you must use the --save flag like so:
npm install <name-of-package> --save
The npm documentation provides further information on additional flags that can be used such as the --save-dev flag for saving development dependencies and the --save-optional flag for saving optional dependencies to your package.json.

Is there a way to generate the bundledDependencies list automatically?

I have an app that I'm deploying to Nodejitsu. Recently, they suffered from npm issues that caused my app to go offline for several hours after I tried (and failed) to restart it, as its dependencies could not be installed. I was told that this could be averted in the future by listing all of my dependencies as bundledDependencies in my package.json, causing the dependencies to be uploaded along with the rest of the application. Which means that I need my package.json to look something like this:
"dependencies": {
"express": "2.5.8",
"mongoose": "2.5.9",
"stylus": "0.24.0"
},
"bundledDependencies": [
"express",
"mongoose",
"stylus"
]
Now, on DRY grounds, this is unappealing. But what's worse is the maintenance: Every time I add or remove a dependency, I have to make the change in two places. Is there a command I can use to sync bundledDependencies with dependencies?
How about implementing a grunt.js task to do it? This works:
module.exports = function(grunt) {
grunt.registerTask('bundle', 'A task that bundles all dependencies.', function () {
// "package" is a reserved word so it's abbreviated to "pkg"
var pkg = grunt.file.readJSON('./package.json');
// set the bundled dependencies to the keys of the dependencies property
pkg.bundledDependencies = Object.keys(pkg.dependencies);
// write back to package.json and indent with two spaces
grunt.file.write('./package.json', JSON.stringify(pkg, undefined, ' '));
});
};
Put that in the root directory of your project in a file called grunt.js. To install grunt, use npm: npm install -g grunt. Then bundle the packages by executing grunt bundle.
A commentor mentioned an npm module that could be useful: https://www.npmjs.com/package/grunt-bundled-dependencies (I haven't tried it.)
You can use a simple Node.js script to read and update the bundleDependencies property and run it via npm lifecycle hooks/scripts.
My folder structure is:
scripts/update-bundle-dependencies.js
package.json
scripts/update-bundle-dependencies.js:
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const pkgPath = path.resolve(__dirname, "../package.json");
const pkg = require(pkgPath);
pkg.bundleDependencies = Object.keys(pkg.dependencies);
const newPkgContents = JSON.stringify(pkg, null, 2);
fs.writeFileSync(pkgPath, newPkgContents);
console.log("Updated bundleDependencies");
If you are using the latest version of npm (> 4.0.0), you can use the prepublishOnly or prepack script: https://docs.npmjs.com/misc/scripts
prepublishOnly: Run BEFORE the package is prepared and packed, ONLY on
npm publish. (See below.)
prepack: run BEFORE a tarball is packed (on npm pack, npm publish, and
when installing git dependencies)
package.json:
{
"scripts": {
"prepack": "npm run update-bundle-dependencies",
"update-bundle-dependencies": "node scripts/update-bundle-dependencies"
}
}
You can test it yourself by running npm run update-bundle-dependencies.
Hope this helps!

Resources