Skip to content

Commit

Permalink
process: argv1 property to preserve original argv[1]
Browse files Browse the repository at this point in the history
  • Loading branch information
GeoffreyBooth committed Sep 28, 2023
1 parent 3838b57 commit 75cd731
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 38 deletions.
26 changes: 26 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,32 @@ $ bash -c 'exec -a customArgv0 ./node'
'customArgv0'
```

## `process.argv1`

<!-- YAML
added: REPLACEME
-->

* {string | undefined}

The `process.argv1` property stores a read-only copy of the original value of
`argv[1]` passed when Node.js starts.

```js
// test.js
console.log('argv1 ', process.argv1);
console.log('argv[1]', process.argv[1]);
```

```shell
$ node ./test.js
argv1 ./test.js
argv[1] /Users/geoffrey/Sites/node/test.js
```

`process.argv1` is `undefined` for cases where Node.js is run without a main
entry point, such as `node --eval`.

## `process.channel`

<!-- YAML
Expand Down
17 changes: 9 additions & 8 deletions lib/internal/main/run_main_module.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ const {
markBootstrapComplete,
} = require('internal/process/pre_execution');

prepareMainThreadExecution(true);
const mainEntry = prepareMainThreadExecution(true);

markBootstrapComplete();

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

// Note: this loads the module through the ESM loader if the module is
// determined to be an ES module. This hangs from the CJS module loader
// because we currently allow monkey-patching of the module loaders
// in the preloaded scripts through require('module').
// runMain here might be monkey-patched by users in --require.
// XXX: the monkey-patchability here should probably be deprecated.
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);
/**
* To support legacy monkey-patching of `Module.runMain`, we call `runMain` here to have the CommonJS loader begin
* the execution of the main entry point, even if the ESM loader immediately takes over because the main entry is an
* ES module or one of the other opt-in conditions (such as the use of `--import`) are met. Users can monkey-patch
* before the main entry point is loaded by doing so via scripts loaded through `--require`. This monkey-patchability
* is undesirable and should be deprecated.
*/
require('internal/modules/cjs/loader').Module.runMain(mainEntry);
21 changes: 11 additions & 10 deletions lib/internal/modules/run_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const {
} = primordials;

const { getOptionValue } = require('internal/options');
const path = require('path');

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

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

/**
* Determine whether the main entry point should be loaded through the ESM Loader.
* @param {string} mainPath Absolute path to the main entry point
* @param {string} mainPath - Absolute path to the main entry point
*/
function shouldUseESMLoader(mainPath) {
/**
Expand All @@ -53,22 +53,22 @@ function shouldUseESMLoader(mainPath) {

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

const { loadESM } = require('internal/process/esm_loader');
handleMainPromise(loadESM((esmLoader) => {
const main = path.isAbsolute(mainPath) ?
pathToFileURL(mainPath).href : mainPath;
return esmLoader.import(main, undefined, { __proto__: null });
return esmLoader.import(mainHref, undefined, { __proto__: null });
}));
}

/**
* Handle process exit events around the main entry point promise.
* @param {Promise} promise Main entry point promise
* @param {Promise} promise - Main entry point promise
*/
async function handleMainPromise(promise) {
const {
Expand All @@ -86,7 +86,8 @@ async function handleMainPromise(promise) {
* Parse the CLI main entry point string and run it.
* For backwards compatibility, we have to run a bunch of monkey-patchable code that belongs to the CJS loader (exposed
* by `require('module')`) even when the entry point is ESM.
* @param {string} main CLI main entry point string
* Because of backwards compatibility, this function is exposed publicly via `import { runMain } from 'node:module'`.
* @param {string} main - Resolved absolute path for the main entry point, if found
*/
function executeUserEntryPoint(main = process.argv[1]) {
const resolvedMain = resolveMainPath(main);
Expand Down
67 changes: 47 additions & 20 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,19 @@ const {
} = require('internal/v8/startup_snapshot');

function prepareMainThreadExecution(expandArgv1 = false, initializeModules = true) {
prepareExecution({
expandArgv1,
initializeModules,
isMainThread: true,
});
return prepareExecution(expandArgv1, initializeModules, true);
}

function prepareWorkerThreadExecution() {
prepareExecution({
expandArgv1: false,
initializeModules: false, // Will need to initialize it after policy setup
isMainThread: false,
});
prepareExecution(undefined, false, false); // Will need to initialize modules after policy setup
}

function prepareExecution(options) {
const { expandArgv1, initializeModules, isMainThread } = options;

function prepareExecution(expandArgv1, initializeModules, isMainThread) {
refreshRuntimeOptions();
reconnectZeroFillToggle();

// Patch the process object with legacy properties and normalizations
patchProcessObject(expandArgv1);
// Patch the process object and get the resolved main entry point.
const mainEntry = patchProcessObject(expandArgv1);
setupTraceCategoryState();
setupInspectorHooks();
setupWarningHandler();
Expand Down Expand Up @@ -131,6 +121,8 @@ function prepareExecution(options) {
if (initializeModules) {
setupUserModules();
}

return mainEntry;
}

function setupSymbolDisposePolyfill() {
Expand Down Expand Up @@ -176,12 +168,21 @@ function refreshRuntimeOptions() {
refreshOptions();
}

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

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

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

// Since we usually replace process.argv[1] below, preserve the original value in case the user needs it.
ObjectDefineProperty(process, 'argv1', {
__proto__: null,
enumerable: true,
// Only set it to true during snapshot building.
configurable: isBuildingSnapshot(),
value: process.argv[1],
});

process.exitCode = undefined;
process._exiting = false;
process.argv[0] = process.execPath;

if (expandArgv1 && process.argv[1] &&
!StringPrototypeStartsWith(process.argv[1], '-')) {
// Expand process.argv[1] into a full path.
const path = require('path');
// If a requested, update process.argv[1] to replace whatever the user provided with the resolved absolute URL or file
// path of the entry point.
/** @type {ReturnType<determineMainEntry>} */
let mainEntry;
if (expandArgv1) {
mainEntry = determineMainEntry(process.argv[1]);
try {
process.argv[1] = path.resolve(process.argv[1]);
process.argv[1] = mainEntry;
} catch {
// Continue regardless of error.
}
Expand All @@ -226,6 +238,21 @@ function patchProcessObject(expandArgv1) {
addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation');
addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false);
addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false);

return mainEntry;
}

/**
* For non-STDIN input, we need to resolve what was specified on the command line into a path.
* @param {string} input - What was specified on the command line as the main entry point.
*/
function determineMainEntry(input = process.argv[1]) {
if (!input || StringPrototypeStartsWith(input, '-')) { // STDIN
return undefined;
}
// Expand the main entry point into a full path.
const { resolve } = require('path');
return resolve(input);
}

function addReadOnlyProcessAlias(name, option, enumerable = true) {
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/process-argv1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log(process.argv1);
console.log(process.argv[1]);
File renamed without changes.
20 changes: 20 additions & 0 deletions test/parallel/test-process-argv1.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { strictEqual } from 'node:assert';
import { describe, it } from 'node:test';
import { dirname } from 'node:path';

describe('process.argv1', { concurrency: true }, () => {
it('should be the string for the entry point before the entry was resolved into an absolute path', async () => {
const fixtureAbsolutePath = fixtures.path('process-argv1.js');
const fixtureRelativePath = './process-argv1.js';
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [fixtureRelativePath], {
cwd: dirname(fixtureAbsolutePath),
});

strictEqual(stderr, '');
strictEqual(stdout, `${fixtureRelativePath}\n${fixtureAbsolutePath}\n`);
strictEqual(code, 0);
strictEqual(signal, null);
});
});

0 comments on commit 75cd731

Please sign in to comment.