Relative file paths in a global npm package - node.js

I have a command line utility that's installed as a global NPM package (privately, within our org).
I've noticed that file paths aren't actually relative to the installed location, but to the current working directory. In CommonJS I used __dirname but that's not valid with ESM.
So, when a user installs the package globally, and runs it, the following code errors because it's trying to use the current working directory.
fs.readFileSync('./templates/controller.ejs', 'utf8')
It works fine when I run the script from within the npm package folder itself. How I can always be sure it points to its install location?
EDIT: So far, the only way to do this is:
import { fileURLToPath } from 'url';
const dirname = fileURLToPath(new URL('.', import.meta.url));
const file = path.resolve(dirname, 'templates/controller.ejs')
fs.readFileSync(file, 'utf8')

Related

Using npm as custom plugin manager?

Think of sublime text, where you can install or uninstall plugins. I want that for my app, and I want to use npm/github to do it.
Maybe I'll require that your package starts with myapp- to be considered a plugin for my app. How can I search npm based on that, and also install/update packages into the folder I want (not node_modules) and ideally it should work even if the person doesn't have npm installed (using an http api?).
Plugins for my app go into plugins/plugin-name folder, all I need to do is download their git source into that folder
I have created a project to solve similar problems. See live-plugin-manager.
You can install, uninstall and load plugins from npm at runtime.
import {PluginManager} from "live-plugin-manager";
import * as path from "path";
const manager = new PluginManager({
pluginsPath: path.join(__dirname, "plugins")
});
async function run() {
await manager.installFromNpm("moment");
const moment = manager.require("moment");
console.log(moment().format());
await manager.uninstall("moment");
}
run();
In the above code I install moment package at runtime, load and execute it. Here I have used typescript, but the same can be written with plain javascript.
Plugins are installed inside the directory specified in the PluginManager constructor or in the plugins directory if not specified.
I just created a great module, for enhancements like your proposal:
https://www.npmjs.com/package/#kawix/core
You can read the README.md, for try understand the usage, i will add a basic example:
> npm install -g #kawix/core
> kwcore "https://raw.githubusercontent.com/voxsoftware/kawix-core/master/example/npmrequire/express.js"
And this is the content of file https://raw.githubusercontent.com/voxsoftware/kawix-core/master/example/npmrequire/express.js
// this will download the npm module and make a local cache
import express from 'npm://express#^4.16.4'
var app = express()
app.get('/', function (req, res) {
res.send('Hello World')
})
app.listen(3000)
console.log("Listening on 3000")
https://api-docs.npms.io/ has an http API for searching npm packages.
Which can be used like this: https://api.npms.io/v2/search?q=keywords:myapp to get all plugins for myapp
To actually download/install an npm package into any folder, I found an npm package called download-npm-package that lets me do it in code

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.

Cross platform: require('..') and fs point to different location for globally installed node package

A globally installed node package can overwrite its config.json with command line parameters.
The settings are read with require('../config.json'); and are updated with:
var globalConfigPath = path.normalize(__dirname + '/../config.json');
fs.writeFileSync(globalConfigPath, JSON.stringify(globalDefaults));
This works on my Windows machine both in local directory with node index.js config ... and as a globally installed package. In VirtualBox Debian however it only works in the local directory.
When installed globally on Debian:
With require, it appears to read from:
/home/vagrant/.nave/installed/default/lib/node_modules/mymodule/config.json
But the __dirname points to:
/home/vagrant/.nave/installed/0.12.2/lib/node_modules/mymodule/config.json
I worked around this by using fs for both the read and write but I'm still curious as to why they are not pointing to the same directory.

Can I access locally-installed packages from a globally-installed package?

I don't know if I've worded the question properly, so I apologize if it isn't clear from the title what I mean.
Say I have an NPM package which installs an executable. Presumably I want users to install this package with the -g flag so that they can run it whenever.
In this case, when I call require() from within the executable, it will look for packages installed globally.
But suppose this package provides generic functionality for Node projects. I might want to know which packages the current project has installed locally. Should I just assume:
path.join(process.cwd(), 'node_modules')
Or is there a more "correct" way to set the NODE_PATH in this case? (Or rather than set NODE_PATH, should I just require(absolute_path_to_file)?)
require will not only lookup the package inside $(CWD)\node_modules but also inside all node_modules of parent, grandparent, etc. So you can use resolve on npm to solve this problem
FILE: your_global_command.js
// npm install resolve
var resolve = require('resolve').sync;
// Lookup for local module at current working dir
function require_cwd(name) {
var absolute_path = resolve(name, { basedir: process.cwd() });
return require(absolute_path);
}
// Load local express
// this will throw an error when express is not found as local module
var express = require_cwd('express');
I also create a package to require a package at current-working-dir (instead of __dirname of module):
https://npmjs.org/package/require-cwd

Determine command line working directory when running node bin script

I am creating a node command line interface. It is installed globally and uses a bin file to execute.
I plan to have a command window open at the root directory of the files I am working on and then just run the command however I have been unable to determine the current working directory as process.cwd() is returning the directory of the node package. I initially assumed that since the code is being executed using a batch file as a wrapper (that is how bin files can execute without node at the beginning) then it is impossible but coffee-script manages to do it. I took a look at the coffee-script source but couldn't follow it (not experienced enough).
To test it for yourself create a package with this package.json file:
{
"name": "test-package",
"version": "1.0.0",
"bin": {
"test-package": "./bin/test-package"
},
"main": "/lib/test"
}
this test-package file in bin:
#!/usr/bin/env node
var path = require('path');
var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require(lib + '/test');
Could anyone shed some light onto this.
and then try and get the command line directory inside lib/test.
process.cwd() returns directory where command has been executed (not directory of the node package) if it's has not been changed by 'process.chdir' inside of application.
__filename returns absolute path to file where it is placed.
__dirname returns absolute path to directory of __filename.
If you need to load files from your module directory you need to use relative paths.
require('../lib/test');
instead of
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require(lib + '/test');
It's always relative to file where it called from and don't depend on current work dir.
Current Working Directory
To get the current working directory, you can use:
process.cwd()
However, be aware that some scripts, notably gulp, will change the current working directory with process.chdir().
Node Module Path
You can get the path of the current module with:
__filename
__dirname
Original Directory (where the command was initiated)
If you are running a script from the command line, and you want the original directory from which the script was run, regardless of what directory the script is currently operating in, you can use:
process.env.INIT_CWD
Original directory, when working with NPM scripts
It's sometimes desirable to run an NPM script in the current directory, rather than the root of the project.
This variable is available inside npm package scripts as:
$INIT_CWD.
You must be running a recent version of NPM. If this variable is not available, make sure NPM is up to date.
This will allow you access the current path in your package.json, e.g.:
scripts: {
"customScript": "gulp customScript --path $INIT_CWD"
}
path.resolve('.') is also a reliable and clean option, because we almost always require('path'). It will give you absolute path of the directory from where it is called.
Alternatively, if you want to solely obtain the current directory of the current NodeJS script, you could try something simple like this. Note that this will not work in the Node CLI itself:
var fs = require('fs'),
path = require('path');
var dirString = path.dirname(fs.realpathSync(__filename));
// output example: "/Users/jb/workspace/abtest"
console.log('directory to start walking...', dirString);
Warning if using ES Modules
According to the ECMAScript modules docs:
CommonJS variables __filename or __dirname are not available in ES modules.
Instead, use cases can be replicated via import.meta.url.
So according to why is __dirname not defined in node?, you can do the following:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Here's what worked for me:
console.log(process.mainModule.filename);

Resources