Skip to content

Commit b6ea5df

Browse files
David Goldsteintargos
David Goldstein
authored andcommitted
module: add --preserve-symlinks-main
Add `--preserve-symlinks-main` option which behaves like `--preserve-symlinks` but for `require.main`. PR-URL: #19911 Reviewed-By: James M Snell <[email protected]>
1 parent de34cfa commit b6ea5df

File tree

8 files changed

+124
-4
lines changed

8 files changed

+124
-4
lines changed

doc/api/cli.md

+23
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,29 @@ are linked from more than one location in the dependency tree (Node.js would
217217
see those as two separate modules and would attempt to load the module multiple
218218
times, causing an exception to be thrown).
219219

220+
The `--preserve-symlinks` flag does not apply to the main module, which allows
221+
`node --preserve-symlinks node_module/.bin/<foo>` to work. To apply the same
222+
behavior for the main module, also use `--preserve-symlinks-main`.
223+
224+
### `--preserve-symlinks-main`
225+
<!-- YAML
226+
added: REPLACEME
227+
-->
228+
229+
Instructs the module loader to preserve symbolic links when resolving and
230+
caching the main module (`require.main`).
231+
232+
This flag exists so that the main module can be opted-in to the same behavior
233+
that `--preserve-symlinks` gives to all other imports; they are separate flags,
234+
however, for backward compatibility with older Node.js versions.
235+
236+
Note that `--preserve-symlinks-main` does not imply `--preserve-symlinks`; it
237+
is expected that `--preserve-symlinks-main` will be used in addition to
238+
`--preserve-symlinks` when it is not desirable to follow symlinks before
239+
resolving relative paths.
240+
241+
See `--preserve-symlinks` for more information.
242+
220243
### `--prof-process`
221244
<!-- YAML
222245
added: v5.2.0

doc/node.1

+4-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,10 @@ Among other uses, this can be used to enable FIPS-compliant crypto if Node.js is
143143
Emit pending deprecation warnings.
144144
.
145145
.It Fl -preserve-symlinks
146-
Instructs the module loader to preserve symbolic links when resolving and caching modules.
146+
Instructs the module loader to preserve symbolic links when resolving and caching modules other than the main module.
147+
.
148+
.It F1 -preserve-symlinks-main
149+
Instructs the module loader to preserve symbolic links when resolving and caching the main module.
147150
.
148151
.It Fl -prof-process
149152
Process V8 profiler output generated using the V8 option

lib/internal/modules/cjs/loader.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const {
4242
stripShebang
4343
} = require('internal/modules/cjs/helpers');
4444
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
45+
const preserveSymlinksMain = !!process.binding('config').preserveSymlinksMain;
4546
const experimentalModules = !!process.binding('config').experimentalModules;
4647

