Skip to content

Commit d9cf132

Browse files
committed
process: argv1 property to preserve original argv[1]
1 parent 3838b57 commit d9cf132

File tree

7 files changed

+115
-38
lines changed

7 files changed

+115
-38
lines changed

doc/api/process.md

+26
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,32 @@ $ bash -c 'exec -a customArgv0 ./node'
958958
'customArgv0'
959959
```
960960

961+
## `process.argv1`
962+
963+
<!-- YAML
964+
added: REPLACEME
965+
-->
966+
967+
* {string | undefined}
968+
969+
The `process.argv1` property stores a read-only copy of the original value of
970+
`argv[1]` passed when Node.js starts.
971+
972+
```js
973+
// test.js
974+
console.log('argv1 ', process.argv1);
975+
console.log('argv[1]', process.argv[1]);
976+
```
977+
978+
```shell
979+
$ node ./test.js
980+
argv1 ./test.js
981+
argv[1] /Users/geoffrey/Sites/node/test.js
982+
```
983+
984+
`process.argv1` is `undefined` for cases where Node.js is run without a main
985+
entry point, such as `node --eval`.
986+
961987
## `process.channel`
962988

963989
<!-- YAML

lib/internal/main/run_main_module.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ const {
77
markBootstrapComplete,
88
} = require('internal/process/pre_execution');
99

10-
prepareMainThreadExecution(true);
10+
const mainEntry = prepareMainThreadExecution(true);
1111

1212
markBootstrapComplete();
1313

1414
// Necessary to reset RegExp statics before user code runs.
1515
RegExpPrototypeExec(/^/, '');
1616

17-
// Note: this loads the module through the ESM loader if the module is
18-
// determined to be an ES module. This hangs from the CJS module loader
19-
// because we currently allow monkey-patching of the module loaders
20-
// in the preloaded scripts through require('module').
21-
// runMain here might be monkey-patched by users in --require.
22-
// XXX: the monkey-patchability here should probably be deprecated.
23-
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);
17+
/**
18+
* To support legacy monkey-patching of `Module.runMain`, we call `runMain` here to have the CommonJS loader begin
19+
* the execution of the main entry point, even if the ESM loader immediately takes over because the main entry is an
20+
* ES module or one of the other opt-in conditions (such as the use of `--import`) are met. Users can monkey-patch
21+
* before the main entry point is loaded by doing so via scripts loaded through `--require`. This monkey-patchability
22+
* is undesirable should be deprecated.
23+
*/
24+
require('internal/modules/cjs/loader').Module.runMain(mainEntry);

lib/internal/modules/run_main.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const {
55
} = primordials;
66

77
const { getOptionValue } = require('internal/options');
8-
const path = require('path');
98

109
/**
1110
* Get the absolute path to the main entry point.
@@ -16,7 +15,8 @@ function resolveMainPath(main) {
1615
// future major.
1716
// Module._findPath is monkey-patchable here.
1817
const { Module, toRealPath } = require('internal/modules/cjs/loader');
19-
let mainPath = Module._findPath(path.resolve(main), null, true);
18+
const { resolve } = require('path');
19+
let mainPath = Module._findPath(resolve(main), null, true);
2020
if (!mainPath) { return; }
2121

2222
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
@@ -29,7 +29,7 @@ function resolveMainPath(main) {
2929

3030
/**
3131
* Determine whether the main entry point should be loaded through the ESM Loader.
32-
* @param {string} mainPath Absolute path to the main entry point
32+
* @param {string} mainPath - Absolute path to the main entry point
3333
*/
3434
function shouldUseESMLoader(mainPath) {
3535
/**
@@ -53,22 +53,22 @@ function shouldUseESMLoader(mainPath) {
5353

5454
/**
5555
* Run the main entry point through the ESM Loader.
56-
* @param {string} mainPath Absolute path to the main entry point
56+
* @param {string} mainPath - Absolute path for the main entry point
5757
*/
5858
function runMainESM(mainPath) {
59-
const { loadESM } = require('internal/process/esm_loader');
59+
const { isAbsolute } = require('path');
6060
const { pathToFileURL } = require('internal/url');
61+
const mainHref = isAbsolute(mainPath) ? pathToFileURL(mainPath).href : mainPath;
6162

63+
const { loadESM } = require('internal/process/esm_loader');
6264
handleMainPromise(loadESM((esmLoader) => {
63-
const main = path.isAbsolute(mainPath) ?
64-
pathToFileURL(mainPath).href : mainPath;
65-
return esmLoader.import(main, undefined, { __proto__: null });
65+
return esmLoader.import(mainHref, undefined, { __proto__: null });
6666
}));
6767
}
6868

6969
/**
7070
* Handle process exit events around the main entry point promise.
71-
* @param {Promise} promise Main entry point promise
71+
* @param {Promise} promise - Main entry point promise
7272
*/
7373
async function handleMainPromise(promise) {
7474
const {
@@ -86,7 +86,8 @@ async function handleMainPromise(promise) {
8686
* Parse the CLI main entry point string and run it.
8787
* For backwards compatibility, we have to run a bunch of monkey-patchable code that belongs to the CJS loader (exposed
8888
* by `require('module')`) even when the entry point is ESM.
89-
* @param {string} main CLI main entry point string
89+
* Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
90+
* @param {string} main - Resolved absolute path for the main entry point, if found
9091
*/
9192
function executeUserEntryPoint(main = process.argv[1]) {
9293
const resolvedMain = resolveMainPath(main);

lib/internal/process/pre_execution.js

+47-20
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,19 @@ const {
5252
} = require('internal/v8/startup_snapshot');
5353

5454
function prepareMainThreadExecution(expandArgv1 = false, initializeModules = true) {
55-
prepareExecution({
56-
expandArgv1,
57-
initializeModules,
58-
isMainThread: true,
59-
});
55+
return prepareExecution(expandArgv1, initializeModules, true);
6056
}
6157

6258
function prepareWorkerThreadExecution() {
63-
prepareExecution({
64-
expandArgv1: false,
65-
initializeModules: false, // Will need to initialize it after policy setup
66-
isMainThread: false,
67-
});
59+
prepareExecution(undefined, false, false); // Will need to initialize modules after policy setup
6860
}
6961

70-
function prepareExecution(options) {
71-
const { expandArgv1, initializeModules, isMainThread } = options;
72-
62+
function prepareExecution(expandArgv1, initializeModules, isMainThread) {
7363
refreshRuntimeOptions();
7464
reconnectZeroFillToggle();
7565

76-
// Patch the process object with legacy properties and normalizations
77-
patchProcessObject(expandArgv1);
66+
// Patch the process object and get the resolved main entry point.
67+
const mainEntry = patchProcessObject(expandArgv1);
7868
setupTraceCategoryState();
7969
setupInspectorHooks();
8070
setupWarningHandler();
@@ -131,6 +121,8 @@ function prepareExecution(options) {
131121
if (initializeModules) {
132122
setupUserModules();
133123
}
124+
125+
return mainEntry;
134126
}
135127

136128
function setupSymbolDisposePolyfill() {
@@ -176,12 +168,21 @@ function refreshRuntimeOptions() {
176168
refreshOptions();
177169
}
178170

171+
/**
172+
* Patch the process object with legacy properties and normalizations.
173+
* Replace `process.argv[0]` with `process.execPath`, preserving the original `argv[0]` value as `process.argv0`.
174+
* Replace `process.argv[1]` with the resolved file path of the entry point, if requested and a replacement can be
175+
* found; preserving the original value as `process.argv1`.
176+
* @param {boolean} expandArgv1 - Whether to replace `process.argv[1]` with the resolved file path of the main entry
177+
* point.
178+
*/
179179
function patchProcessObject(expandArgv1) {
180180
const binding = internalBinding('process_methods');
181181
binding.patchProcessObject(process);
182182

183183
require('internal/process/per_thread').refreshHrtimeBuffer();
184184

185+
// Since we replace process.argv[0] below, preserve the original value in case the user needs it.
185186
ObjectDefineProperty(process, 'argv0', {
186187
__proto__: null,
187188
enumerable: true,
@@ -190,16 +191,27 @@ function patchProcessObject(expandArgv1) {
190191
value: process.argv[0],
191192
});
192193

194+
// Since we usually replace process.argv[1] below, preserve the original value in case the user needs it.
195+
ObjectDefineProperty(process, 'argv1', {
196+
__proto__: null,
197+
enumerable: true,
198+
// Only set it to true during snapshot building.
199+
configurable: isBuildingSnapshot(),
200+
value: process.argv[1],
201+
});
202+
193203
process.exitCode = undefined;
194204
process._exiting = false;
195205
process.argv[0] = process.execPath;
196206

197-
if (expandArgv1 && process.argv[1] &&
198-
!StringPrototypeStartsWith(process.argv[1], '-')) {
199-
// Expand process.argv[1] into a full path.
200-
const path = require('path');
207+
// If a requested, update process.argv[1] to replace whatever the user provided with the resolved absolute URL or file
208+
// path of the entry point.
209+
/** @type {ReturnType<determineMainEntry>} */
210+
let mainEntry;
211+
if (expandArgv1) {
212+
mainEntry = determineMainEntry(process.argv[1]);
201213
try {
202-
process.argv[1] = path.resolve(process.argv[1]);
214+
process.argv[1] = mainEntry;
203215
} catch {
204216
// Continue regardless of error.
205217
}
@@ -226,6 +238,21 @@ function patchProcessObject(expandArgv1) {
226238
addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation');
227239
addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false);
228240
addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false);
241+
242+
return mainEntry;
243+
}
244+
245+
/**
246+
* For non-STDIN input, we need to resolve what was specified on the command line into a path.
247+
* @param {string} input - What was specified on the command line as the main entry point.
248+
*/
249+
function determineMainEntry(input = process.argv[1]) {
250+
if (!input || StringPrototypeStartsWith(input, '-')) { // STDIN
251+
return undefined;
252+
}
253+
// Expand the main entry point into a full path.
254+
const { resolve } = require('path');
255+
return resolve(input);
229256
}
230257

231258
function addReadOnlyProcessAlias(name, option, enumerable = true) {

test/fixtures/process-argv1.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log(process.argv1);
2+
console.log(process.argv[1]);
File renamed without changes.

test/parallel/test-process-argv1.mjs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
import { strictEqual } from 'node:assert';
4+
import { describe, it } from 'node:test';
5+
import { dirname } from 'node:path';
6+
7+
describe('process.argv1', { concurrency: true }, () => {
8+
it('should be the string for the entry point before the entry was resolved into an absolute path', async () => {
9+
const fixtureAbsolutePath = fixtures.path('process-argv1.js');
10+
const fixtureRelativePath = './process-argv1.js';
11+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [fixtureRelativePath], {
12+
cwd: dirname(fixtureAbsolutePath),
13+
});
14+
15+
strictEqual(stderr, '');
16+
strictEqual(stdout, `${fixtureRelativePath}\n${fixtureAbsolutePath}\n`);
17+
strictEqual(code, 0);
18+
strictEqual(signal, null);
19+
});
20+
});

0 commit comments

Comments
 (0)