Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] npm init @usr/foo fails when @user/create-foo is unpublished but installed globally #5596

Closed
2 tasks done
ig3 opened this issue Sep 27, 2022 · 2 comments
Closed
2 tasks done
Labels
Bug thing that needs fixing Needs Triage needs review for next steps Release 8.x work is associated with a specific npm 8 release

Comments

@ig3
Copy link

ig3 commented Sep 27, 2022

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

Current Behavior

I have written an npm initializer @usr/create-foo and installed it globally with npm install -g but not published.

I then attempt to use this initializer with npm init @usr/foo but this fails after attempting to find the package in the registry, where it doesn't exist because it hasn't been published.

npm init @usr/foo is translated to npm exec @usr/create-foo as described in the docs. But this fails to find the bin from the globally installed package.

package.json of the initializer includes:

{
  "name": "@usr/create-foo",
  "bin": "index.js"
...
}

Documentation for package.json includes: If you have a single executable, and its name should be the name of the package, then you can just supply it as a string.

Here, the package name is @usr/create-foo so the name of the executable should be @usr/create-foo and this is consistent with the executable that npm exec @usr/create-foo attempts to execute.

The problem is that npm install -g . in the directory of the initializer package doesn't install an executable @usr/create-foo as the documentation indicates it should. Rather, it installs create-foo. Therefore, npm exec @usr/create-foo can't find the executable: it hasn't been installed as such but rather with the different name create-foo.

The problem is compounded by the fact that npm exec @usr/create-foo looks for a locally installed package but it doesn't look for a globally installed package. It goes straight to trying to download the package, ignoring the globally installed package.

I hacked libnpmexec/lib/index.js to look for the globally installed package. It gets the manifest OK and derives the name of the executable: create-foo rather than @usr/create-foo. But still it doesn't work because next it checks that every package in the manifest list (there is only one) is installed locally - again ignoring the globally installed package.

A bit more hacking and I could have it working.

I can simply ignore the 'sugar' of npm init @usr/foo and run the bin directly: create-foo works fine although @usr/create-foo does not, despite the documentation for package.json.

Rather than bin being set to a string value in package.json I also tried:

{
  "name": "@usr/create-foo",
  "bin": {
    "@usr/create-foo": "index.js"
  },
  ...
}

But even with the executable name given explicitly rather than implied by the package name, npm install -g installs the executable as create-foo rather than @usr/create-foo.

Expected Behavior

If package name is @usr/create-foo and the value of bin is a string, then the executable name should be @usr/create-foo, according to the package.json documentation. While this is what I expect based on the documentation, I think it is problematic: attempting to execute @usr/create-foo will look for create-foo in a local subdirectory @usr rather finding it in /bin which is in the path - where the globally installed package executables are installed.

npm init @usr/foo and npm exec @usr/create-foo should find the globally installed package and use it if there is no locally installed package of that name. Having found it, they should resolve the executable name consistently with where npm install -g installs it, despite that being inconsistent with the documentation. Given that the package is globally installed, neither command should attempt to download it from the registry.

Installing the executable to the bin directory with the scope refix removed is problematic. Consider the case of several scoped packages named @scope/create for different scopes, for use with npm init @scope. These would be translated to npm exec @scope/create which appears distinct for each scope but if the scope prefix is removed then each executable is simply create and the executables from the various scoped packages will collide. Yet one of the motivations for using scoped packages is exactly to avoid name collisions. Stripping the scope prefix eliminates this advantage. On the other hand, keeping it with the separator character being the same as the system path separator is problematic. Then the executable would be installed into a subdirectory of the bin directory or it would be installed with a name that includes the path separator, which would have to be escaped. Neither is very appealing. The npm exec command at least could find the executable in the .bin directory of the installed package, rather than the global bin directory. That would avoid name conflicts and allow npm init @scope to work as expected. I assume this is effectively what happens when the package is downloaded from the registry: it is downloaded to an npx cache and the executable is run from the copy in that cache, never installed to the global bin directory where it might collide with other such executables.

Steps To Reproduce

  1. Create a scoped package named @usr/create-foo with an executable, either with bin value being a string - the path of the script that implements the executable, or an object where key is the same as the package name and value is the same path.
  2. Install the package globally with npm install -g . in the root directory of the package
  3. Do not publish the package to the registry
  4. Confirm that the package has been installed globally, including the executable, though with the 'incorrect' name create-foo rather than @usr/create-foo
  5. In an empty directory, run npm init @usr/foo and npm exec @usr/create-foo - both will fail after failing attempted download of the package from the registry, despite it being installed globally.

Environment

  • npm: 8.3.1
  • Node.js: v16.14.0
  • OS Name: Debian GNU/Linux 11 (bullseye)
  • System Model Name: Dynabook tecra A50-J
  • npm config:
; "user" config from /home/ian/.npmrc

//npm.entrain.nz/:_authToken = (protected) 
//registry.npmjs.org/:_authToken = (protected) 
prefix = "/home/ian/n" 
registry = "https://registry.npmjs.org/" 

; node bin location = /home/ian/n/bin/node
; cwd = /home/ian
; HOME = /home/ian
; Run `npm config ls -l` to show all defaults.
@ig3 ig3 added Bug thing that needs fixing Needs Triage needs review for next steps Release 8.x work is associated with a specific npm 8 release labels Sep 27, 2022
@ig3
Copy link
Author

ig3 commented Sep 30, 2022

After all, I realize that this bug/limitation isn't serious. If the initiator package is installed globally, it can be run directly. npm init <initiator> is just another way or running it. And if it isn't installed globally, it can be run by npx. So there is really no need for npm init <initiator> that I can see and without it, one is free to name the package to avoid naming conflicts. None the less, it seems as long as the behaviour remains, it would make more sense if it stripped the scope from the package name before trying to execute it, to be consistent with what npm install -g does. For my part, now that I realize more clearly what npm init <initiator> is doing, I won't write initiators. I'll just write packages/commands that do the same thing and run them directly.

@ig3
Copy link
Author

ig3 commented Jun 17, 2024

I opened it so I guess I can close it. It's not an issue for me any more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug thing that needs fixing Needs Triage needs review for next steps Release 8.x work is associated with a specific npm 8 release
Projects
None yet
Development

No branches or pull requests

1 participant