Repository navigation
Entry points specified as absolute URLs fail to load #46009
Description
Version
19.3.0
Platform
Darwin Geoffreys-MacBook-Pro.local 22.2.0 Darwin Kernel Version 22.2.0: Fri Nov 11 02:08:47 PST 2022; root:xnu-8792.61.2~4/RELEASE_X86_64 x86_64
Subsystem
module, esm, process, cli
What steps will reproduce the bug?
With a checkout of the node repo at ~/Sites/node:
$ pwd
/Users/geoffrey
$ node file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs
node:internal/modules/cjs/loader:1042
throw err;
^
Error: Cannot find module '/Users/geoffrey/file:/Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs'
at Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
at Module._load (node:internal/modules/cjs/loader:885:27)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:82:12)
at node:internal/main/run_main_module:23:47 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
Node.js v19.3.0Note the /Users/geoffrey/file:/Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs constructed specifier. This doesn’t make sense.
How often does it reproduce? Is there a required condition?
Every time.
What is the expected behavior?
Absolute file URLs should be allowable as program entry points.
What do you see instead?
Error: Cannot find module '/Users/geoffrey/file:/Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs'
Additional information
I understand per https://nodejs.org/api/cli.html#program-entry-point the entry point is parsed by the CommonJS resolver, even when it passes the criteria for being loaded by the ESM one; but it doesn’t make sense that either resolver would be constructing an invalid path.
It’s also counterintuitive that file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs should be acceptable input for --loader and --import but not as the main entry point. For example, all of these are valid:
node --import file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs --eval ';'node --loader file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs --eval ';'node --import file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs ./Sites/node/test/fixtures/es-modules/mjs-file.mjsnode --loader file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs ./Sites/node/test/fixtures/es-modules/mjs-file.mjs
cc @aduh95 @JakobJingleheimer @nodejs/modules @nodejs/loaders
This came up because I was writing a test like this:
const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [
'--loader',
fixtures.fileURL('/es-module-loaders/loader.mjs'),
fixtures.fileURL('/es-modules/mjs-file.mjs'),
]);The solution was to change the second fixtures.fileURL to fixtures.path; but this feels wrong.
Activity
ljharb commentedon Dec 29, 2022
If I'm reading this right, it seems like there's two distinct (altho related) items here:
aduh95 commentedon Dec 29, 2022
The issue is that
file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjscan be parsed either as an absolute file: URL or a relative path. Since CLI application more often deal with path than URLs, it doesn’t strike me as bad design to prefer dealing with paths only (but maybe we should display a warning when the path doesn’t exist and can be parsed as a valid URL).It's not bug, it's a feature :)
./is always prepended when something that's not an absolute path is provided so it never references anode_modulespackage.Do we need a new feature or are we happy suggesting folks to do one of the following?
node --input-type=module -e 'import "file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs"'node --import file:///Users/geoffrey/Sites/node/test/fixtures/es-modules/mjs-file.mjs -e ';'JakobJingleheimer commentedon Dec 29, 2022
Hmm, yeah, I would expect anything provided via CLI to be a path, not a URL. I think we should handle it in SOME way, maybe even throw if the underlying utils can't handle a URL.
arcanis commentedon Dec 29, 2022
Imo it should however be consistent with how
--require/--import/--loaderwork (but more in the sense "these options should all accept absolute paths", not the other way around).devsnek commentedon Dec 29, 2022
the resolved path isn't invalid, you just don't happen to have a folder named
file:in your cwd (which you could if you really wanted to)aduh95 commentedon Dec 29, 2022
I disagree, it seems much more logical that
--importbehaves the same asimport– i.e. no concept of paths, only relative and absolute URLs. Luckily UNIX paths can serve as relative URLs as long as all components of the path are composed of alphanumeric ASCII chars.Similarly,
--requireshould support the same asrequire()– i.e. paths rather than URLs.bmeck commentedon Dec 29, 2022
GeoffreyBooth commentedon Dec 29, 2022
Sure, but I couldn’t have a folder named
file://orfile:///, could I? It collapses the///into/, which I guess it’s always done, but does it have to? If not, that could be one way to disambiguate.There’s a distinction between allowable potential filename/path and existing potential filename/path. The resolution algorithm tries a bunch of ways to interpret the input, returning on the first option that succeeds or erroring if they all fail. We could add an additional method or two to the list of ways that it tries, to try to load the entry point if the input is interpreted as a URL that maps to an existing file:
After exhausting all current methods of trying to resolve the entry point, if the entry point begins with a known protocol like
file://,data://,http://, orhttps://, parse it viafileURLToPath(forfile:) or via the ESM resolution algorithm (for other supported protocols). Alternatively we could check for these protocols before trying to resolve via the CommonJS loader, which would technically be a breaking change for anyone with folders namedfile:,data:,http:, orhttps:.After exhausting all current methods of trying to resolve the entry point, run the input through the ESM resolution algorithm (like what’s used for
--import) and see if any of its attempts find an existing file.bnoordhuis commentedon Dec 30, 2022
What you're proposing is, depending on one's POV, either DWIM or second-guessing the user.
I lean towards simplicity. Less room for surprise, confusion, and bugs - potentially of the security impacting kind.
GeoffreyBooth commentedon Jan 9, 2023
avivkeller commentedon Sep 27, 2024
Fixed by
--entry-url, reopen if that is not the case.