4748
const {
@@ -239,7 +240,21 @@ Module._findPath = function(request, paths, isMain) {
239240
var rc = stat(basePath);
240241
if (!trailingSlash) {
241242
if (rc === 0) { // File.
242-
if (preserveSymlinks && !isMain) {
243+
if (!isMain) {
244+
if (preserveSymlinks) {
245+
filename = path.resolve(basePath);
246+
} else {
247+
filename = toRealPath(basePath);
248+
}
249+
} else if (preserveSymlinksMain) {
250+
// For the main module, we use the preserveSymlinksMain flag instead
251+
// mainly for backward compatibility, as the preserveSymlinks flag
252+
// historically has not applied to the main module. Most likely this
253+
// was intended to keep .bin/ binaries working, as following those
254+
// symlinks is usually required for the imports in the corresponding
255+
// files to resolve; that said, in some use cases following symlinks
256+
// causes bigger problems which is why the preserveSymlinksMain option
257+
// is needed.
243258
filename = path.resolve(basePath);
244259
} else {
245260
filename = toRealPath(basePath);

lib/internal/modules/esm/default_resolve.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { NativeModule, internalBinding } = require('internal/bootstrap/loaders');
77
const { extname } = require('path');
88
const { realpathSync } = require('fs');
99
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
10+
const preserveSymlinksMain = !!process.binding('config').preserveSymlinksMain;
1011
const {
1112
ERR_MISSING_MODULE,
1213
ERR_MODULE_RESOLUTION_LEGACY,
@@ -71,7 +72,7 @@ function resolve(specifier, parentURL) {
7172

7273
const isMain = parentURL === undefined;
7374

74-
if (!preserveSymlinks || isMain) {
75+
if (isMain ? !preserveSymlinksMain : !preserveSymlinks) {
7576
const real = realpathSync(getPathFromURL(url), {
7677
[internalFS.realpathCacheKey]: realpathCache
7778
});

src/node.cc

+15-1
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ bool trace_warnings = false;
236236
// that is used by lib/module.js
237237
bool config_preserve_symlinks = false;
238238

239+
// Set in node.cc by ParseArgs when --preserve-symlinks-main is used.
240+
// Used in node_config.cc to set a constant on process.binding('config')
241+
// that is used by lib/module.js
242+
bool config_preserve_symlinks_main = false;
243+
239244
// Set in node.cc by ParseArgs when --experimental-modules is used.
240245
// Used in node_config.cc to set a constant on process.binding('config')
241246
// that is used by lib/module.js
@@ -3519,6 +3524,8 @@ static void PrintHelp() {
35193524
" --pending-deprecation emit pending deprecation warnings\n"
35203525
#if defined(NODE_HAVE_I18N_SUPPORT)
35213526
" --preserve-symlinks preserve symbolic links when resolving\n"
3527+
" --preserve-symlinks-main preserve symbolic links when resolving\n"
3528+
" the main module\n"
35223529
#endif
35233530
" --prof-process process v8 profiler output generated\n"
35243531
" using --prof\n"
@@ -3569,7 +3576,6 @@ static void PrintHelp() {
35693576
" -r, --require module to preload (option can be "
35703577
"repeated)\n"
35713578
" -v, --version print Node.js version\n"
3572-
35733579
"\n"
35743580
"Environment variables:\n"
35753581
"NODE_DEBUG ','-separated list of core modules\n"
@@ -3832,6 +3838,8 @@ static void ParseArgs(int* argc,
38323838
Revert(cve);
38333839
} else if (strcmp(arg, "--preserve-symlinks") == 0) {
38343840
config_preserve_symlinks = true;
3841+
} else if (strcmp(arg, "--preserve-symlinks-main") == 0) {
3842+
config_preserve_symlinks_main = true;
38353843
} else if (strcmp(arg, "--experimental-modules") == 0) {
38363844
config_experimental_modules = true;
38373845
new_v8_argv[new_v8_argc] = "--harmony-dynamic-import";
@@ -4276,6 +4284,12 @@ void Init(int* argc,
42764284
SafeGetenv("NODE_PRESERVE_SYMLINKS", &text) && text[0] == '1';
42774285
}
42784286

4287+
{
4288+
std::string text;
4289+
config_preserve_symlinks_main =
4290+
SafeGetenv("NODE_PRESERVE_SYMLINKS_MAIN", &text) && text[0] == '1';
4291+
}
4292+
42794293
if (config_warning_file.empty())
42804294
SafeGetenv("NODE_REDIRECT_WARNINGS", &config_warning_file);
42814295

src/node_config.cc

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ static void Initialize(Local<Object> target,
7272

7373
if (config_preserve_symlinks)
7474
READONLY_BOOLEAN_PROPERTY("preserveSymlinks");
75+
if (config_preserve_symlinks_main)
76+
READONLY_BOOLEAN_PROPERTY("preserveSymlinksMain");
7577

7678
if (config_experimental_modules) {
7779
READONLY_BOOLEAN_PROPERTY("experimentalModules");

src/node_internals.h

+5
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ extern std::string openssl_config;
173173
// that is used by lib/module.js
174174
extern bool config_preserve_symlinks;
175175

176+
// Set in node.cc by ParseArgs when --preserve-symlinks-main is used.
177+
// Used in node_config.cc to set a constant on process.binding('config')
178+
// that is used by lib/module.js
179+
extern bool config_preserve_symlinks_main;
180+
176181
// Set in node.cc by ParseArgs when --experimental-modules is used.
177182
// Used in node_config.cc to set a constant on process.binding('config')
178183
// that is used by lib/module.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { spawn } = require('child_process');
5+
const assert = require('assert');
6+
const path = require('path');
7+
const fs = require('fs');
8+
9+
const tmpdir = require('../common/tmpdir');
10+
tmpdir.refresh();
11+
const tmpDir = tmpdir.path;
12+
13+
fs.mkdirSync(path.join(tmpDir, 'nested'));
14+
fs.mkdirSync(path.join(tmpDir, 'nested2'));
15+
16+
const entry = path.join(tmpDir, 'nested', 'entry.js');
17+
const entry_link_absolute_path = path.join(tmpDir, 'link.js');
18+
const submodule = path.join(tmpDir, 'nested2', 'submodule.js');
19+
const submodule_link_absolute_path = path.join(tmpDir, 'submodule_link.js');
20+
21+
fs.writeFileSync(entry, `
22+
const assert = require('assert');
23+
24+
// this import only resolves with --preserve-symlinks-main set
25+
require('./submodule_link.js');
26+
`);
27+
fs.writeFileSync(submodule, '');
28+
29+
try {
30+
fs.symlinkSync(entry, entry_link_absolute_path);
31+
fs.symlinkSync(submodule, submodule_link_absolute_path);
32+
} catch (err) {
33+
if (err.code !== 'EPERM') throw err;
34+
common.skip('insufficient privileges for symlinks');
35+
}
36+
37+
function doTest(flags, done) {
38+
// invoke the main file via a symlink. In this case --preserve-symlinks-main
39+
// dictates that it'll resolve relative imports in the main file relative to
40+
// the symlink, and not relative to the symlink target; the file structure set
41+
// up above requires this to not crash when loading ./submodule_link.js
42+
spawn(process.execPath,
43+
flags.concat([
44+
'--preserve-symlinks',
45+
'--preserve-symlinks-main', entry_link_absolute_path
46+
]),
47+
{ stdio: 'inherit' }).on('exit', (code) => {
48+
assert.strictEqual(code, 0);
49+
done();
50+
});
51+
}
52+
53+
// first test the commonjs module loader
54+
doTest([], () => {
55+
// now test the new loader
56+
doTest(['--experimental-modules'], () => {});
57+
});

0 commit comments

Comments
 (0)