From 3c40868fd37ea5f94a11b7284241aca2b4f50fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 16 Jul 2024 15:29:10 +0200 Subject: [PATCH 001/120] build: disable test-asan workflow It is running on ubuntu-20.04, which will inevitably be removed from GitHub actions at some point. Attempts to upgrade it to ubuntu-22.04 and ubuntu-24.04 have failed. It is now blocking V8 updates because of errors that happen only with the `test-asan` job. Refs: https://github.com/nodejs/node/pull/52374 Refs: https://github.com/nodejs/node/pull/53651#issuecomment-2198510810 PR-URL: https://github.com/nodejs/node/pull/53844 Reviewed-By: Marco Ippolito Reviewed-By: Antoine du Hamel Reviewed-By: Moshe Atlow Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell --- .github/workflows/test-asan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-asan.yml b/.github/workflows/test-asan.yml index 1d699e2846e6f8..f2767253aa4091 100644 --- a/.github/workflows/test-asan.yml +++ b/.github/workflows/test-asan.yml @@ -38,7 +38,7 @@ permissions: jobs: test-asan: - if: github.event.pull_request.draft == false + if: false # Temporarily disabled. References: https://github.com/nodejs/node/pull/52374, https://github.com/nodejs/node/pull/53651#issuecomment-2198510810 runs-on: ubuntu-20.04 env: CC: sccache clang From df5083e5f9064a94c91e61887948127423dca3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinicius=20Louren=C3=A7o?= <12551007+H4ad@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:40:22 -0300 Subject: [PATCH 002/120] src,lib: expose getCategoryEnabledBuffer to use on node.http Instead call the C++ code every time we need to check for a trace category, now we get the C++ pointer to the flag that holds the info if the trace is enabled and return this pointer inside a buffer that we can use to call/check if the value is enabled. With this change, no C++ call is made and the access to the info happens in JS side, which has no perf penalty. PR-URL: https://github.com/nodejs/node/pull/53602 Reviewed-By: James M Snell Reviewed-By: Matteo Collina Reviewed-By: Yagiz Nizipli --- lib/internal/http.js | 6 ++- src/node_trace_events.cc | 27 ++++++++++++ ...race-events-get-category-enabled-buffer.js | 43 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-trace-events-get-category-enabled-buffer.js diff --git a/lib/internal/http.js b/lib/internal/http.js index 6fc0156bf5265f..251f51ec454f9c 100644 --- a/lib/internal/http.js +++ b/lib/internal/http.js @@ -6,7 +6,7 @@ const { } = primordials; const { setUnrefTimeout } = require('internal/timers'); -const { trace, isTraceCategoryEnabled } = internalBinding('trace_events'); +const { getCategoryEnabledBuffer, trace } = internalBinding('trace_events'); const { CHAR_LOWERCASE_B, CHAR_LOWERCASE_E, @@ -35,8 +35,10 @@ function getNextTraceEventId() { return ++traceEventId; } +const httpEnabled = getCategoryEnabledBuffer('node.http'); + function isTraceHTTPEnabled() { - return isTraceCategoryEnabled('node.http'); + return httpEnabled[0] > 0; } const traceEventCategory = 'node,node.http'; diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index a0f8f4de67dabb..9787b14352753c 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -16,6 +16,8 @@ namespace node { class ExternalReferenceRegistry; using v8::Array; +using v8::ArrayBuffer; +using v8::BackingStore; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; @@ -25,6 +27,7 @@ using v8::Local; using v8::NewStringType; using v8::Object; using v8::String; +using v8::Uint8Array; using v8::Value; class NodeCategorySet : public BaseObject { @@ -120,6 +123,27 @@ static void SetTraceCategoryStateUpdateHandler( env->set_trace_category_state_function(args[0].As()); } +static void GetCategoryEnabledBuffer(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsString()); + + Isolate* isolate = args.GetIsolate(); + node::Utf8Value category_name(isolate, args[0]); + + const uint8_t* enabled_pointer = + TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(category_name.out()); + uint8_t* enabled_pointer_cast = const_cast(enabled_pointer); + + std::unique_ptr bs = ArrayBuffer::NewBackingStore( + enabled_pointer_cast, + sizeof(*enabled_pointer_cast), + [](void*, size_t, void*) {}, + nullptr); + auto ab = ArrayBuffer::New(isolate, std::move(bs)); + v8::Local u8 = v8::Uint8Array::New(ab, 0, 1); + + args.GetReturnValue().Set(u8); +} + void NodeCategorySet::Initialize(Local target, Local unused, Local context, @@ -132,6 +156,8 @@ void NodeCategorySet::Initialize(Local target, target, "setTraceCategoryStateUpdateHandler", SetTraceCategoryStateUpdateHandler); + SetMethod( + context, target, "getCategoryEnabledBuffer", GetCategoryEnabledBuffer); Local category_set = NewFunctionTemplate(isolate, NodeCategorySet::New); @@ -160,6 +186,7 @@ void NodeCategorySet::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(GetEnabledCategories); registry->Register(SetTraceCategoryStateUpdateHandler); + registry->Register(GetCategoryEnabledBuffer); registry->Register(NodeCategorySet::New); registry->Register(NodeCategorySet::Enable); registry->Register(NodeCategorySet::Disable); diff --git a/test/parallel/test-trace-events-get-category-enabled-buffer.js b/test/parallel/test-trace-events-get-category-enabled-buffer.js new file mode 100644 index 00000000000000..3017b8e6dc87d7 --- /dev/null +++ b/test/parallel/test-trace-events-get-category-enabled-buffer.js @@ -0,0 +1,43 @@ +'use strict'; +// Flags: --expose-internals + +const common = require('../common'); +const { it } = require('node:test'); + +try { + require('trace_events'); +} catch { + common.skip('missing trace events'); +} + +const { createTracing, getEnabledCategories } = require('trace_events'); +const assert = require('assert'); + +const binding = require('internal/test/binding'); +const getCategoryEnabledBuffer = binding.internalBinding('trace_events').getCategoryEnabledBuffer; + +it('should track enabled/disabled categories', () => { + const random = Math.random().toString().slice(2); + const category = `node.${random}`; + + const buffer = getCategoryEnabledBuffer(category); + + const tracing = createTracing({ + categories: [category], + }); + + assert.ok(buffer[0] === 0, `the buffer[0] should start with value 0, got: ${buffer[0]}`); + + tracing.enable(); + + let currentCategories = getEnabledCategories(); + + assert.ok(currentCategories.includes(category), `the getEnabledCategories should include ${category}, got: ${currentCategories}`); + assert.ok(buffer[0] > 0, `the buffer[0] should be greater than 0, got: ${buffer[0]}`); + + tracing.disable(); + + currentCategories = getEnabledCategories(); + assert.ok(currentCategories === undefined, `the getEnabledCategories should return undefined, got: ${currentCategories}`); + assert.ok(buffer[0] === 0, `the buffer[0] should be 0, got: ${buffer[0]}`); +}); From 97da7ca11bb478144b4345445e0aac2139a01c4c Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Tue, 16 Jul 2024 17:05:46 -0400 Subject: [PATCH 003/120] test_runner: consolidate option parsing This commit consolidates all option parsing for the test runner in the parseCommandLine() internal helper function. The exception is a couple of temporary flags used for feature gating which will eventually become no-ops. This consolidation is prep work for supporting running test files in the test runner process. PR-URL: https://github.com/nodejs/node/pull/53849 Reviewed-By: James M Snell Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow --- lib/internal/main/test_runner.js | 58 +++++++--------------------- lib/internal/test_runner/coverage.js | 24 ++++++------ lib/internal/test_runner/utils.js | 41 ++++++++++++++++++++ 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/lib/internal/main/test_runner.js b/lib/internal/main/test_runner.js index 5d4140bef94a62..3f86ddb84bb64e 100644 --- a/lib/internal/main/test_runner.js +++ b/lib/internal/main/test_runner.js @@ -1,26 +1,16 @@ 'use strict'; -const { - NumberParseInt, - RegExpPrototypeExec, - StringPrototypeSplit, -} = primordials; - const { prepareMainThreadExecution, markBootstrapComplete, } = require('internal/process/pre_execution'); -const { getOptionValue } = require('internal/options'); const { isUsingInspector } = require('internal/util/inspector'); const { run } = require('internal/test_runner/runner'); -const { setupTestReporters } = require('internal/test_runner/utils'); -const { exitCodes: { kGenericUserError } } = internalBinding('errors'); const { - codes: { - ERR_INVALID_ARG_VALUE, - }, -} = require('internal/errors'); - + parseCommandLine, + setupTestReporters, +} = require('internal/test_runner/utils'); +const { exitCodes: { kGenericUserError } } = internalBinding('errors'); let debug = require('internal/util/debuglog').debuglog('test_runner', (fn) => { debug = fn; }); @@ -28,7 +18,14 @@ let debug = require('internal/util/debuglog').debuglog('test_runner', (fn) => { prepareMainThreadExecution(false); markBootstrapComplete(); -let concurrency = getOptionValue('--test-concurrency') || true; +const { + perFileTimeout, + runnerConcurrency, + shard, + watchMode, +} = parseCommandLine(); + +let concurrency = runnerConcurrency; let inspectPort; if (isUsingInspector()) { @@ -38,39 +35,12 @@ if (isUsingInspector()) { inspectPort = process.debugPort; } -let shard; -const shardOption = getOptionValue('--test-shard'); -if (shardOption) { - if (!RegExpPrototypeExec(/^\d+\/\d+$/, shardOption)) { - process.exitCode = kGenericUserError; - - throw new ERR_INVALID_ARG_VALUE( - '--test-shard', - shardOption, - 'must be in the form of /', - ); - } - - const { 0: indexStr, 1: totalStr } = StringPrototypeSplit(shardOption, '/'); - - const index = NumberParseInt(indexStr, 10); - const total = NumberParseInt(totalStr, 10); - - shard = { - __proto__: null, - index, - total, - }; -} - -const timeout = getOptionValue('--test-timeout') || Infinity; - const options = { concurrency, inspectPort, - watch: getOptionValue('--watch'), + watch: watchMode, setup: setupTestReporters, - timeout, + timeout: perFileTimeout, shard, }; debug('test runner configuration:', options); diff --git a/lib/internal/test_runner/coverage.js b/lib/internal/test_runner/coverage.js index 1ef13d89285cee..23f479260292e7 100644 --- a/lib/internal/test_runner/coverage.js +++ b/lib/internal/test_runner/coverage.js @@ -25,18 +25,20 @@ const { readFileSync, } = require('fs'); const { setupCoverageHooks } = require('internal/util'); -const { getOptionValue } = require('internal/options'); const { tmpdir } = require('os'); const { join, resolve, relative, matchesGlob } = require('path'); const { fileURLToPath } = require('internal/url'); const { kMappings, SourceMap } = require('internal/source_map/source_map'); +const { parseCommandLine } = require('internal/test_runner/utils'); const kCoverageFileRegex = /^coverage-(\d+)-(\d{13})-(\d+)\.json$/; const kIgnoreRegex = /\/\* node:coverage ignore next (?\d+ )?\*\//; const kLineEndingRegex = /\r?\n$/u; const kLineSplitRegex = /(?<=\r?\n)/u; const kStatusRegex = /\/\* node:coverage (?enable|disable) \*\//; -const excludeFileGlobs = getOptionValue('--test-coverage-exclude'); -const includeFileGlobs = getOptionValue('--test-coverage-include'); +const { + coverageExcludeGlobs, + coverageIncludeGlobs, +} = parseCommandLine(); class CoverageLine { constructor(line, startOffset, src, length = src?.length) { @@ -498,18 +500,18 @@ function shouldSkipFileCoverage(url, workingDirectory) { const relativePath = relative(workingDirectory, absolutePath); // This check filters out files that match the exclude globs. - if (excludeFileGlobs?.length > 0) { - for (let i = 0; i < excludeFileGlobs.length; ++i) { - if (matchesGlob(relativePath, excludeFileGlobs[i]) || - matchesGlob(absolutePath, excludeFileGlobs[i])) return true; + if (coverageExcludeGlobs?.length > 0) { + for (let i = 0; i < coverageExcludeGlobs.length; ++i) { + if (matchesGlob(relativePath, coverageExcludeGlobs[i]) || + matchesGlob(absolutePath, coverageExcludeGlobs[i])) return true; } } // This check filters out files that do not match the include globs. - if (includeFileGlobs?.length > 0) { - for (let i = 0; i < includeFileGlobs.length; ++i) { - if (matchesGlob(relativePath, includeFileGlobs[i]) || - matchesGlob(absolutePath, includeFileGlobs[i])) return false; + if (coverageIncludeGlobs?.length > 0) { + for (let i = 0; i < coverageIncludeGlobs.length; ++i) { + if (matchesGlob(relativePath, coverageIncludeGlobs[i]) || + matchesGlob(absolutePath, coverageIncludeGlobs[i])) return false; } return true; } diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index b8576dbe6673b5..82af14c38e8baa 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -9,6 +9,7 @@ const { MathFloor, MathMax, MathMin, + NumberParseInt, NumberPrototypeToFixed, ObjectGetOwnPropertyDescriptor, RegExp, @@ -19,6 +20,7 @@ const { StringPrototypePadStart, StringPrototypeRepeat, StringPrototypeSlice, + StringPrototypeSplit, } = primordials; const { AsyncResource } = require('async_hooks'); @@ -196,13 +198,19 @@ function parseCommandLine() { const forceExit = getOptionValue('--test-force-exit'); const sourceMaps = getOptionValue('--enable-source-maps'); const updateSnapshots = getOptionValue('--test-update-snapshots'); + const watchMode = getOptionValue('--watch'); const isChildProcess = process.env.NODE_TEST_CONTEXT === 'child'; const isChildProcessV8 = process.env.NODE_TEST_CONTEXT === 'child-v8'; + let coverageExcludeGlobs; + let coverageIncludeGlobs; let destinations; + let perFileTimeout; let reporters; + let runnerConcurrency; let testNamePatterns; let testSkipPatterns; let testOnlyFlag; + let shard; if (isChildProcessV8) { kBuiltinReporters.set('v8-serializer', 'internal/test_runner/reporter/v8-serializer'); @@ -232,9 +240,31 @@ function parseCommandLine() { } if (isTestRunner) { + perFileTimeout = getOptionValue('--test-timeout') || Infinity; + runnerConcurrency = getOptionValue('--test-concurrency') || true; testOnlyFlag = false; testNamePatterns = null; + + const shardOption = getOptionValue('--test-shard'); + if (shardOption) { + if (!RegExpPrototypeExec(/^\d+\/\d+$/, shardOption)) { + throw new ERR_INVALID_ARG_VALUE( + '--test-shard', + shardOption, + 'must be in the form of /', + ); + } + + const indexAndTotal = StringPrototypeSplit(shardOption, '/'); + shard = { + __proto__: null, + index: NumberParseInt(indexAndTotal[0], 10), + total: NumberParseInt(indexAndTotal[1], 10), + }; + } } else { + perFileTimeout = Infinity; + runnerConcurrency = 1; const testNamePatternFlag = getOptionValue('--test-name-pattern'); testOnlyFlag = getOptionValue('--test-only'); testNamePatterns = testNamePatternFlag?.length > 0 ? @@ -247,11 +277,21 @@ function parseCommandLine() { ArrayPrototypeMap(testSkipPatternFlag, (re) => convertStringToRegExp(re, '--test-skip-pattern')) : null; } + if (coverage) { + coverageExcludeGlobs = getOptionValue('--test-coverage-exclude'); + coverageIncludeGlobs = getOptionValue('--test-coverage-include'); + } + globalTestOptions = { __proto__: null, isTestRunner, coverage, + coverageExcludeGlobs, + coverageIncludeGlobs, forceExit, + perFileTimeout, + runnerConcurrency, + shard, sourceMaps, testOnlyFlag, testNamePatterns, @@ -259,6 +299,7 @@ function parseCommandLine() { updateSnapshots, reporters, destinations, + watchMode, }; return globalTestOptions; From bac3a485f6df6c1a0beb156ab78cf9c3d71dbe3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Wed, 17 Jul 2024 00:00:48 +0200 Subject: [PATCH 004/120] src: fix potential segmentation fault in SQLite The Local returned from ColumnToValue() and ColumnNameToValue() may be empty (if a JavaScript exception is pending), in which case a segmentation fault may occur at the call sites, which do not check if the Local is empty. Fix this bug returning early if an exception is pending (as indicated by the Local being empty). In the long term, these functions should return MaybeLocal instead of Local, but this patch is supposed to be a minimal bug fix only. PR-URL: https://github.com/nodejs/node/pull/53850 Reviewed-By: Colin Ihrig Reviewed-By: Yagiz Nizipli --- src/node_sqlite.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index cb7855a2ad1707..1202d2c8cf2464 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -441,7 +441,9 @@ void StatementSync::All(const FunctionCallbackInfo& args) { for (int i = 0; i < num_cols; ++i) { Local key = stmt->ColumnNameToValue(i); + if (key.IsEmpty()) return; Local val = stmt->ColumnToValue(i); + if (val.IsEmpty()) return; if (row->Set(env->context(), key, val).IsNothing()) { return; @@ -483,7 +485,9 @@ void StatementSync::Get(const FunctionCallbackInfo& args) { for (int i = 0; i < num_cols; ++i) { Local key = stmt->ColumnNameToValue(i); + if (key.IsEmpty()) return; Local val = stmt->ColumnToValue(i); + if (val.IsEmpty()) return; if (result->Set(env->context(), key, val).IsNothing()) { return; From 698e44f8e7ddcc3891915ca9827f72f6152b7a25 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Sun, 14 Jul 2024 23:37:10 -0400 Subject: [PATCH 005/120] test_runner: add context.filePath This commit adds a filePath getter to the TestContext and SuiteContext classes. This allows a context to be mapped back to the original test file that created it, even if it was imported from another file. This is useful for mapping features like test snapshots to the correct test file. This is also prep work for supporting running test files in the test runner process. PR-URL: https://github.com/nodejs/node/pull/53853 Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow Reviewed-By: Benjamin Gruenbaum --- doc/api/test.md | 20 +++++++++ lib/internal/test_runner/harness.js | 2 +- lib/internal/test_runner/test.js | 12 ++++- test/parallel/test-runner-test-filepath.js | 52 ++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-runner-test-filepath.js diff --git a/doc/api/test.md b/doc/api/test.md index 57cb8b57ebb8e4..c1f57c2b991db8 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -3194,6 +3194,16 @@ test('top level test', (t) => { }); ``` +### `context.filePath` + + + +The absolute path of the test file that created the current test. If a test file +imports additional modules that generate tests, the imported tests will return +the path of the root test file. + ### `context.fullName` + +The absolute path of the test file that created the current suite. If a test +file imports additional modules that generate suites, the imported suites will +return the path of the root test file. + ### `context.name` -Activate inspector on `host:port`. Default is `127.0.0.1:9229`. +Activate inspector on `host:port`. Default is `127.0.0.1:9229`. If port `0` is +specified, a random available port will be used. V8 inspector integration allows tools such as Chrome DevTools and IDEs to debug and profile Node.js instances. The tools attach to Node.js instances via a @@ -1482,7 +1483,8 @@ added: v7.6.0 --> Activate inspector on `host:port` and break at start of user script. -Default `host:port` is `127.0.0.1:9229`. +Default `host:port` is `127.0.0.1:9229`. If port `0` is specified, +a random available port will be used. See [V8 Inspector integration for Node.js][] for further explanation on Node.js debugger. @@ -1495,7 +1497,8 @@ added: v7.6.0 Set the `host:port` to be used when the inspector is activated. Useful when activating the inspector by sending the `SIGUSR1` signal. -Default host is `127.0.0.1`. +Default host is `127.0.0.1`. If port `0` is specified, +a random available port will be used. See the [security warning][] below regarding the `host` parameter usage. @@ -1514,7 +1517,8 @@ added: v22.2.0 --> Activate inspector on `host:port` and wait for debugger to be attached. -Default `host:port` is `127.0.0.1:9229`. +Default `host:port` is `127.0.0.1:9229`. If port `0` is specified, +a random available port will be used. See [V8 Inspector integration for Node.js][] for further explanation on Node.js debugger. From d8375d623607a43ad689d19f6cda6dfbf09ed445 Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Thu, 18 Jul 2024 22:45:43 +0800 Subject: [PATCH 018/120] lib: decorate async stack trace in source maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decorate stack frame with 'async' and 'new' keywords based on the type of the call site info. PR-URL: https://github.com/nodejs/node/pull/53860 Reviewed-By: James M Snell Reviewed-By: Michaël Zasso Reviewed-By: Benjamin Gruenbaum --- .../source_map/prepare_stack_trace.js | 111 +++++++++++------- .../source_map_throw_async_stack_trace.mjs | 13 ++ ...source_map_throw_async_stack_trace.mjs.map | 1 + .../source_map_throw_async_stack_trace.mts | 22 ++++ ...ource_map_throw_async_stack_trace.snapshot | 11 ++ .../output/source_map_throw_construct.mjs | 12 ++ .../output/source_map_throw_construct.mjs.map | 1 + .../output/source_map_throw_construct.mts | 21 ++++ .../source_map_throw_construct.snapshot | 13 ++ .../source_map_throw_set_immediate.snapshot | 2 +- test/parallel/test-node-output-sourcemaps.mjs | 4 +- 11 files changed, 166 insertions(+), 45 deletions(-) create mode 100644 test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs create mode 100644 test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs.map create mode 100644 test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts create mode 100644 test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot create mode 100644 test/fixtures/source-map/output/source_map_throw_construct.mjs create mode 100644 test/fixtures/source-map/output/source_map_throw_construct.mjs.map create mode 100644 test/fixtures/source-map/output/source_map_throw_construct.mts create mode 100644 test/fixtures/source-map/output/source_map_throw_construct.snapshot diff --git a/lib/internal/source_map/prepare_stack_trace.js b/lib/internal/source_map/prepare_stack_trace.js index 1b12bb89f084ca..60c9d1ed3316ff 100644 --- a/lib/internal/source_map/prepare_stack_trace.js +++ b/lib/internal/source_map/prepare_stack_trace.js @@ -24,6 +24,8 @@ const { const { fileURLToPath } = require('internal/url'); const { setGetSourceMapErrorSource } = internalBinding('errors'); +const kStackLineAt = '\n at '; + // Create a prettified stacktrace, inserting context from source maps // if possible. function prepareStackTraceWithSourceMaps(error, trace) { @@ -40,14 +42,13 @@ function prepareStackTraceWithSourceMaps(error, trace) { let lastSourceMap; let lastFileName; - const preparedTrace = ArrayPrototypeJoin(ArrayPrototypeMap(trace, (t, i) => { - const str = '\n at '; + const preparedTrace = ArrayPrototypeJoin(ArrayPrototypeMap(trace, (callSite, i) => { try { // A stack trace will often have several call sites in a row within the // same file, cache the source map and file content accordingly: - let fileName = t.getFileName(); + let fileName = callSite.getFileName(); if (fileName === undefined) { - fileName = t.getEvalOrigin(); + fileName = callSite.getEvalOrigin(); } const sm = fileName === lastFileName ? lastSourceMap : @@ -55,60 +56,84 @@ function prepareStackTraceWithSourceMaps(error, trace) { lastSourceMap = sm; lastFileName = fileName; if (sm) { - // Source Map V3 lines/columns start at 0/0 whereas stack traces - // start at 1/1: - const { - originalLine, - originalColumn, - originalSource, - } = sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1); - if (originalSource && originalLine !== undefined && - originalColumn !== undefined) { - const name = getOriginalSymbolName(sm, trace, i); - // Construct call site name based on: v8.dev/docs/stack-trace-api: - const fnName = t.getFunctionName() ?? t.getMethodName(); - const typeName = t.getTypeName(); - const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : ''; - const originalName = `${namePrefix}${fnName || ''}`; - // The original call site may have a different symbol name - // associated with it, use it: - const prefix = (name && name !== originalName) ? - `${name}` : - `${originalName}`; - const hasName = !!(name || originalName); - const originalSourceNoScheme = - StringPrototypeStartsWith(originalSource, 'file://') ? - fileURLToPath(originalSource) : originalSource; - // Replace the transpiled call site with the original: - return `${str}${prefix}${hasName ? ' (' : ''}` + - `${originalSourceNoScheme}:${originalLine + 1}:` + - `${originalColumn + 1}${hasName ? ')' : ''}`; - } + return `${kStackLineAt}${serializeJSStackFrame(sm, callSite, trace[i + 1])}`; } } catch (err) { debug(err); } - return `${str}${t}`; + return `${kStackLineAt}${callSite}`; }), ''); return `${errorString}${preparedTrace}`; } +/** + * Serialize a single call site in the stack trace. + * Refer to SerializeJSStackFrame in deps/v8/src/objects/call-site-info.cc for + * more details about the default ToString(CallSite). + * The CallSite API is documented at https://v8.dev/docs/stack-trace-api. + * @param {import('internal/source_map/source_map').SourceMap} sm + * @param {CallSite} callSite - the CallSite object to be serialized + * @param {CallSite} callerCallSite - caller site info + * @returns {string} - the serialized call site + */ +function serializeJSStackFrame(sm, callSite, callerCallSite) { + // Source Map V3 lines/columns start at 0/0 whereas stack traces + // start at 1/1: + const { + originalLine, + originalColumn, + originalSource, + } = sm.findEntry(callSite.getLineNumber() - 1, callSite.getColumnNumber() - 1); + if (originalSource === undefined || originalLine === undefined || + originalColumn === undefined) { + return `${callSite}`; + } + const name = getOriginalSymbolName(sm, callSite, callerCallSite); + const originalSourceNoScheme = + StringPrototypeStartsWith(originalSource, 'file://') ? + fileURLToPath(originalSource) : originalSource; + // Construct call site name based on: v8.dev/docs/stack-trace-api: + const fnName = callSite.getFunctionName() ?? callSite.getMethodName(); + + let prefix = ''; + if (callSite.isAsync()) { + // Promise aggregation operation frame has no locations. This must be an + // async stack frame. + prefix = 'async '; + } else if (callSite.isConstructor()) { + prefix = 'new '; + } + + const typeName = callSite.getTypeName(); + const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : ''; + const originalName = `${namePrefix}${fnName || ''}`; + // The original call site may have a different symbol name + // associated with it, use it: + const mappedName = (name && name !== originalName) ? + `${name}` : + `${originalName}`; + const hasName = !!(name || originalName); + // Replace the transpiled call site with the original: + return `${prefix}${mappedName}${hasName ? ' (' : ''}` + + `${originalSourceNoScheme}:${originalLine + 1}:` + + `${originalColumn + 1}${hasName ? ')' : ''}`; +} + // Transpilers may have removed the original symbol name used in the stack // trace, if possible restore it from the names field of the source map: -function getOriginalSymbolName(sourceMap, trace, curIndex) { +function getOriginalSymbolName(sourceMap, callSite, callerCallSite) { // First check for a symbol name associated with the enclosing function: const enclosingEntry = sourceMap.findEntry( - trace[curIndex].getEnclosingLineNumber() - 1, - trace[curIndex].getEnclosingColumnNumber() - 1, + callSite.getEnclosingLineNumber() - 1, + callSite.getEnclosingColumnNumber() - 1, ); if (enclosingEntry.name) return enclosingEntry.name; - // Fallback to using the symbol name attached to the next stack frame: - const currentFileName = trace[curIndex].getFileName(); - const nextCallSite = trace[curIndex + 1]; - if (nextCallSite && currentFileName === nextCallSite.getFileName()) { + // Fallback to using the symbol name attached to the caller site: + const currentFileName = callSite.getFileName(); + if (callerCallSite && currentFileName === callerCallSite.getFileName()) { const { name } = sourceMap.findEntry( - nextCallSite.getLineNumber() - 1, - nextCallSite.getColumnNumber() - 1, + callerCallSite.getLineNumber() - 1, + callerCallSite.getColumnNumber() - 1, ); return name; } diff --git a/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs new file mode 100644 index 00000000000000..8e3fefbebe4d5d --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs @@ -0,0 +1,13 @@ +// Flags: --enable-source-maps +import '../../../common/index.mjs'; +async function Throw() { + await 0; + throw new Error('message'); +} +(async function main() { + await Promise.all([0, 1, 2, Throw()]); +})(); +// To recreate: +// +// npx --package typescript tsc --module nodenext --target esnext --outDir test/fixtures/source-map/output --sourceMap test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts +//# sourceMappingURL=source_map_throw_async_stack_trace.mjs.map \ No newline at end of file diff --git a/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs.map b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs.map new file mode 100644 index 00000000000000..728e8c20291a9b --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"source_map_throw_async_stack_trace.mjs","sourceRoot":"","sources":["source_map_throw_async_stack_trace.mts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,OAAO,2BAA2B,CAAC;AAQnC,KAAK,UAAU,KAAK;IAClB,MAAM,CAAC,CAAC;IACR,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAA;AAC5B,CAAC;AAED,CAAC,KAAK,UAAU,IAAI;IAClB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC,CAAC,EAAE,CAAA;AAEJ,eAAe;AACf,EAAE;AACF,6LAA6L"} \ No newline at end of file diff --git a/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts new file mode 100644 index 00000000000000..718f617928d5ce --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts @@ -0,0 +1,22 @@ +// Flags: --enable-source-maps + +import '../../../common/index.mjs'; + +interface Foo { + /** line + * + * blocks */ +} + +async function Throw() { + await 0; + throw new Error('message') +} + +(async function main() { + await Promise.all([0, 1, 2, Throw()]); +})() + +// To recreate: +// +// npx --package typescript tsc --module nodenext --target esnext --outDir test/fixtures/source-map/output --sourceMap test/fixtures/source-map/output/source_map_throw_async_stack_trace.mts diff --git a/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot new file mode 100644 index 00000000000000..8f7f0490587585 --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_async_stack_trace.snapshot @@ -0,0 +1,11 @@ +*output*source_map_throw_async_stack_trace.mts:13 + throw new Error('message') + ^ + + +Error: message + at Throw (*output*source_map_throw_async_stack_trace.mts:13:9) + at async Promise.all (index 3) + at async main (*output*source_map_throw_async_stack_trace.mts:17:3) + +Node.js * diff --git a/test/fixtures/source-map/output/source_map_throw_construct.mjs b/test/fixtures/source-map/output/source_map_throw_construct.mjs new file mode 100644 index 00000000000000..24361da883da7c --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_construct.mjs @@ -0,0 +1,12 @@ +// Flags: --enable-source-maps +import '../../../common/index.mjs'; +class Foo { + constructor() { + throw new Error('message'); + } +} +new Foo(); +// To recreate: +// +// npx --package typescript tsc --module nodenext --target esnext --outDir test/fixtures/source-map/output --sourceMap test/fixtures/source-map/output/source_map_throw_construct.mts +//# sourceMappingURL=source_map_throw_construct.mjs.map \ No newline at end of file diff --git a/test/fixtures/source-map/output/source_map_throw_construct.mjs.map b/test/fixtures/source-map/output/source_map_throw_construct.mjs.map new file mode 100644 index 00000000000000..2bf39629caf627 --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_construct.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"source_map_throw_construct.mjs","sourceRoot":"","sources":["source_map_throw_construct.mts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,OAAO,2BAA2B,CAAC;AAQnC,MAAM,GAAG;IACP;QACE,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC;CACF;AAED,IAAI,GAAG,EAAE,CAAC;AAEV,eAAe;AACf,EAAE;AACF,qLAAqL"} \ No newline at end of file diff --git a/test/fixtures/source-map/output/source_map_throw_construct.mts b/test/fixtures/source-map/output/source_map_throw_construct.mts new file mode 100644 index 00000000000000..38f2dee88a652e --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_construct.mts @@ -0,0 +1,21 @@ +// Flags: --enable-source-maps + +import '../../../common/index.mjs'; + +interface Block { + /** line + * + * blocks */ +} + +class Foo { + constructor() { + throw new Error('message'); + } +} + +new Foo(); + +// To recreate: +// +// npx --package typescript tsc --module nodenext --target esnext --outDir test/fixtures/source-map/output --sourceMap test/fixtures/source-map/output/source_map_throw_construct.mts diff --git a/test/fixtures/source-map/output/source_map_throw_construct.snapshot b/test/fixtures/source-map/output/source_map_throw_construct.snapshot new file mode 100644 index 00000000000000..8618d4b51a46ec --- /dev/null +++ b/test/fixtures/source-map/output/source_map_throw_construct.snapshot @@ -0,0 +1,13 @@ +*output*source_map_throw_construct.mts:13 + throw new Error('message'); + ^ + + +Error: message + at new Foo (*output*source_map_throw_construct.mts:13:11) + at (*output*source_map_throw_construct.mts:17:1) + * + * + * + +Node.js * diff --git a/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot b/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot index d2d838ca35a3de..ec9f1346ca5e0c 100644 --- a/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot +++ b/test/fixtures/source-map/output/source_map_throw_set_immediate.snapshot @@ -6,6 +6,6 @@ Error: goodbye at Hello (*uglify-throw-original.js:5:9) at Immediate. (*uglify-throw-original.js:9:3) - at process.processImmediate (node:internal*timers:483:21) + * Node.js * diff --git a/test/parallel/test-node-output-sourcemaps.mjs b/test/parallel/test-node-output-sourcemaps.mjs index d82f4a249cd1d9..e9104db220867f 100644 --- a/test/parallel/test-node-output-sourcemaps.mjs +++ b/test/parallel/test-node-output-sourcemaps.mjs @@ -13,7 +13,7 @@ describe('sourcemaps output', { concurrency: !process.env.TEST_PARALLEL }, () => .replaceAll(/\/(\w)/g, '*$1') .replaceAll('*test*', '*') .replaceAll('*fixtures*source-map*', '*') - .replaceAll(/(\W+).*node:internal\*modules.*/g, '$1*'); + .replaceAll(/(\W+).*node:.*/g, '$1*'); if (common.isWindows) { const currentDeviceLetter = path.parse(process.cwd()).root.substring(0, 1).toLowerCase(); const regex = new RegExp(`${currentDeviceLetter}:/?`, 'gi'); @@ -34,7 +34,9 @@ describe('sourcemaps output', { concurrency: !process.env.TEST_PARALLEL }, () => { name: 'source-map/output/source_map_prepare_stack_trace.js' }, { name: 'source-map/output/source_map_reference_error_tabs.js' }, { name: 'source-map/output/source_map_sourcemapping_url_string.js' }, + { name: 'source-map/output/source_map_throw_async_stack_trace.mjs' }, { name: 'source-map/output/source_map_throw_catch.js' }, + { name: 'source-map/output/source_map_throw_construct.mjs' }, { name: 'source-map/output/source_map_throw_first_tick.js' }, { name: 'source-map/output/source_map_throw_icu.js' }, { name: 'source-map/output/source_map_throw_set_immediate.js' }, From dcca9ba560db1a9b6c5ab52917c51e1de9c3f770 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Thu, 18 Jul 2024 19:57:40 +0200 Subject: [PATCH 019/120] esm: refactor `get_format` PR-URL: https://github.com/nodejs/node/pull/53872 Reviewed-By: Geoffrey Booth Reviewed-By: Paolo Insogna Reviewed-By: Matteo Collina --- lib/internal/modules/esm/get_format.js | 58 +++++++++++++++----------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index cd5c88dce8e021..c6bc030f8b2b31 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -17,6 +17,7 @@ const { mimeToFormat, } = require('internal/modules/esm/formats'); +const detectModule = getOptionValue('--experimental-detect-module'); const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const { containsModuleSyntax } = internalBinding('contextify'); @@ -33,6 +34,17 @@ const protocolHandlers = { 'node:'() { return 'builtin'; }, }; +/** + * Determine whether the given ambiguous source contains CommonJS or ES module syntax. + * @param {string | Buffer | undefined} source + * @param {URL} url + */ +function detectModuleFormat(source, url) { + if (!source) { return detectModule ? null : 'commonjs'; } + if (!detectModule) { return 'commonjs'; } + return containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs'; +} + /** * @param {URL} parsed * @returns {string | null} @@ -112,26 +124,23 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE default: { // The user did not pass `--experimental-default-type`. // `source` is undefined when this is called from `defaultResolve`; // but this gets called again from `defaultLoad`/`defaultLoadSync`. - if (getOptionValue('--experimental-detect-module')) { - const format = source ? - (containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs') : - null; - if (format === 'module') { - // This module has a .js extension, a package.json with no `type` field, and ESM syntax. - // Warn about the missing `type` field so that the user can avoid the performance penalty of detection. - typelessPackageJsonFilesWarnedAbout ??= new SafeSet(); - if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { - const warning = `${url} parsed as an ES module because module syntax was detected;` + - ` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`; - process.emitWarning(warning, { - code: 'MODULE_TYPELESS_PACKAGE_JSON', - }); - typelessPackageJsonFilesWarnedAbout.add(pjsonPath); - } + // For ambiguous files (no type field, .js extension) we return + // undefined from `resolve` and re-run the check in `load`. + const format = detectModuleFormat(source, url); + if (format === 'module') { + // This module has a .js extension, a package.json with no `type` field, and ESM syntax. + // Warn about the missing `type` field so that the user can avoid the performance penalty of detection. + typelessPackageJsonFilesWarnedAbout ??= new SafeSet(); + if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { + const warning = `${url} parsed as an ES module because module syntax was detected;` + + ` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`; + process.emitWarning(warning, { + code: 'MODULE_TYPELESS_PACKAGE_JSON', + }); + typelessPackageJsonFilesWarnedAbout.add(pjsonPath); } - return format; } - return 'commonjs'; + return format; } } } @@ -154,15 +163,14 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE return 'commonjs'; } default: { // The user did not pass `--experimental-default-type`. - if (getOptionValue('--experimental-detect-module')) { - if (!source) { return null; } - const format = getFormatOfExtensionlessFile(url); - if (format === 'module') { - return containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs'; - } + if (!source) { + return null; + } + const format = getFormatOfExtensionlessFile(url); + if (format === 'wasm') { return format; } - return 'commonjs'; + return detectModuleFormat(source, url); } } } From 55461be05f5420ce5e0338287fe38963aa7dcc3a Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Thu, 18 Jul 2024 16:41:49 -0400 Subject: [PATCH 020/120] src: refactor webstorage implementation PR-URL: https://github.com/nodejs/node/pull/53876 Reviewed-By: Colin Ihrig Reviewed-By: James M Snell --- src/node_webstorage.cc | 69 ++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/node_webstorage.cc b/src/node_webstorage.cc index 798a07c5b7a5b6..6c3e19db1dbffc 100644 --- a/src/node_webstorage.cc +++ b/src/node_webstorage.cc @@ -29,7 +29,6 @@ using v8::Maybe; using v8::MaybeLocal; using v8::Name; using v8::NamedPropertyHandlerConfiguration; -using v8::Null; using v8::Object; using v8::PropertyAttribute; using v8::PropertyCallbackInfo; @@ -40,7 +39,7 @@ using v8::Uint32; using v8::Value; #define THROW_SQLITE_ERROR(env, r) \ - node::THROW_ERR_INVALID_STATE((env), sqlite3_errstr((r))) + THROW_ERR_INVALID_STATE((env), sqlite3_errstr((r))) #define CHECK_ERROR_OR_THROW(env, expr, expected, ret) \ do { \ @@ -80,7 +79,7 @@ static void ThrowQuotaExceededException(Local context) { Storage::Storage(Environment* env, Local object, Local location) : BaseObject(env, object) { MakeWeak(); - node::Utf8Value utf8_location(env->isolate(), location); + Utf8Value utf8_location(env->isolate(), location); symbols_.Reset(env->isolate(), Map::New(env->isolate())); db_ = nullptr; location_ = utf8_location.ToString(); @@ -97,9 +96,9 @@ void Storage::MemoryInfo(MemoryTracker* tracker) const { bool Storage::Open() { static const int kCurrentSchemaVersion = 1; - static const char get_schema_version_sql[] = + static constexpr std::string_view get_schema_version_sql = "SELECT schema_version FROM nodejs_webstorage_state"; - static const char init_sql_v0[] = + static constexpr std::string_view init_sql_v0 = "PRAGMA encoding = 'UTF-16le';" "PRAGMA busy_timeout = 3000;" "PRAGMA journal_mode = WAL;" @@ -165,13 +164,14 @@ bool Storage::Open() { int r = sqlite3_open(location_.c_str(), &db); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false); - r = sqlite3_exec(db, init_sql_v0, 0, 0, nullptr); + r = sqlite3_exec(db, init_sql_v0.data(), 0, 0, nullptr); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false); // Get the current schema version, used to determine schema migrations. sqlite3_stmt* s = nullptr; - r = sqlite3_prepare_v2(db, get_schema_version_sql, -1, &s, 0); - r = sqlite3_exec(db, init_sql_v0, 0, 0, nullptr); + r = sqlite3_prepare_v2( + db, get_schema_version_sql.data(), get_schema_version_sql.size(), &s, 0); + r = sqlite3_exec(db, init_sql_v0.data(), 0, 0, nullptr); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false); auto stmt = stmt_unique_ptr(s); CHECK_ERROR_OR_THROW(env(), sqlite3_step(stmt.get()), SQLITE_ROW, false); @@ -180,7 +180,7 @@ bool Storage::Open() { stmt = nullptr; // Force finalization. if (schema_version > kCurrentSchemaVersion) { - node::THROW_ERR_INVALID_STATE( + THROW_ERR_INVALID_STATE( env(), "localStorage was created with a newer version of Node.js"); return false; } @@ -217,10 +217,13 @@ void Storage::Clear() { return; } - static const char sql[] = "DELETE FROM nodejs_webstorage"; + static constexpr std::string_view sql = "DELETE FROM nodejs_webstorage"; sqlite3_stmt* s = nullptr; CHECK_ERROR_OR_THROW( - env(), sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0), SQLITE_OK, void()); + env(), + sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0), + SQLITE_OK, + void()); auto stmt = stmt_unique_ptr(s); CHECK_ERROR_OR_THROW(env(), sqlite3_step(stmt.get()), SQLITE_DONE, void()); } @@ -230,9 +233,9 @@ Local Storage::Enumerate() { return Local(); } - static const char sql[] = "SELECT key FROM nodejs_webstorage"; + static constexpr std::string_view sql = "SELECT key FROM nodejs_webstorage"; sqlite3_stmt* s = nullptr; - int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0); + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local()); auto stmt = stmt_unique_ptr(s); std::vector> values; @@ -253,12 +256,13 @@ Local Storage::Enumerate() { Local Storage::Length() { if (!Open()) { - return Local(); + return {}; } - static const char sql[] = "SELECT count(*) FROM nodejs_webstorage"; + static constexpr std::string_view sql = + "SELECT count(*) FROM nodejs_webstorage"; sqlite3_stmt* s = nullptr; - int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0); + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local()); auto stmt = stmt_unique_ptr(s); CHECK_ERROR_OR_THROW( @@ -276,16 +280,16 @@ Local Storage::Load(Local key) { } if (!Open()) { - return Local(); + return {}; } - static const char sql[] = + static constexpr std::string_view sql = "SELECT value FROM nodejs_webstorage WHERE key = ? LIMIT 1"; sqlite3_stmt* s = nullptr; - int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0); + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local()); auto stmt = stmt_unique_ptr(s); - node::TwoByteValue utf16key(env()->isolate(), key); + TwoByteValue utf16key(env()->isolate(), key); auto key_size = utf16key.length() * sizeof(uint16_t); r = sqlite3_bind_blob(stmt.get(), 1, utf16key.out(), key_size, SQLITE_STATIC); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local()); @@ -312,10 +316,10 @@ Local Storage::LoadKey(const int index) { return Local(); } - static const char sql[] = + static constexpr std::string_view sql = "SELECT key FROM nodejs_webstorage LIMIT 1 OFFSET ?"; sqlite3_stmt* s = nullptr; - int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0); + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local()); auto stmt = stmt_unique_ptr(s); r = sqlite3_bind_int(stmt.get(), 1, index); @@ -350,12 +354,13 @@ bool Storage::Remove(Local key) { return false; } - static const char sql[] = "DELETE FROM nodejs_webstorage WHERE key = ?"; + static constexpr std::string_view sql = + "DELETE FROM nodejs_webstorage WHERE key = ?"; sqlite3_stmt* s = nullptr; - int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0); + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false); auto stmt = stmt_unique_ptr(s); - node::TwoByteValue utf16key(env()->isolate(), key); + TwoByteValue utf16key(env()->isolate(), key); auto key_size = utf16key.length() * sizeof(uint16_t); r = sqlite3_bind_blob(stmt.get(), 1, utf16key.out(), key_size, SQLITE_STATIC); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false); @@ -379,14 +384,14 @@ bool Storage::Store(Local key, Local value) { return false; } - static const char sql[] = + static constexpr std::string_view sql = "INSERT INTO nodejs_webstorage (key, value) VALUES (?, ?)" " ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value" " WHERE EXCLUDED.key = key"; sqlite3_stmt* s = nullptr; - node::TwoByteValue utf16key(env()->isolate(), key); - node::TwoByteValue utf16val(env()->isolate(), val); - int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0); + TwoByteValue utf16key(env()->isolate(), key); + TwoByteValue utf16val(env()->isolate(), val); + int r = sqlite3_prepare_v2(db_.get(), sql.data(), sql.size(), &s, 0); CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false); auto stmt = stmt_unique_ptr(s); auto key_size = utf16key.length() * sizeof(uint16_t); @@ -435,7 +440,7 @@ static void GetItem(const FunctionCallbackInfo& info) { Local result = storage->Load(prop); if (result.IsEmpty()) { - info.GetReturnValue().Set(Null(env->isolate())); + info.GetReturnValue().SetNull(); } else { info.GetReturnValue().Set(result); } @@ -457,13 +462,13 @@ static void Key(const FunctionCallbackInfo& info) { } if (index < 0) { - info.GetReturnValue().Set(Null(env->isolate())); + info.GetReturnValue().SetNull(); return; } Local result = storage->LoadKey(index); if (result.IsEmpty()) { - info.GetReturnValue().Set(Null(env->isolate())); + info.GetReturnValue().SetNull(); } else { info.GetReturnValue().Set(result); } From a94c3ae06f7bc7e1155f0a7d08c56574ead4a7cd Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 16 Jul 2024 06:56:15 -0700 Subject: [PATCH 021/120] src: replace ToLocalChecked uses with ToLocal in node-file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/53869 Reviewed-By: Yagiz Nizipli Reviewed-By: Juan José Arboleda Reviewed-By: Gerhard Stöbich Reviewed-By: Tobias Nießen Reviewed-By: Anna Henningsen --- src/node_file-inl.h | 32 +++++++++++++++++++++++--------- src/node_file.cc | 33 ++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/node_file-inl.h b/src/node_file-inl.h index 6c059add3bfc02..36c2f8067c6e49 100644 --- a/src/node_file-inl.h +++ b/src/node_file-inl.h @@ -221,9 +221,15 @@ void FSReqPromise::Reject(v8::Local reject) { finished_ = true; v8::HandleScope scope(env()->isolate()); InternalCallbackScope callback_scope(this); - v8::Local value = - object()->Get(env()->context(), - env()->promise_string()).ToLocalChecked(); + v8::Local value; + if (!object() + ->Get(env()->context(), env()->promise_string()) + .ToLocal(&value)) { + // If we hit this, getting the value from the object failed and + // an error was likely scheduled. We could try to reject the promise + // but let's just allow the error to propagate. + return; + } v8::Local resolver = value.As(); USE(resolver->Reject(env()->context(), reject).FromJust()); } @@ -233,9 +239,13 @@ void FSReqPromise::Resolve(v8::Local value) { finished_ = true; v8::HandleScope scope(env()->isolate()); InternalCallbackScope callback_scope(this); - v8::Local val = - object()->Get(env()->context(), - env()->promise_string()).ToLocalChecked(); + v8::Local val; + if (!object()->Get(env()->context(), env()->promise_string()).ToLocal(&val)) { + // If we hit this, getting the value from the object failed and + // an error was likely scheduled. We could try to reject the promise + // but let's just allow the error to propagate. + return; + } v8::Local resolver = val.As(); USE(resolver->Resolve(env()->context(), value).FromJust()); } @@ -255,9 +265,13 @@ void FSReqPromise::ResolveStatFs(const uv_statfs_t* stat) { template void FSReqPromise::SetReturnValue( const v8::FunctionCallbackInfo& args) { - v8::Local val = - object()->Get(env()->context(), - env()->promise_string()).ToLocalChecked(); + v8::Local val; + if (!object()->Get(env()->context(), env()->promise_string()).ToLocal(&val)) { + // If we hit this, getting the value from the object failed and + // an error was likely scheduled. We could try to reject the promise + // but let's just allow the error to propagate. + return; + } v8::Local resolver = val.As(); args.GetReturnValue().Set(resolver->GetPromise()); } diff --git a/src/node_file.cc b/src/node_file.cc index c59235b51cca9f..150935e05a0276 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -437,7 +437,8 @@ MaybeLocal FileHandle::ClosePromise() { auto maybe_resolver = Promise::Resolver::New(context); CHECK(!maybe_resolver.IsEmpty()); - Local resolver = maybe_resolver.ToLocalChecked(); + Local resolver; + if (!maybe_resolver.ToLocal(&resolver)) return {}; Local promise = resolver.As(); Local close_req_obj; @@ -844,10 +845,12 @@ void AfterStringPath(uv_fs_t* req) { req->path, req_wrap->encoding(), &error); - if (link.IsEmpty()) + if (link.IsEmpty()) { req_wrap->Reject(error); - else - req_wrap->Resolve(link.ToLocalChecked()); + } else { + Local val; + if (link.ToLocal(&val)) req_wrap->Resolve(val); + } } } @@ -864,10 +867,12 @@ void AfterStringPtr(uv_fs_t* req) { static_cast(req->ptr), req_wrap->encoding(), &error); - if (link.IsEmpty()) + if (link.IsEmpty()) { req_wrap->Reject(error); - else - req_wrap->Resolve(link.ToLocalChecked()); + } else { + Local val; + if (link.ToLocal(&val)) req_wrap->Resolve(val); + } } } @@ -2237,7 +2242,8 @@ static void WriteBuffers(const FunctionCallbackInfo& args) { MaybeStackBuffer iovs(chunks->Length()); for (uint32_t i = 0; i < iovs.length(); i++) { - Local chunk = chunks->Get(env->context(), i).ToLocalChecked(); + Local chunk; + if (!chunks->Get(env->context(), i).ToLocal(&chunk)) return; CHECK(Buffer::HasInstance(chunk)); iovs[i] = uv_buf_init(Buffer::Data(chunk), Buffer::Length(chunk)); } @@ -2577,8 +2583,12 @@ static void ReadFileUtf8(const FunctionCallbackInfo& args) { } FS_SYNC_TRACE_END(read); - args.GetReturnValue().Set( - ToV8Value(env->context(), result, isolate).ToLocalChecked()); + Local val; + if (!ToV8Value(env->context(), result, isolate).ToLocal(&val)) { + return; + } + + args.GetReturnValue().Set(val); } // Wrapper for readv(2). @@ -2606,7 +2616,8 @@ static void ReadBuffers(const FunctionCallbackInfo& args) { // Init uv buffers from ArrayBufferViews for (uint32_t i = 0; i < iovs.length(); i++) { - Local buffer = buffers->Get(env->context(), i).ToLocalChecked(); + Local buffer; + if (!buffers->Get(env->context(), i).ToLocal(&buffer)) return; CHECK(Buffer::HasInstance(buffer)); iovs[i] = uv_buf_init(Buffer::Data(buffer), Buffer::Length(buffer)); } From de1fbc292fb817f25ed691e1286c17c8d7848207 Mon Sep 17 00:00:00 2001 From: Kohei Ueno Date: Fri, 19 Jul 2024 14:00:30 +0900 Subject: [PATCH 022/120] inspector: add initial support for network inspection PR-URL: https://github.com/nodejs/node/pull/53593 Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell Reviewed-By: Matteo Collina Reviewed-By: Stephen Belanger Reviewed-By: Joyee Cheung Reviewed-By: Chengzhong Wu Reviewed-By: Paolo Insogna --- doc/api/cli.md | 11 ++ doc/api/inspector.md | 69 ++++++++++ lib/inspector.js | 16 +++ lib/internal/inspector_network_tracking.js | 63 +++++++++ lib/internal/process/pre_execution.js | 11 ++ src/env_properties.h | 2 + src/inspector/network_agent.cc | 84 ++++++++++++ src/inspector/network_agent.h | 49 +++++++ src/inspector/network_inspector.cc | 48 +++++++ src/inspector/network_inspector.h | 38 ++++++ src/inspector/node_inspector.gypi | 6 + src/inspector/node_protocol.pdl | 51 ++++++++ src/inspector/node_string.cc | 2 +- src/inspector_agent.cc | 92 +++++++++++++- src/inspector_agent.h | 13 ++ src/inspector_js_api.cc | 28 ++++ src/node_options.cc | 3 + src/node_options.h | 1 + .../test-inspector-emit-protocol-event.js | 85 +++++++++++++ .../parallel/test-inspector-network-domain.js | 120 ++++++++++++++++++ 20 files changed, 789 insertions(+), 3 deletions(-) create mode 100644 lib/internal/inspector_network_tracking.js create mode 100644 src/inspector/network_agent.cc create mode 100644 src/inspector/network_agent.h create mode 100644 src/inspector/network_inspector.cc create mode 100644 src/inspector/network_inspector.h create mode 100644 test/parallel/test-inspector-emit-protocol-event.js create mode 100644 test/parallel/test-inspector-network-domain.js diff --git a/doc/api/cli.md b/doc/api/cli.md index 651ac02467ae45..320ad45b50aa58 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1028,6 +1028,17 @@ added: Enable experimental support for the `https:` protocol in `import` specifiers. +### `--experimental-network-inspection` + + + +> Stability: 1 - Experimental + +Enable experimental support for the network inspection with Chrome DevTools. + ### `--experimental-permission` + +> Stability: 1 - Experimental + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.requestWillBeSent` event to connected frontends. This event indicates that +the application is about to send an HTTP request. + +### `inspector.Network.responseReceived([params])` + + + +> Stability: 1 - Experimental + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.responseReceived` event to connected frontends. This event indicates that +HTTP response is available. + +### `inspector.Network.loadingFinished([params])` + + + +> Stability: 1 - Experimental + +* `params` {Object} + +This feature is only available with the `--experimental-network-inspection` flag enabled. + +Broadcasts the `Network.loadingFinished` event to connected frontends. This event indicates that +HTTP request has finished loading. + ## Support of breakpoints The Chrome DevTools Protocol [`Debugger` domain][] allows an diff --git a/lib/inspector.js b/lib/inspector.js index e51bcf2f3cd977..b38bb1af974819 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -42,6 +42,7 @@ const { isEnabled, waitForDebugger, console, + emitProtocolEvent, } = internalBinding('inspector'); class Session extends EventEmitter { @@ -188,6 +189,20 @@ function inspectorWaitForDebugger() { throw new ERR_INSPECTOR_NOT_ACTIVE(); } +function broadcastToFrontend(eventName, params) { + validateString(eventName, 'eventName'); + if (params) { + validateObject(params, 'params'); + } + emitProtocolEvent(eventName, JSONStringify(params ?? {})); +} + +const Network = { + requestWillBeSent: (params) => broadcastToFrontend('Network.requestWillBeSent', params), + responseReceived: (params) => broadcastToFrontend('Network.responseReceived', params), + loadingFinished: (params) => broadcastToFrontend('Network.loadingFinished', params), +}; + module.exports = { open: inspectorOpen, close: _debugEnd, @@ -195,4 +210,5 @@ module.exports = { waitForDebugger: inspectorWaitForDebugger, console, Session, + Network, }; diff --git a/lib/internal/inspector_network_tracking.js b/lib/internal/inspector_network_tracking.js new file mode 100644 index 00000000000000..4865537e37b7d5 --- /dev/null +++ b/lib/internal/inspector_network_tracking.js @@ -0,0 +1,63 @@ +'use strict'; + +const { + DateNow, +} = primordials; + +let dc; +let Network; + +let requestId = 0; +const getNextRequestId = () => `node-network-event-${++requestId}`; + +function onClientRequestStart({ request }) { + const url = `${request.protocol}//${request.host}${request.path}`; + const wallTime = DateNow(); + const timestamp = wallTime / 1000; + request._inspectorRequestId = getNextRequestId(); + Network.requestWillBeSent({ + requestId: request._inspectorRequestId, + timestamp, + wallTime, + request: { + url, + method: request.method, + }, + }); +} + +function onClientResponseFinish({ request }) { + if (typeof request._inspectorRequestId !== 'string') { + return; + } + const timestamp = DateNow() / 1000; + Network.responseReceived({ + requestId: request._inspectorRequestId, + timestamp, + }); + Network.loadingFinished({ + requestId: request._inspectorRequestId, + timestamp, + }); +} + +function enable() { + if (!dc) { + dc = require('diagnostics_channel'); + } + if (!Network) { + Network = require('inspector').Network; + } + dc.subscribe('http.client.request.start', onClientRequestStart); + dc.subscribe('http.client.response.finish', onClientResponseFinish); +} + +function disable() { + dc.unsubscribe('http.client.request.start', onClientRequestStart); + dc.unsubscribe('http.client.response.finish', onClientResponseFinish); +} + +module.exports = { + enable, + disable, +}; diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 383dee4e6988e4..080e6d55bb2a29 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -106,6 +106,7 @@ function prepareExecution(options) { const mainEntry = patchProcessObject(expandArgv1); setupTraceCategoryState(); setupInspectorHooks(); + setupNetworkInspection(); setupNavigator(); setupWarningHandler(); setupUndici(); @@ -513,6 +514,16 @@ function setupInspectorHooks() { } } +function setupNetworkInspection() { + if (internalBinding('config').hasInspector && getOptionValue('--experimental-network-inspection')) { + const { + enable, + disable, + } = require('internal/inspector_network_tracking'); + internalBinding('inspector').setupNetworkTracking(enable, disable); + } +} + // In general deprecations are initialized wherever the APIs are implemented, // this is used to deprecate APIs implemented in C++ where the deprecation // utilities are not easily accessible. diff --git a/src/env_properties.h b/src/env_properties.h index c6262a79504e61..1ed8ab4d116313 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -448,7 +448,9 @@ V(immediate_callback_function, v8::Function) \ V(inspector_console_extension_installer, v8::Function) \ V(inspector_disable_async_hooks, v8::Function) \ + V(inspector_disable_network_tracking, v8::Function) \ V(inspector_enable_async_hooks, v8::Function) \ + V(inspector_enable_network_tracking, v8::Function) \ V(maybe_cache_generated_source_map, v8::Function) \ V(messaging_deserialize_create_object, v8::Function) \ V(message_port, v8::Object) \ diff --git a/src/inspector/network_agent.cc b/src/inspector/network_agent.cc new file mode 100644 index 00000000000000..de17ff0ecb2041 --- /dev/null +++ b/src/inspector/network_agent.cc @@ -0,0 +1,84 @@ +#include "network_agent.h" +#include "network_inspector.h" + +namespace node { +namespace inspector { +namespace protocol { + +std::unique_ptr Request(const String& url, + const String& method) { + return Network::Request::create().setUrl(url).setMethod(method).build(); +} + +NetworkAgent::NetworkAgent(NetworkInspector* inspector) + : inspector_(inspector) { + event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent; + event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived; + event_notifier_map_["loadingFinished"] = &NetworkAgent::loadingFinished; +} + +void NetworkAgent::emitNotification( + const String& event, std::unique_ptr params) { + if (!inspector_->IsEnabled()) return; + auto it = event_notifier_map_.find(event); + if (it != event_notifier_map_.end()) { + (this->*(it->second))(std::move(params)); + } +} + +void NetworkAgent::Wire(UberDispatcher* dispatcher) { + frontend_ = std::make_unique(dispatcher->channel()); + Network::Dispatcher::wire(dispatcher, this); +} + +DispatchResponse NetworkAgent::enable() { + inspector_->Enable(); + return DispatchResponse::OK(); +} + +DispatchResponse NetworkAgent::disable() { + inspector_->Disable(); + return DispatchResponse::OK(); +} + +void NetworkAgent::requestWillBeSent( + std::unique_ptr params) { + String request_id; + params->getString("requestId", &request_id); + double timestamp; + params->getDouble("timestamp", ×tamp); + double wall_time; + params->getDouble("wallTime", &wall_time); + auto request = params->getObject("request"); + String url; + request->getString("url", &url); + String method; + request->getString("method", &method); + + frontend_->requestWillBeSent( + request_id, Request(url, method), timestamp, wall_time); +} + +void NetworkAgent::responseReceived( + std::unique_ptr params) { + String request_id; + params->getString("requestId", &request_id); + double timestamp; + params->getDouble("timestamp", ×tamp); + + frontend_->responseReceived(request_id, timestamp); +} + +void NetworkAgent::loadingFinished( + std::unique_ptr params) { + String request_id; + params->getString("requestId", &request_id); + double timestamp; + params->getDouble("timestamp", ×tamp); + + frontend_->loadingFinished(request_id, timestamp); +} + +} // namespace protocol +} // namespace inspector +} // namespace node diff --git a/src/inspector/network_agent.h b/src/inspector/network_agent.h new file mode 100644 index 00000000000000..e2ca447b6e9480 --- /dev/null +++ b/src/inspector/network_agent.h @@ -0,0 +1,49 @@ +#ifndef SRC_INSPECTOR_NETWORK_AGENT_H_ +#define SRC_INSPECTOR_NETWORK_AGENT_H_ + +#include "node/inspector/protocol/Network.h" + +#include + +namespace node { + +namespace inspector { +class NetworkInspector; + +namespace protocol { + +std::unique_ptr Request(const String& url, + const String& method); + +class NetworkAgent : public Network::Backend { + public: + explicit NetworkAgent(NetworkInspector* inspector); + + void Wire(UberDispatcher* dispatcher); + + DispatchResponse enable() override; + + DispatchResponse disable() override; + + void emitNotification(const String& event, + std::unique_ptr params); + + void requestWillBeSent(std::unique_ptr params); + + void responseReceived(std::unique_ptr params); + + void loadingFinished(std::unique_ptr params); + + private: + NetworkInspector* inspector_; + std::shared_ptr frontend_; + using EventNotifier = + void (NetworkAgent::*)(std::unique_ptr); + std::unordered_map event_notifier_map_; +}; + +} // namespace protocol +} // namespace inspector +} // namespace node + +#endif // SRC_INSPECTOR_NETWORK_AGENT_H_ diff --git a/src/inspector/network_inspector.cc b/src/inspector/network_inspector.cc new file mode 100644 index 00000000000000..a03a66d461e527 --- /dev/null +++ b/src/inspector/network_inspector.cc @@ -0,0 +1,48 @@ +#include "network_inspector.h" + +namespace node { +namespace inspector { + +NetworkInspector::NetworkInspector(Environment* env) + : enabled_(false), env_(env) { + network_agent_ = std::make_unique(this); +} +NetworkInspector::~NetworkInspector() { + network_agent_.reset(); +} + +void NetworkInspector::Wire(protocol::UberDispatcher* dispatcher) { + network_agent_->Wire(dispatcher); +} + +bool NetworkInspector::canEmit(const std::string& domain) { + return domain == "Network"; +} + +void NetworkInspector::emitNotification( + const std::string& domain, + const std::string& method, + std::unique_ptr params) { + if (domain == "Network") { + network_agent_->emitNotification(method, std::move(params)); + } else { + UNREACHABLE("Unknown domain"); + } +} + +void NetworkInspector::Enable() { + if (auto agent = env_->inspector_agent()) { + agent->EnableNetworkTracking(); + } + enabled_ = true; +} + +void NetworkInspector::Disable() { + if (auto agent = env_->inspector_agent()) { + agent->DisableNetworkTracking(); + } + enabled_ = false; +} + +} // namespace inspector +} // namespace node diff --git a/src/inspector/network_inspector.h b/src/inspector/network_inspector.h new file mode 100644 index 00000000000000..1a30997bad98f1 --- /dev/null +++ b/src/inspector/network_inspector.h @@ -0,0 +1,38 @@ +#ifndef SRC_INSPECTOR_NETWORK_INSPECTOR_H_ +#define SRC_INSPECTOR_NETWORK_INSPECTOR_H_ + +#include "env.h" +#include "network_agent.h" + +namespace node { +class Environment; + +namespace inspector { + +class NetworkInspector { + public: + explicit NetworkInspector(Environment* env); + ~NetworkInspector(); + + void Wire(protocol::UberDispatcher* dispatcher); + + bool canEmit(const std::string& domain); + + void emitNotification(const std::string& domain, + const std::string& method, + std::unique_ptr params); + + void Enable(); + void Disable(); + bool IsEnabled() const { return enabled_; } + + private: + bool enabled_; + Environment* env_; + std::unique_ptr network_agent_; +}; + +} // namespace inspector +} // namespace node + +#endif // SRC_INSPECTOR_NETWORK_INSPECTOR_H_ diff --git a/src/inspector/node_inspector.gypi b/src/inspector/node_inspector.gypi index a2dfdcb42db196..d559004be80944 100644 --- a/src/inspector/node_inspector.gypi +++ b/src/inspector/node_inspector.gypi @@ -23,6 +23,10 @@ 'src/inspector/tracing_agent.h', 'src/inspector/worker_agent.cc', 'src/inspector/worker_agent.h', + 'src/inspector/network_inspector.cc', + 'src/inspector/network_inspector.h', + 'src/inspector/network_agent.cc', + 'src/inspector/network_agent.h', 'src/inspector/worker_inspector.cc', 'src/inspector/worker_inspector.h', ], @@ -36,6 +40,8 @@ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.h', '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.cpp', '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h', + '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.cpp', + '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.h', ], 'node_protocol_files': [ '<(protocol_tool_path)/lib/Allocator_h.template', diff --git a/src/inspector/node_protocol.pdl b/src/inspector/node_protocol.pdl index d8a873de263f23..ab7b6a5414c846 100644 --- a/src/inspector/node_protocol.pdl +++ b/src/inspector/node_protocol.pdl @@ -98,6 +98,57 @@ experimental domain NodeWorker SessionID sessionId string message +# Partial support for Network domain of ChromeDevTools Protocol. +# https://chromedevtools.github.io/devtools-protocol/tot/Network +experimental domain Network + # Unique request identifier. + type RequestId extends string + + # UTC time in seconds, counted from January 1, 1970. + type TimeSinceEpoch extends number + + # Monotonically increasing time in seconds since an arbitrary point in the past. + type MonotonicTime extends number + + # HTTP request data. + type Request extends object + properties + string url + string method + + # Disables network tracking, prevents network events from being sent to the client. + command disable + + # Enables network tracking, network events will now be delivered to the client. + command enable + + # Fired when page is about to send HTTP request. + event requestWillBeSent + parameters + # Request identifier. + RequestId requestId + # Request data. + Request request + # Timestamp. + MonotonicTime timestamp + # Timestamp. + TimeSinceEpoch wallTime + + # Fired when HTTP response is available. + event responseReceived + parameters + # Request identifier. + RequestId requestId + # Timestamp. + MonotonicTime timestamp + + event loadingFinished + parameters + # Request identifier. + RequestId requestId + # Timestamp. + MonotonicTime timestamp + # Support for inspecting node process state. experimental domain NodeRuntime # Enable the NodeRuntime events except by `NodeRuntime.waitingForDisconnect`. diff --git a/src/inspector/node_string.cc b/src/inspector/node_string.cc index 7960971a094fd4..c62e7ed30c4e19 100644 --- a/src/inspector/node_string.cc +++ b/src/inspector/node_string.cc @@ -84,7 +84,7 @@ String StringViewToUtf8(v8_inspector::StringView view) { String fromDouble(double d) { std::ostringstream stream; stream.imbue(std::locale::classic()); // Ignore current locale - stream << d; + stream << std::fixed << d; return stream.str(); } diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 4cab7dea04379c..bb39a0cb42a7be 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -2,6 +2,7 @@ #include "env-inl.h" #include "inspector/main_thread_interface.h" +#include "inspector/network_inspector.h" #include "inspector/node_string.h" #include "inspector/runtime_agent.h" #include "inspector/tracing_agent.h" @@ -231,6 +232,8 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, } runtime_agent_ = std::make_unique(); runtime_agent_->Wire(node_dispatcher_.get()); + network_inspector_ = std::make_unique(env); + network_inspector_->Wire(node_dispatcher_.get()); } ~ChannelImpl() override { @@ -242,6 +245,24 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, } runtime_agent_->disable(); runtime_agent_.reset(); // Dispose before the dispatchers + network_inspector_->Disable(); + network_inspector_.reset(); // Dispose before the dispatchers + } + + void emitNotificationFromBackend(const StringView& event, + const StringView& params) { + std::unique_ptr value = + protocol::DictionaryValue::cast( + protocol::StringUtil::parseJSON(params)); + std::string raw_event = protocol::StringUtil::StringViewToUtf8(event); + std::string domain_name = raw_event.substr(0, raw_event.find('.')); + std::string event_name = raw_event.substr(raw_event.find('.') + 1); + if (network_inspector_->canEmit(domain_name)) { + network_inspector_->emitNotification( + domain_name, event_name, std::move(value)); + } else { + UNREACHABLE("Unknown domain for emitNotificationFromBackend"); + } } void dispatchProtocolMessage(const StringView& message) { @@ -259,8 +280,8 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, Utf8ToStringView(method)->string())) { session_->dispatchProtocolMessage(message); } else { - node_dispatcher_->dispatch(call_id, method, std::move(value), - raw_message); + node_dispatcher_->dispatch( + call_id, method, std::move(value), raw_message); } } @@ -335,6 +356,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, std::unique_ptr runtime_agent_; std::unique_ptr tracing_agent_; std::unique_ptr worker_agent_; + std::unique_ptr network_inspector_; std::unique_ptr delegate_; std::unique_ptr session_; std::unique_ptr node_dispatcher_; @@ -631,6 +653,12 @@ class NodeInspectorClient : public V8InspectorClient { return retaining_context; } + void emitNotification(const StringView& event, const StringView& params) { + for (const auto& id_channel : channels_) { + id_channel.second->emitNotificationFromBackend(event, params); + } + } + std::shared_ptr getThreadHandle() { if (!interface_) { interface_ = std::make_shared( @@ -844,6 +872,66 @@ std::unique_ptr Agent::ConnectToMainThread( prevent_shutdown); } +void Agent::EmitProtocolEvent(const StringView& event, + const StringView& params) { + if (!env()->options()->experimental_network_inspection) return; + client_->emitNotification(event, params); +} + +void Agent::SetupNetworkTracking(Local enable_function, + Local disable_function) { + parent_env_->set_inspector_enable_network_tracking(enable_function); + parent_env_->set_inspector_disable_network_tracking(disable_function); + if (pending_enable_network_tracking) { + pending_enable_network_tracking = false; + EnableNetworkTracking(); + } else if (pending_disable_network_tracking) { + pending_disable_network_tracking = false; + DisableNetworkTracking(); + } +} + +void Agent::EnableNetworkTracking() { + if (network_tracking_enabled_) { + return; + } + HandleScope scope(parent_env_->isolate()); + Local enable = parent_env_->inspector_enable_network_tracking(); + if (enable.IsEmpty()) { + pending_enable_network_tracking = true; + } else { + ToggleNetworkTracking(parent_env_->isolate(), enable); + network_tracking_enabled_ = true; + } +} + +void Agent::DisableNetworkTracking() { + if (!network_tracking_enabled_) { + return; + } + HandleScope scope(parent_env_->isolate()); + Local disable = parent_env_->inspector_disable_network_tracking(); + if (disable.IsEmpty()) { + pending_disable_network_tracking = true; + } else if (!client_->hasConnectedSessions()) { + ToggleNetworkTracking(parent_env_->isolate(), disable); + network_tracking_enabled_ = false; + } +} + +void Agent::ToggleNetworkTracking(Isolate* isolate, Local fn) { + if (!parent_env_->can_call_into_js()) return; + auto context = parent_env_->context(); + HandleScope scope(isolate); + CHECK(!fn.IsEmpty()); + v8::TryCatch try_catch(isolate); + USE(fn->Call(context, Undefined(isolate), 0, nullptr)); + if (try_catch.HasCaught() && !try_catch.HasTerminated()) { + PrintCaughtException(isolate, context, try_catch); + UNREACHABLE("Cannot toggle network tracking, please report this."); + } +} + void Agent::WaitForDisconnect() { THROW_IF_INSUFFICIENT_PERMISSIONS(parent_env_, permission::PermissionScope::kInspector, diff --git a/src/inspector_agent.h b/src/inspector_agent.h index 725275e43c7135..2ddffdb63ffee3 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -69,6 +69,14 @@ class Agent { void ReportUncaughtException(v8::Local error, v8::Local message); + void EmitProtocolEvent(const v8_inspector::StringView& event, + const v8_inspector::StringView& params); + + void SetupNetworkTracking(v8::Local enable_function, + v8::Local disable_function); + void EnableNetworkTracking(); + void DisableNetworkTracking(); + // Async stack traces instrumentation. void AsyncTaskScheduled(const v8_inspector::StringView& taskName, void* task, bool recurring); @@ -121,6 +129,7 @@ class Agent { private: void ToggleAsyncHook(v8::Isolate* isolate, v8::Local fn); + void ToggleNetworkTracking(v8::Isolate* isolate, v8::Local fn); node::Environment* parent_env_; // Encapsulates majority of the Inspector functionality @@ -139,6 +148,10 @@ class Agent { bool pending_enable_async_hook_ = false; bool pending_disable_async_hook_ = false; + + bool network_tracking_enabled_ = false; + bool pending_enable_network_tracking = false; + bool pending_disable_network_tracking = false; }; } // namespace inspector diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 5700f8c5efc698..282575601545d1 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -270,6 +270,30 @@ static void RegisterAsyncHookWrapper(const FunctionCallbackInfo& args) { enable_function, disable_function); } +void EmitProtocolEvent(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsString()); + Local eventName = args[0].As(); + CHECK(args[1]->IsString()); + Local params = args[1].As(); + + env->inspector_agent()->EmitProtocolEvent( + ToProtocolString(env->isolate(), eventName)->string(), + ToProtocolString(env->isolate(), params)->string()); +} + +void SetupNetworkTracking(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args[0]->IsFunction()); + Local enable_function = args[0].As(); + CHECK(args[1]->IsFunction()); + Local disable_function = args[1].As(); + + env->inspector_agent()->SetupNetworkTracking(enable_function, + disable_function); +} + void IsEnabled(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); args.GetReturnValue().Set(env->inspector_agent()->IsListening()); @@ -354,6 +378,8 @@ void Initialize(Local target, Local unused, SetMethod(context, target, "registerAsyncHook", RegisterAsyncHookWrapper); SetMethodNoSideEffect(context, target, "isEnabled", IsEnabled); + SetMethod(context, target, "emitProtocolEvent", EmitProtocolEvent); + SetMethod(context, target, "setupNetworkTracking", SetupNetworkTracking); Local console_string = FIXED_ONE_BYTE_STRING(isolate, "console"); @@ -387,6 +413,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(RegisterAsyncHookWrapper); registry->Register(IsEnabled); + registry->Register(EmitProtocolEvent); + registry->Register(SetupNetworkTracking); registry->Register(JSBindingsConnection::New); registry->Register(JSBindingsConnection::Dispatch); diff --git a/src/node_options.cc b/src/node_options.cc index 8557cbac2ae1a3..18d0ec4fdd0405 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -607,6 +607,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "Directory where the V8 profiles generated by --cpu-prof will be " "placed. Does not affect --prof.", &EnvironmentOptions::cpu_prof_dir); + AddOption("--experimental-network-inspection", + "experimental network inspection support", + &EnvironmentOptions::experimental_network_inspection); AddOption( "--heap-prof", "Start the V8 heap profiler on start up, and write the heap profile " diff --git a/src/node_options.h b/src/node_options.h index b8a2f6b6dd21d8..3ff665f807594a 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -163,6 +163,7 @@ class EnvironmentOptions : public Options { uint64_t cpu_prof_interval = kDefaultCpuProfInterval; std::string cpu_prof_name; bool cpu_prof = false; + bool experimental_network_inspection = false; std::string heap_prof_dir; std::string heap_prof_name; static const uint64_t kDefaultHeapProfInterval = 512 * 1024; diff --git a/test/parallel/test-inspector-emit-protocol-event.js b/test/parallel/test-inspector-emit-protocol-event.js new file mode 100644 index 00000000000000..1a4e622c78881b --- /dev/null +++ b/test/parallel/test-inspector-emit-protocol-event.js @@ -0,0 +1,85 @@ +// Flags: --inspect=0 --experimental-network-inspection +'use strict'; +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +const inspector = require('node:inspector/promises'); +const assert = require('node:assert'); + +const EXPECTED_EVENTS = { + Network: [ + { + name: 'requestWillBeSent', + params: { + requestId: 'request-id-1', + request: { + url: 'https://nodejs.org/en', + method: 'GET' + }, + timestamp: 1000, + wallTime: 1000, + } + }, + { + name: 'responseReceived', + params: { + requestId: 'request-id-1', + timestamp: 1000, + } + }, + { + name: 'loadingFinished', + params: { + requestId: 'request-id-1', + timestamp: 1000, + } + }, + ] +}; + +// Check that all domains and events are present in the inspector object. +for (const [domain, events] of Object.entries(EXPECTED_EVENTS)) { + if (!(domain in inspector)) { + assert.fail(`Expected domain ${domain} to be present in inspector`); + } + const actualEventNames = Object.keys(inspector[domain]); + const expectedEventNames = events.map((event) => event.name); + assert.deepStrictEqual(actualEventNames, expectedEventNames, `Expected ${domain} to have events ${expectedEventNames}, but got ${actualEventNames}`); +} + +// Check that all events throw when called with a non-object argument. +for (const [domain, events] of Object.entries(EXPECTED_EVENTS)) { + for (const event of events) { + assert.throws(() => inspector[domain][event.name]('params'), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: 'The "params" argument must be of type object. Received type string (\'params\')' + }); + } +} + +const runAsyncTest = async () => { + const session = new inspector.Session(); + session.connect(); + + // Check that all events emit the expected parameters. + await session.post('Network.enable'); + for (const [domain, events] of Object.entries(EXPECTED_EVENTS)) { + for (const event of events) { + session.on(`${domain}.${event.name}`, common.mustCall(({ params }) => { + assert.deepStrictEqual(params, event.params); + })); + inspector[domain][event.name](event.params); + } + } + + // Check tht no events are emitted after disabling the domain. + await session.post('Network.disable'); + session.on('Network.requestWillBeSent', common.mustNotCall()); + inspector.Network.requestWillBeSent({}); +}; + +runAsyncTest().then(common.mustCall()).catch((e) => { + assert.fail(e); +}); diff --git a/test/parallel/test-inspector-network-domain.js b/test/parallel/test-inspector-network-domain.js new file mode 100644 index 00000000000000..1dc0d4f65a216e --- /dev/null +++ b/test/parallel/test-inspector-network-domain.js @@ -0,0 +1,120 @@ +// Flags: --inspect=0 --experimental-network-inspection +'use strict'; +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +const assert = require('node:assert'); +const fixtures = require('../common/fixtures'); +const http = require('node:http'); +const https = require('node:https'); +const inspector = require('node:inspector/promises'); + +const session = new inspector.Session(); +session.connect(); + +const httpServer = http.createServer((req, res) => { + const path = req.url; + switch (path) { + case '/hello-world': + res.writeHead(200); + res.end('hello world\n'); + break; + default: + assert(false, `Unexpected path: ${path}`); + } +}); + +const httpsServer = https.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem') +}, (req, res) => { + const path = req.url; + switch (path) { + case '/hello-world': + res.writeHead(200); + res.end('hello world\n'); + break; + default: + assert(false, `Unexpected path: ${path}`); + } +}); + +const terminate = () => { + session.disconnect(); + httpServer.close(); + httpsServer.close(); + inspector.close(); +}; + +const testHttpGet = () => new Promise((resolve, reject) => { + session.on('Network.requestWillBeSent', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(params.request.url, 'http://127.0.0.1/hello-world'); + assert.strictEqual(params.request.method, 'GET'); + assert.strictEqual(typeof params.timestamp, 'number'); + assert.strictEqual(typeof params.wallTime, 'number'); + })); + session.on('Network.responseReceived', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(typeof params.timestamp, 'number'); + })); + session.on('Network.loadingFinished', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(typeof params.timestamp, 'number'); + resolve(); + })); + + http.get({ + host: '127.0.0.1', + port: httpServer.address().port, + path: '/hello-world', + }, common.mustCall()); +}); + +const testHttpsGet = () => new Promise((resolve, reject) => { + session.on('Network.requestWillBeSent', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(params.request.url, 'https://127.0.0.1/hello-world'); + assert.strictEqual(params.request.method, 'GET'); + assert.strictEqual(typeof params.timestamp, 'number'); + assert.strictEqual(typeof params.wallTime, 'number'); + })); + session.on('Network.responseReceived', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(typeof params.timestamp, 'number'); + })); + session.on('Network.loadingFinished', common.mustCall(({ params }) => { + assert.ok(params.requestId.startsWith('node-network-event-')); + assert.strictEqual(typeof params.timestamp, 'number'); + resolve(); + })); + + https.get({ + host: '127.0.0.1', + port: httpsServer.address().port, + path: '/hello-world', + rejectUnauthorized: false, + }, common.mustCall()); +}); + +const testNetworkInspection = async () => { + await testHttpGet(); + session.removeAllListeners(); + await testHttpsGet(); + session.removeAllListeners(); +}; + +httpServer.listen(0, () => { + httpsServer.listen(0, async () => { + try { + await session.post('Network.enable'); + await testNetworkInspection(); + await session.post('Network.disable'); + } catch (e) { + assert.fail(e); + } finally { + terminate(); + } + }); +}); From d7615004d8a2c5548f9c46aac1080359a0a50c57 Mon Sep 17 00:00:00 2001 From: Aviv Keller <38299977+RedYetiDev@users.noreply.github.com> Date: Fri, 19 Jul 2024 06:44:56 -0400 Subject: [PATCH 023/120] doc: update `api_assets` README for new files PR-URL: https://github.com/nodejs/node/pull/53676 Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Claudio Wunder --- doc/api_assets/README.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/doc/api_assets/README.md b/doc/api_assets/README.md index e2c1d90cd0953f..b5d55c09b85169 100644 --- a/doc/api_assets/README.md +++ b/doc/api_assets/README.md @@ -1,13 +1,7 @@ -# API Reference Document Assets +# API documentation assets -## api.js - -The main script for API reference documents. - -## hljs.css - -The syntax theme for code snippets in API reference documents. - -## style.css - -The main stylesheet for API reference documents. +* [`api.js`](./api.js): This file contains all the JavaScript used throughout the documentation. +* [`hljs.css`](./hljs.css): This CSS file is used for syntax highlighting styles in code blocks. +* [`js-flavor-cjs.svg`](./js-flavor-cjs.svg): This SVG image represents the toggle between ESM and CJS (_CJS_) +* [`js-flavor-esm.svg`](./js-flavor-esm.svg): This SVG image represents the toggle between ESM and CJS (_ESM_) +* [`style.css`](./style.css): This CSS file contains the styling rules for the overall appearance of the API documentation. From 87bab76df219c1ea53152340fba7abd2ba391f8d Mon Sep 17 00:00:00 2001 From: jakecastelli <38635403+jakecastelli@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:53:24 +0700 Subject: [PATCH 024/120] doc,tty: add documentation for ReadStream and WriteStream Co-authored-by: Qingyu Deng PR-URL: https://github.com/nodejs/node/pull/53567 Fixes: https://github.com/nodejs/node/issues/37780 Reviewed-By: Luigi Pinca Reviewed-By: Raz Luvaton Reviewed-By: Claudio Wunder --- doc/api/tty.md | 28 ++++++++++++++++++++++++++++ tools/doc/type-parser.mjs | 3 +++ 2 files changed, 31 insertions(+) diff --git a/doc/api/tty.md b/doc/api/tty.md index 1234526dd588dd..1139355ed0f280 100644 --- a/doc/api/tty.md +++ b/doc/api/tty.md @@ -98,6 +98,33 @@ Represents the writable side of a TTY. In normal circumstances, `tty.WriteStream` instances created for a Node.js process and there should be no reason to create additional instances. +### `new tty.ReadStream(fd[, options])` + + + +* `fd` {number} A file descriptor associated with a TTY. +* `options` {Object} Options passed to parent `net.Socket`, + see `options` of [`net.Socket` constructor][]. +* Returns {tty.ReadStream} + +Creates a `ReadStream` for `fd` associated with a TTY. + +### `new tty.WriteStream(fd)` + + + +* `fd` {number} A file descriptor associated with a TTY. +* Returns {tty.WriteStream} + +Creates a `WriteStream` for `fd` associated with a TTY. + ### Event: `'resize'` -> Stability: 1.1 - Active development +> Stability: 1.1 - Active development. Enable this API with the +> [`--experimental-sqlite`][] CLI flag. @@ -314,6 +315,7 @@ exception. | `BLOB` | `Uint8Array` | [SQL injection]: https://en.wikipedia.org/wiki/SQL_injection +[`--experimental-sqlite`]: cli.md#--experimental-sqlite [`sqlite3_changes64()`]: https://www.sqlite.org/c3ref/changes.html [`sqlite3_close_v2()`]: https://www.sqlite.org/c3ref/close.html [`sqlite3_exec()`]: https://www.sqlite.org/c3ref/exec.html From 03f353293bbe09ef6bef35317858aac71a87cb92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinicius=20Louren=C3=A7o?= <12551007+H4ad@users.noreply.github.com> Date: Fri, 19 Jul 2024 21:22:24 -0300 Subject: [PATCH 028/120] lib: improve error message when index not found on cjs PR-URL: https://github.com/nodejs/node/pull/53859 Reviewed-By: James M Snell Reviewed-By: Marco Ippolito --- src/node_file.cc | 15 ++++++++------- test/es-module/test-cjs-legacyMainResolve.js | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index 150935e05a0276..7040a17bfa2edf 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -3083,6 +3083,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& args) { return; } + std::string package_initial_file = ""; + ada::result file_path_url; std::optional initial_file_path; std::string file_path; @@ -3105,6 +3107,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& args) { FromNamespacedPath(&initial_file_path.value()); + package_initial_file = *initial_file_path; + for (int i = 0; i < legacy_main_extensions_with_main_end; i++) { file_path = *initial_file_path + std::string(legacy_main_extensions[i]); @@ -3160,13 +3164,10 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& args) { } } - std::optional module_path = - node::url::FileURLToPath(env, *package_json_url); - std::optional module_base; + if (package_initial_file == "") + package_initial_file = *initial_file_path + ".js"; - if (!module_path.has_value()) { - return; - } + std::optional module_base; if (args.Length() >= 3 && args[2]->IsString()) { Utf8Value utf8_base_path(isolate, args[2]); @@ -3191,7 +3192,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& args) { THROW_ERR_MODULE_NOT_FOUND(isolate, "Cannot find package '%s' imported from %s", - *module_path, + package_initial_file, *module_base); } diff --git a/test/es-module/test-cjs-legacyMainResolve.js b/test/es-module/test-cjs-legacyMainResolve.js index 1dc7d8faafe6eb..0bfeb567a22b1f 100644 --- a/test/es-module/test-cjs-legacyMainResolve.js +++ b/test/es-module/test-cjs-legacyMainResolve.js @@ -129,7 +129,7 @@ describe('legacyMainResolve', () => { ); assert.throws( () => legacyMainResolve(packageJsonUrl, { main: null }, packageJsonUrl), - { code: 'ERR_MODULE_NOT_FOUND' }, + { message: /index\.js/, code: 'ERR_MODULE_NOT_FOUND' }, ); }); @@ -137,7 +137,20 @@ describe('legacyMainResolve', () => { const packageJsonUrl = pathToFileURL('/c/file%20with%20percents/package.json'); assert.throws( () => legacyMainResolve(packageJsonUrl, { main: null }, packageJsonUrl), - { code: 'ERR_MODULE_NOT_FOUND' }, + { message: /index\.js/, code: 'ERR_MODULE_NOT_FOUND' }, + ); + }); + + it('should report main file on error message when not found', () => { + const packageJsonUrl = pathToFileURL( + path.resolve( + fixtures.path('/es-modules/legacy-main-resolver'), + 'package.json' + ) + ); + assert.throws( + () => legacyMainResolve(packageJsonUrl, { main: './index.node' }, packageJsonUrl), + { message: /index\.node/, code: 'ERR_MODULE_NOT_FOUND' }, ); }); From c24758259127f6b582dd3a2ca11ad637ec98e76c Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Sat, 20 Jul 2024 15:16:23 +0200 Subject: [PATCH 029/120] test: deflake test-blob-file-backed Avoid race conditions by using a different file for each subtest. Fixes: https://github.com/nodejs/node/issues/51860 PR-URL: https://github.com/nodejs/node/pull/53920 Reviewed-By: Richard Lau Reviewed-By: Michael Dawson --- test/parallel/test-blob-file-backed.js | 28 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/test/parallel/test-blob-file-backed.js b/test/parallel/test-blob-file-backed.js index 0532bda07b6e80..24560d9215178c 100644 --- a/test/parallel/test-blob-file-backed.js +++ b/test/parallel/test-blob-file-backed.js @@ -21,13 +21,17 @@ const tmpdir = require('../common/tmpdir'); const testfile = tmpdir.resolve('test-file-backed-blob.txt'); const testfile2 = tmpdir.resolve('test-file-backed-blob2.txt'); const testfile3 = tmpdir.resolve('test-file-backed-blob3.txt'); +const testfile4 = tmpdir.resolve('test-file-backed-blob4.txt'); +const testfile5 = tmpdir.resolve('test-file-backed-blob5.txt'); tmpdir.refresh(); const data = `${'a'.repeat(1000)}${'b'.repeat(2000)}`; writeFileSync(testfile, data); -writeFileSync(testfile2, data.repeat(100)); -writeFileSync(testfile3, ''); +writeFileSync(testfile2, data); +writeFileSync(testfile3, data.repeat(100)); +writeFileSync(testfile4, ''); +writeFileSync(testfile5, ''); (async () => { const blob = await openAsBlob(testfile); @@ -69,7 +73,7 @@ writeFileSync(testfile3, ''); (async () => { // Refs: https://github.com/nodejs/node/issues/47683 - const blob = await openAsBlob(testfile); + const blob = await openAsBlob(testfile2); const res = blob.slice(10, 20); const ab = await res.arrayBuffer(); strictEqual(res.size, ab.byteLength); @@ -82,39 +86,41 @@ writeFileSync(testfile3, ''); const res1 = blob.slice(995, 1005); strictEqual(await res1.text(), data.slice(995, 1005)); + await unlink(testfile2); })().then(common.mustCall()); (async () => { - const blob = await openAsBlob(testfile2); + const blob = await openAsBlob(testfile3); const stream = blob.stream(); const read = async () => { // eslint-disable-next-line no-unused-vars for await (const _ of stream) { - writeFileSync(testfile2, data + 'abc'); + writeFileSync(testfile3, data + 'abc'); } }; await rejects(read(), { name: 'NotReadableError' }); - await unlink(testfile2); + await unlink(testfile3); })().then(common.mustCall()); (async () => { - const blob = await openAsBlob(testfile3); + const blob = await openAsBlob(testfile4); strictEqual(blob.size, 0); strictEqual(await blob.text(), ''); - writeFileSync(testfile3, 'abc'); + writeFileSync(testfile4, 'abc'); await rejects(blob.text(), { name: 'NotReadableError' }); - await unlink(testfile3); + await unlink(testfile4); })().then(common.mustCall()); (async () => { - const blob = await openAsBlob(testfile3); + const blob = await openAsBlob(testfile5); strictEqual(blob.size, 0); - writeFileSync(testfile3, 'abc'); + writeFileSync(testfile5, 'abc'); const stream = blob.stream(); const reader = stream.getReader(); await rejects(() => reader.read(), { name: 'NotReadableError' }); + await unlink(testfile5); })().then(common.mustCall()); (async () => { From e907236dd940bfaf2dbc744dbbc4f3d7463d74de Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Sat, 20 Jul 2024 09:26:09 -0400 Subject: [PATCH 030/120] doc: move --test-coverage-{ex,in}clude to proper location This commit moves the documentation for two CLI flags to the proper sorted location. PR-URL: https://github.com/nodejs/node/pull/53926 Reviewed-By: Richard Lau Reviewed-By: Moshe Atlow --- doc/api/cli.md | 68 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 320ad45b50aa58..efd9e54801623f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -490,40 +490,6 @@ For example, to run a module with "development" resolutions: node -C development app.js ``` -### `--test-coverage-exclude` - - - -> Stability: 1 - Experimental - -Excludes specific files from code coverage using a glob pattern, which can match -both absolute and relative file paths. - -This option may be specified multiple times to exclude multiple glob patterns. - -If both `--test-coverage-exclude` and `--test-coverage-include` are provided, -files must meet **both** criteria to be included in the coverage report. - -### `--test-coverage-include` - - - -> Stability: 1 - Experimental - -Includes specific files in code coverage using a glob pattern, which can match -both absolute and relative file paths. - -This option may be specified multiple times to include multiple glob patterns. - -If both `--test-coverage-exclude` and `--test-coverage-include` are provided, -files must meet **both** criteria to be included in the coverage report. - ### `--cpu-prof` + +> Stability: 1 - Experimental + +Excludes specific files from code coverage using a glob pattern, which can match +both absolute and relative file paths. + +This option may be specified multiple times to exclude multiple glob patterns. + +If both `--test-coverage-exclude` and `--test-coverage-include` are provided, +files must meet **both** criteria to be included in the coverage report. + +### `--test-coverage-include` + + + +> Stability: 1 - Experimental + +Includes specific files in code coverage using a glob pattern, which can match +both absolute and relative file paths. + +This option may be specified multiple times to include multiple glob patterns. + +If both `--test-coverage-exclude` and `--test-coverage-include` are provided, +files must meet **both** criteria to be included in the coverage report. + ### `--test-force-exit` + +> Stability: 1.0 - Early development + +Enable experimental type-stripping for TypeScript files. +For more information, see the [TypeScript type-stripping][] documentation. + ### `--experimental-test-coverage` + +Type stripping is not supported for files descendent of a `node_modules` directory. + [ES Module]: esm.md [ICU]: intl.md#internationalization-support [JSON Web Key Elliptic Curve Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve diff --git a/doc/api/index.md b/doc/api/index.md index 51915a78d90de4..0f3d4c8c4fec35 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -41,6 +41,7 @@ * [Modules: ECMAScript modules](esm.md) * [Modules: `node:module` API](module.md) * [Modules: Packages](packages.md) +* [Modules: TypeScript](typescript.md) * [Net](net.md) * [OS](os.md) * [Path](path.md) diff --git a/doc/api/typescript.md b/doc/api/typescript.md new file mode 100644 index 00000000000000..5f5e8b2552333e --- /dev/null +++ b/doc/api/typescript.md @@ -0,0 +1,153 @@ +# Modules: TypeScript + +## Enabling + +There are two ways to enable runtime TypeScript support in Node.js: + +1. For [full support][] of all of TypeScript's syntax and features, including + using any version of TypeScript, use a third-party package. + +2. For lightweight support, you can use the built-in support for + [type stripping][]. + +## Full TypeScript support + +To use TypeScript with full support for all TypeScript features, including +`tsconfig.json`, you can use a third-party package. These instructions use +[`tsx`][] as an example but there are many other similar libraries available. + +1. Install the package as a development dependency using whatever package + manager you're using for your project. For example, with `npm`: + + ```bash + npm install --save-dev tsx + ``` + +2. Then you can run your TypeScript code via: + + ```bash + npx tsx your-file.ts + ``` + + Or alternatively, you can run with `node` via: + + ```bash + node --import=tsx your-file.ts + ``` + +## Type stripping + + + +> Stability: 1.0 - Early development + +The flag [`--experimental-strip-types`][] enables Node.js to run TypeScript +files that contain only type annotations. Such files contain no TypeScript +features that require transformation, such as enums or namespaces. Node.js will +replace inline type annotations with whitespace, and no type checking is +performed. TypeScript features that depend on settings within `tsconfig.json`, +such as paths or converting newer JavaScript syntax to older standards, are +intentionally unsupported. To get fuller TypeScript support, including support +for enums and namespaces and paths, see [Full TypeScript support][]. + +The type stripping feature is designed to be lightweight. +By intentionally not supporting syntaxes that require JavaScript code +generation, and by replacing inline types with whitespace, Node.js can run +TypeScript code without the need for source maps. + +### Determining module system + +Node.js supports both [CommonJS][] and [ES Modules][] syntax in TypeScript +files. Node.js will not convert from one module system to another; if you want +your code to run as an ES module, you must use `import` and `export` syntax, and +if you want your code to run as CommonJS you must use `require` and +`module.exports`. + +* `.ts` files will have their module system determined [the same way as `.js` + files.][] To use `import` and `export` syntax, add `"type": "module"` to the + nearest parent `package.json`. +* `.mts` files will always be run as ES modules, similar to `.mjs` files. +* `.cts` files will always be run as CommonJS modules, similar to `.cjs` files. +* `.tsx` files are unsupported. + +As in JavaScript files, [file extensions are mandatory][] in `import` statements +and `import()` expressions: `import './file.ts'`, not `import './file'`. Because +of backward compatibility, file extensions are also mandatory in `require()` +calls: `require('./file.ts')`, not `require('./file')`, similar to how the +`.cjs` extension is mandatory in `require` calls in CommonJS files. + +The `tsconfig.json` option `allowImportingTsExtensions` will allow the +TypeScript compiler `tsc` to type-check files with `import` specifiers that +include the `.ts` extension. + +### Unsupported TypeScript features + +Since Node.js is only removing inline types, any TypeScript features that +involve _replacing_ TypeScript syntax with new JavaScript syntax will error. +This is by design. To run TypeScript with such features, see +[Full TypeScript support][]. + +The most prominent unsupported features that require transformation are: + +* `Enum` +* `experimentalDecorators` +* `namespaces` +* parameter properties + +In addition, Node.js does not read `tsconfig.json` files and does not support +features that depend on settings within `tsconfig.json`, such as paths or +converting newer JavaScript syntax into older standards. + +### Importing types without `type` keyword + +Due to the nature of type stripping, the `type` keyword is necessary to +correctly strip type imports. Without the `type` keyword, Node.js will treat the +import as a value import, which will result in a runtime error. The tsconfig +option [`verbatimModuleSyntax`][] can be used to match this behavior. + +This example will work correctly: + +```ts +import type { Type1, Type2 } from './module.ts'; +import { fn, type FnParams } from './fn.ts'; +``` + +This will result in a runtime error: + +```ts +import { Type1, Type2 } from './module.ts'; +import { fn, FnParams } from './fn.ts'; +``` + +### Non-file forms of input + +Type stripping can be enabled for `--eval` and STDIN input. The module system +will be determined by `--input-type`, as it is for JavaScript. + +TypeScript syntax is unsupported in the REPL, `--print`, `--check`, and +`inspect`. + +### Source maps + +Since inline types are replaced by whitespace, source maps are unnecessary for +correct line numbers in stack traces; and Node.js does not generate them. For +source maps support, see [Full TypeScript support][]. + +### Type stripping in dependencies + +To discourage package authors from publishing packages written in TypeScript, +Node.js will by default refuse to handle TypeScript files inside folders under +a `node_modules` path. + +[CommonJS]: modules.md +[ES Modules]: esm.md +[Full TypeScript support]: #full-typescript-support +[`--experimental-strip-types`]: cli.md#--experimental-strip-types +[`tsx`]: https://tsx.is/ +[`verbatimModuleSyntax`]: https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax +[file extensions are mandatory]: esm.md#mandatory-file-extensions +[full support]: #full-typescript-support +[the same way as `.js` files.]: packages.md#determining-module-system +[type stripping]: #type-stripping diff --git a/doc/contributing/maintaining/maintaining-dependencies.md b/doc/contributing/maintaining/maintaining-dependencies.md index d1bfc7dcaf6755..14a6004e382278 100644 --- a/doc/contributing/maintaining/maintaining-dependencies.md +++ b/doc/contributing/maintaining/maintaining-dependencies.md @@ -10,6 +10,7 @@ This a list of all the dependencies: * [acorn][] * [ada][] +* [amaro][] * [base64][] * [brotli][] * [c-ares][] @@ -168,6 +169,11 @@ an abstract syntax tree walker for the ESTree format. The [ada](https://github.com/ada-url/ada) dependency is a fast and spec-compliant URL parser written in C++. +### amaro + +The [amaro](https://www.npmjs.com/package/amaro) dependency is a wrapper around the +WebAssembly version of the SWC JavaScript/TypeScript parser. + ### brotli The [brotli](https://github.com/google/brotli) dependency is @@ -336,6 +342,7 @@ performance improvements not currently available in standard zlib. [acorn]: #acorn [ada]: #ada +[amaro]: #amaro [base64]: #base64 [brotli]: #brotli [c-ares]: #c-ares diff --git a/doc/node.1 b/doc/node.1 index 389c11513d898d..80a5f6401202af 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -194,6 +194,9 @@ Enable module mocking in the test runner. .It Fl -experimental-test-snapshots Enable snapshot testing in the test runner. . +.It Fl -experimental-strip-types +Enable experimental type-stripping for TypeScript files. +. .It Fl -experimental-eventsource Enable experimental support for the EventSource Web API. . diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 45b71cc4a90dd1..67d8dcf5b83aab 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1837,6 +1837,9 @@ E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url, supported) => { msg += `. Received protocol '${url.protocol}'`; return msg; }, Error); +E('ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING', + 'Stripping types is currently unsupported for files under node_modules, for "%s"', + Error); E('ERR_UNSUPPORTED_RESOLVE_REQUEST', 'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.', TypeError); diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index 1125aa8d98e5aa..716cb7e8c8eb06 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -14,7 +14,7 @@ const { markBootstrapComplete, } = require('internal/process/pre_execution'); const { evalModuleEntryPoint, evalScript } = require('internal/process/execution'); -const { addBuiltinLibsToObject } = require('internal/modules/helpers'); +const { addBuiltinLibsToObject, tsParse } = require('internal/modules/helpers'); const { getOptionValue } = require('internal/options'); @@ -22,7 +22,11 @@ prepareMainThreadExecution(); addBuiltinLibsToObject(globalThis, ''); markBootstrapComplete(); -const source = getOptionValue('--eval'); +const code = getOptionValue('--eval'); +const source = getOptionValue('--experimental-strip-types') ? + tsParse(code) : + code; + const print = getOptionValue('--print'); const shouldLoadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; if (getOptionValue('--input-type') === 'module' || diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 825c119674828c..b18773a6ed8763 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -146,6 +146,7 @@ const { safeGetenv } = internalBinding('credentials'); const { getCjsConditions, initializeCjsConditions, + isUnderNodeModules, loadBuiltinModule, makeRequireFunction, setHasStartedUserCJSExecution, @@ -168,6 +169,7 @@ const { ERR_REQUIRE_CYCLE_MODULE, ERR_REQUIRE_ESM, ERR_UNKNOWN_BUILTIN_MODULE, + ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, }, setArrowMessage, } = require('internal/errors'); @@ -428,9 +430,18 @@ function initializeCJS() { Module.runMain = require('internal/modules/run_main').executeUserEntryPoint; + const tsEnabled = getOptionValue('--experimental-strip-types'); + if (tsEnabled) { + emitExperimentalWarning('Type Stripping'); + Module._extensions['.cts'] = loadCTS; + Module._extensions['.ts'] = loadTS; + } if (getOptionValue('--experimental-require-module')) { emitExperimentalWarning('Support for loading ES Module in require()'); Module._extensions['.mjs'] = loadESMFromCJS; + if (tsEnabled) { + Module._extensions['.mts'] = loadESMFromCJS; + } } } @@ -639,10 +650,24 @@ function resolveExports(nmPath, request) { // We don't cache this in case user extends the extensions. function getDefaultExtensions() { - const extensions = ObjectKeys(Module._extensions); + let extensions = ObjectKeys(Module._extensions); + const tsEnabled = getOptionValue('--experimental-strip-types'); + if (tsEnabled) { + extensions = ArrayPrototypeFilter(extensions, (ext) => + ext !== '.ts' || Module._extensions['.ts'] !== loadTS || + ext !== '.cts' || Module._extensions['.ts'] !== loadCTS, + ); + } + if (!getOptionValue('--experimental-require-module')) { return extensions; } + + if (tsEnabled) { + extensions = ArrayPrototypeFilter(extensions, (ext) => + ext !== '.mts' || Module._extensions['.mts'] !== loadESMFromCJS, + ); + } // If the .mjs extension is added by --experimental-require-module, // remove it from the supported default extensions to maintain // compatibility. @@ -1279,6 +1304,12 @@ Module.prototype.load = function(filename) { throw new ERR_REQUIRE_ESM(filename, true); } + if (getOptionValue('--experimental-strip-types')) { + if (StringPrototypeEndsWith(filename, '.mts') && !Module._extensions['.mts']) { + throw new ERR_REQUIRE_ESM(filename, true); + } + } + Module._extensions[extension](this, filename); this.loaded = true; @@ -1322,7 +1353,14 @@ let hasPausedEntry = false; * @param {string} filename Absolute path of the file. */ function loadESMFromCJS(mod, filename) { - const source = getMaybeCachedSource(mod, filename); + let source = getMaybeCachedSource(mod, filename); + if (getOptionValue('--experimental-strip-types') && path.extname(filename) === '.mts') { + if (isUnderNodeModules(filename)) { + throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); + } + const { tsParse } = require('internal/modules/helpers'); + source = tsParse(source); + } const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); const isMain = mod[kIsMainSymbol]; if (isMain) { @@ -1529,6 +1567,77 @@ function getMaybeCachedSource(mod, filename) { return content; } +function loadCTS(module, filename) { + if (isUnderNodeModules(filename)) { + throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); + } + const source = getMaybeCachedSource(module, filename); + const { tsParse } = require('internal/modules/helpers'); + const content = tsParse(source); + module._compile(content, filename, 'commonjs'); +} + +/** + * Built-in handler for `.ts` files. + * @param {Module} module The module to compile + * @param {string} filename The file path of the module + */ +function loadTS(module, filename) { + if (isUnderNodeModules(filename)) { + throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(filename); + } + // If already analyzed the source, then it will be cached. + const source = getMaybeCachedSource(module, filename); + const { tsParse } = require('internal/modules/helpers'); + const content = tsParse(source); + let format; + const pkg = packageJsonReader.getNearestParentPackageJSON(filename); + // Function require shouldn't be used in ES modules. + if (pkg?.data.type === 'module') { + if (getOptionValue('--experimental-require-module')) { + module._compile(content, filename, 'module'); + return; + } + + const parent = module[kModuleParent]; + const parentPath = parent?.filename; + const packageJsonPath = path.resolve(pkg.path, 'package.json'); + const usesEsm = containsModuleSyntax(content, filename); + const err = new ERR_REQUIRE_ESM(filename, usesEsm, parentPath, + packageJsonPath); + // Attempt to reconstruct the parent require frame. + if (Module._cache[parentPath]) { + let parentSource; + try { + parentSource = tsParse(fs.readFileSync(parentPath, 'utf8')); + } catch { + // Continue regardless of error. + } + if (parentSource) { + reconstructErrorStack(err, parentPath, parentSource); + } + } + throw err; + } else if (pkg?.data.type === 'commonjs') { + format = 'commonjs'; + } + + module._compile(content, filename, format); +}; + +function reconstructErrorStack(err, parentPath, parentSource) { + const errLine = StringPrototypeSplit( + StringPrototypeSlice(err.stack, StringPrototypeIndexOf( + err.stack, ' at ')), '\n', 1)[0]; + const { 1: line, 2: col } = + RegExpPrototypeExec(/(\d+):(\d+)\)/, errLine) || []; + if (line && col) { + const srcLine = StringPrototypeSplit(parentSource, '\n')[line - 1]; + const frame = `${parentPath}:${line}\n${srcLine}\n${StringPrototypeRepeat(' ', col - 1)}^\n`; + setArrowMessage(err, frame); + } +} + /** * Built-in handler for `.js` files. * @param {Module} module The module to compile @@ -1564,17 +1673,7 @@ Module._extensions['.js'] = function(module, filename) { // Continue regardless of error. } if (parentSource) { - const errLine = StringPrototypeSplit( - StringPrototypeSlice(err.stack, StringPrototypeIndexOf( - err.stack, ' at ')), '\n', 1)[0]; - const { 1: line, 2: col } = - RegExpPrototypeExec(/(\d+):(\d+)\)/, errLine) || []; - if (line && col) { - const srcLine = StringPrototypeSplit(parentSource, '\n')[line - 1]; - const frame = `${parentPath}:${line}\n${srcLine}\n${ - StringPrototypeRepeat(' ', col - 1)}^\n`; - setArrowMessage(err, frame); - } + reconstructErrorStack(err, parentPath, parentSource); } } throw err; diff --git a/lib/internal/modules/esm/formats.js b/lib/internal/modules/esm/formats.js index 9f59349997fce3..608b69caf3fe49 100644 --- a/lib/internal/modules/esm/formats.js +++ b/lib/internal/modules/esm/formats.js @@ -23,6 +23,12 @@ if (experimentalWasmModules) { extensionFormatMap['.wasm'] = 'wasm'; } +if (getOptionValue('--experimental-strip-types')) { + extensionFormatMap['.ts'] = 'module-typescript'; + extensionFormatMap['.mts'] = 'module-typescript'; + extensionFormatMap['.cts'] = 'commonjs-typescript'; +} + /** * @param {string} mime * @returns {string | null} diff --git a/lib/internal/modules/esm/get_format.js b/lib/internal/modules/esm/get_format.js index c6bc030f8b2b31..46938d88a496b9 100644 --- a/lib/internal/modules/esm/get_format.js +++ b/lib/internal/modules/esm/get_format.js @@ -95,6 +95,18 @@ function underNodeModules(url) { } let typelessPackageJsonFilesWarnedAbout; +function warnTypelessPackageJsonFile(pjsonPath, url) { + typelessPackageJsonFilesWarnedAbout ??= new SafeSet(); + if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { + const warning = `${url} parsed as an ES module because module syntax was detected;` + + ` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`; + process.emitWarning(warning, { + code: 'MODULE_TYPELESS_PACKAGE_JSON', + }); + typelessPackageJsonFilesWarnedAbout.add(pjsonPath); + } +} + /** * @param {URL} url * @param {{parentURL: string; source?: Buffer}} context @@ -130,15 +142,38 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE if (format === 'module') { // This module has a .js extension, a package.json with no `type` field, and ESM syntax. // Warn about the missing `type` field so that the user can avoid the performance penalty of detection. - typelessPackageJsonFilesWarnedAbout ??= new SafeSet(); - if (!typelessPackageJsonFilesWarnedAbout.has(pjsonPath)) { - const warning = `${url} parsed as an ES module because module syntax was detected;` + - ` to avoid the performance penalty of syntax detection, add "type": "module" to ${pjsonPath}`; - process.emitWarning(warning, { - code: 'MODULE_TYPELESS_PACKAGE_JSON', - }); - typelessPackageJsonFilesWarnedAbout.add(pjsonPath); - } + warnTypelessPackageJsonFile(pjsonPath, url); + } + return format; + } + } + } + if (ext === '.ts' && getOptionValue('--experimental-strip-types')) { + const { type: packageType, pjsonPath } = getPackageScopeConfig(url); + if (packageType !== 'none') { + return `${packageType}-typescript`; + } + // The controlling `package.json` file has no `type` field. + switch (getOptionValue('--experimental-default-type')) { + case 'module': { // The user explicitly passed `--experimental-default-type=module`. + // An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules` + // should retain the assumption that a lack of a `type` field means CommonJS. + return underNodeModules(url) ? 'commonjs-typescript' : 'module-typescript'; + } + case 'commonjs': { // The user explicitly passed `--experimental-default-type=commonjs`. + return 'commonjs-typescript'; + } + default: { // The user did not pass `--experimental-default-type`. + // `source` is undefined when this is called from `defaultResolve`; + // but this gets called again from `defaultLoad`/`defaultLoadSync`. + const { tsParse } = require('internal/modules/helpers'); + const parsedSource = tsParse(source); + const detectedFormat = detectModuleFormat(parsedSource, url); + const format = detectedFormat ? `${detectedFormat}-typescript` : 'commonjs-typescript'; + if (format === 'module-typescript') { + // This module has a .js extension, a package.json with no `type` field, and ESM syntax. + // Warn about the missing `type` field so that the user can avoid the performance penalty of detection. + warnTypelessPackageJsonFile(pjsonPath, url); } return format; } diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 1ca6495c84a029..fda873d5717916 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -18,12 +18,16 @@ const defaultType = getOptionValue('--experimental-default-type'); const { Buffer: { from: BufferFrom } } = require('buffer'); +const { + isUnderNodeModules, +} = require('internal/modules/helpers'); const { URL } = require('internal/url'); const { ERR_INVALID_URL, ERR_UNKNOWN_MODULE_FORMAT, ERR_UNSUPPORTED_ESM_URL_SCHEME, + ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, } = require('internal/errors').codes; const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/; @@ -147,6 +151,12 @@ async function defaultLoad(url, context = kEmptyObject) { format = 'commonjs-sync'; } + if (getOptionValue('--experimental-strip-types') && + (format === 'module-typescript' || format === 'commonjs-typescript') && + isUnderNodeModules(url)) { + throw new ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING(url); + } + return { __proto__: null, format, diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 92491e088ee054..105f8da7bdef77 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -579,13 +579,18 @@ class ModuleLoader { this.#customizations.loadSync(url, context) : defaultLoadSync(url, context); let format = result?.format; - if (format === 'module') { + if (format === 'module' || format === 'module-typescript') { throw new ERR_REQUIRE_ESM(url, true); } if (format === 'commonjs') { format = 'require-commonjs'; result = { __proto__: result, format }; } + if (format === 'commonjs-typescript') { + format = 'require-commonjs-typescript'; + result = { __proto__: result, format }; + } + this.validateLoadResult(url, format); return result; } diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 973e8853cc099d..d931d72f5ec1e0 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -3,6 +3,7 @@ const { ArrayPrototypeMap, Boolean, + FunctionPrototypeCall, JSONParse, ObjectKeys, ObjectPrototypeHasOwnProperty, @@ -37,6 +38,7 @@ const { readFileSync } = require('fs'); const { dirname, extname, isAbsolute } = require('path'); const { loadBuiltinModule, + tsParse, stripBOM, urlToFilename, } = require('internal/modules/helpers'); @@ -302,6 +304,15 @@ translators.set('require-commonjs', (url, source, isMain) => { return createCJSModuleWrap(url, source); }); +// Handle CommonJS modules referenced by `require` calls. +// This translator function must be sync, as `require` is sync. +translators.set('require-commonjs-typescript', (url, source, isMain) => { + emitExperimentalWarning('Type Stripping'); + assert(cjsParse); + const code = tsParse(stringify(source)); + return createCJSModuleWrap(url, code); +}); + // Handle CommonJS modules referenced by `import` statements or expressions, // or as the initial entry point when the ESM loader handles a CommonJS entry. translators.set('commonjs', async function commonjsStrategy(url, source, @@ -510,3 +521,21 @@ translators.set('wasm', async function(url, source) { } }).module; }); + +// Strategy for loading a commonjs TypeScript module +translators.set('commonjs-typescript', function(url, source) { + emitExperimentalWarning('Type Stripping'); + assertBufferSource(source, false, 'load'); + const code = tsParse(stringify(source)); + debug(`Translating TypeScript ${url}`); + return FunctionPrototypeCall(translators.get('commonjs'), this, url, code, false); +}); + +// Strategy for loading an esm TypeScript module +translators.set('module-typescript', function(url, source) { + emitExperimentalWarning('Type Stripping'); + assertBufferSource(source, false, 'load'); + const code = tsParse(stringify(source)); + debug(`Translating TypeScript ${url}`); + return FunctionPrototypeCall(translators.get('module'), this, url, code, false); +}); diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index c9742f7f2317dc..2eae0f6cd3f78f 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -2,6 +2,7 @@ const { ArrayPrototypeForEach, + ArrayPrototypeIncludes, ObjectDefineProperty, ObjectPrototypeHasOwnProperty, SafeMap, @@ -9,6 +10,7 @@ const { StringPrototypeCharCodeAt, StringPrototypeIncludes, StringPrototypeSlice, + StringPrototypeSplit, StringPrototypeStartsWith, } = primordials; const { @@ -298,14 +300,37 @@ function getBuiltinModule(id) { return normalizedId ? require(normalizedId) : undefined; } +let parseTS; + +function lazyLoadTSParser() { + parseTS ??= require('internal/deps/amaro/dist/index').transformSync; + return parseTS; +} + +function tsParse(source) { + if (!source || typeof source !== 'string') { return; } + const transformSync = lazyLoadTSParser(); + const { code } = transformSync(source); + return code; +} + +function isUnderNodeModules(filename) { + const resolvedPath = path.resolve(filename); + const normalizedPath = path.normalize(resolvedPath); + const splitPath = StringPrototypeSplit(normalizedPath, path.sep); + return ArrayPrototypeIncludes(splitPath, 'node_modules'); +} + module.exports = { addBuiltinLibsToObject, getBuiltinModule, getCjsConditions, initializeCjsConditions, + isUnderNodeModules, loadBuiltinModule, makeRequireFunction, normalizeReferrerURL, + tsParse, stripBOM, toRealPath, hasStartedUserCJSExecution() { diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 6c5601ed458b89..627cb2d50e2ce6 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -81,6 +81,14 @@ function shouldUseESMLoader(mainPath) { if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; } + if (getOptionValue('--experimental-strip-types')) { + // This ensures that --experimental-default-type=commonjs and .mts files are treated as commonjs + if (getOptionValue('--experimental-default-type') === 'commonjs') { return false; } + if (mainPath && StringPrototypeEndsWith(mainPath, '.cts')) { return false; } + // This will likely change in the future to start with commonjs loader by default + if (mainPath && StringPrototypeEndsWith(mainPath, '.mts')) { return true; } + } + const type = getNearestParentPackageJSONType(mainPath); // No package.json or no `type` field. diff --git a/node.gyp b/node.gyp index f61b036afac556..46baef1db3eace 100644 --- a/node.gyp +++ b/node.gyp @@ -56,6 +56,7 @@ 'deps/acorn/acorn/dist/acorn.js', 'deps/acorn/acorn-walk/dist/walk.js', 'deps/minimatch/index.js', + 'deps/amaro/dist/index.js', '<@(node_builtin_shareable_builtins)', ], 'node_sources': [ diff --git a/src/amaro_version.h b/src/amaro_version.h new file mode 100644 index 00000000000000..04b36e61b95d48 --- /dev/null +++ b/src/amaro_version.h @@ -0,0 +1,6 @@ +// This is an auto generated file, please do not edit. +// Refer to tools/dep_updaters/update-amaro.sh +#ifndef SRC_AMARO_VERSION_H_ +#define SRC_AMARO_VERSION_H_ +#define AMARO_VERSION "0.0.4" +#endif // SRC_AMARO_VERSION_H_ diff --git a/src/node_metadata.cc b/src/node_metadata.cc index 36599a3360fe49..823a548e87219e 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -1,6 +1,7 @@ #include "node_metadata.h" #include "acorn_version.h" #include "ada.h" +#include "amaro_version.h" #include "ares.h" #include "brotli/encode.h" #include "cjs_module_lexer_version.h" @@ -116,6 +117,7 @@ Metadata::Versions::Versions() { acorn = ACORN_VERSION; cjs_module_lexer = CJS_MODULE_LEXER_VERSION; uvwasi = UVWASI_VERSION_STRING; + amaro = AMARO_VERSION; #if HAVE_OPENSSL openssl = GetOpenSSLVersion(); diff --git a/src/node_metadata.h b/src/node_metadata.h index 87dcfd08f5191c..5502c9696cc474 100644 --- a/src/node_metadata.h +++ b/src/node_metadata.h @@ -51,6 +51,7 @@ namespace node { V(sqlite) \ V(ada) \ V(nbytes) \ + V(amaro) \ NODE_VERSIONS_KEY_UNDICI(V) \ V(cjs_module_lexer) diff --git a/src/node_options.cc b/src/node_options.cc index 18d0ec4fdd0405..ee92bf89708629 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -800,6 +800,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "ES module to preload (option can be repeated)", &EnvironmentOptions::preload_esm_modules, kAllowedInEnvvar); + AddOption("--experimental-strip-types", + "Experimental type-stripping for TypeScript files.", + &EnvironmentOptions::experimental_strip_types, + kAllowedInEnvvar); AddOption("--interactive", "always enter the REPL even if stdin does not appear " "to be a terminal", diff --git a/src/node_options.h b/src/node_options.h index 3ff665f807594a..5e4d1a408efaaf 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -235,6 +235,8 @@ class EnvironmentOptions : public Options { std::vector preload_esm_modules; + bool experimental_strip_types = false; + std::vector user_argv; bool report_exclude_network = false; diff --git a/test/es-module/test-typescript-commonjs.mjs b/test/es-module/test-typescript-commonjs.mjs new file mode 100644 index 00000000000000..477e0633f78678 --- /dev/null +++ b/test/es-module/test-typescript-commonjs.mjs @@ -0,0 +1,166 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { match, strictEqual } from 'node:assert'; +import { test } from 'node:test'; + +test('require a .ts file with explicit extension succeeds', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--eval', + 'require("./test-typescript.ts")', + '--no-warnings', + ], { + cwd: fixtures.path('typescript/ts'), + }); + + strictEqual(result.stderr, ''); + strictEqual(result.stdout, 'Hello, TypeScript!\n'); + strictEqual(result.code, 0); +}); + +// TODO(marco-ippolito) This test should fail because extensionless require +// but it's behaving like a .js file +test('eval require a .ts file with implicit extension fails', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--eval', + 'require("./test-typescript")', + '--no-warnings', + ], { + cwd: fixtures.path('typescript/ts'), + }); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +// TODO(marco-ippolito) This test should fail because extensionless require +// but it's behaving like a .js file +test('require a .ts file with implicit extension fails', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/cts/test-extensionless-require.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect failure of an .mts file with CommonJS syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/cts/test-cts-but-module-syntax.cts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /To load an ES module, set "type": "module" in the package\.json or use the \.mjs extension\./); + strictEqual(result.code, 1); +}); + +test('execute a .cts file importing a .cts file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/cts/test-require-commonjs.cts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a .cts file importing a .ts file export', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/cts/test-require-ts-file.cts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a .cts file importing a .mts file export', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/cts/test-require-mts-module.cts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /Error \[ERR_REQUIRE_ESM\]: require\(\) of ES Module/); + strictEqual(result.code, 1); +}); + +test('execute a .cts file importing a .mts file export', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-require-module', + fixtures.path('typescript/cts/test-require-mts-module.cts'), + ]); + + match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect failure of a .cts file with default type module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', // Keeps working with commonjs + '--no-warnings', + fixtures.path('typescript/cts/test-require-commonjs.cts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect failure of a .cts file in node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/cts/test-cts-node_modules.cts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.code, 1); +}); + +test('expect failure of a .ts file in node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/cts/test-ts-node_modules.cts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.code, 1); +}); + +test('expect failure of a .cts requiring esm without default type module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/cts/test-mts-node_modules.cts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /ERR_REQUIRE_ESM/); + strictEqual(result.code, 1); +}); + +test('expect failure of a .cts file requiring esm in node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-require-module', + fixtures.path('typescript/cts/test-mts-node_modules.cts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.code, 1); +}); diff --git a/test/es-module/test-typescript-eval.mjs b/test/es-module/test-typescript-eval.mjs new file mode 100644 index 00000000000000..aba4c01108f821 --- /dev/null +++ b/test/es-module/test-typescript-eval.mjs @@ -0,0 +1,84 @@ +import { spawnPromisified } from '../common/index.mjs'; +import { match, strictEqual } from 'node:assert'; +import { test } from 'node:test'; + +test('eval TypeScript ESM syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--input-type=module', + '--experimental-strip-types', + '--eval', + `import util from 'node:util' + const text: string = 'Hello, TypeScript!' + console.log(util.styleText('red', text));`]); + + match(result.stderr, /Type Stripping is an experimental feature and might change at any time/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('eval TypeScript CommonJS syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--input-type=commonjs', + '--experimental-strip-types', + '--eval', + `const util = require('node:util'); + const text: string = 'Hello, TypeScript!' + console.log(util.styleText('red', text));`, + '--no-warnings']); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.stderr, ''); + strictEqual(result.code, 0); +}); + +test('eval TypeScript CommonJS syntax by default', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--eval', + `const util = require('node:util'); + const text: string = 'Hello, TypeScript!' + console.log(util.styleText('red', text));`, + '--no-warnings']); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('TypeScript ESM syntax not specified', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--eval', + `import util from 'node:util' + const text: string = 'Hello, TypeScript!' + console.log(text);`]); + match(result.stderr, /ExperimentalWarning: Type Stripping is an experimental/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect fail eval TypeScript CommonJS syntax with input-type module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--input-type=module', + '--eval', + `const util = require('node:util'); + const text: string = 'Hello, TypeScript!' + console.log(util.styleText('red', text));`]); + + strictEqual(result.stdout, ''); + match(result.stderr, /require is not defined in ES module scope, you can use import instead/); + strictEqual(result.code, 1); +}); + +test('expect fail eval TypeScript CommonJS syntax with input-type module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--input-type=commonjs', + '--eval', + `import util from 'node:util' + const text: string = 'Hello, TypeScript!' + console.log(util.styleText('red', text));`]); + strictEqual(result.stdout, ''); + match(result.stderr, /Cannot use import statement outside a module/); + strictEqual(result.code, 1); +}); diff --git a/test/es-module/test-typescript-module.mjs b/test/es-module/test-typescript-module.mjs new file mode 100644 index 00000000000000..976e6004100bf5 --- /dev/null +++ b/test/es-module/test-typescript-module.mjs @@ -0,0 +1,97 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { match, strictEqual } from 'node:assert'; +import { test } from 'node:test'; + +test('expect failure of a .mts file with CommonJS syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/mts/test-mts-but-commonjs-syntax.mts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /require is not defined in ES module scope, you can use import instead/); + strictEqual(result.code, 1); +}); + +test('execute an .mts file importing an .mts file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/mts/test-import-module.mts'), + ]); + + match(result.stderr, /Type Stripping is an experimental feature and might change at any time/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute an .mts file importing a .ts file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', // this should fail + '--no-warnings', + fixtures.path('typescript/mts/test-import-ts-file.mts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute an .mts file importing a .cts file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + '--no-warnings', + fixtures.path('typescript/mts/test-import-commonjs.mts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute an .mts file with wrong default module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=commonjs', + fixtures.path('typescript/mts/test-import-module.mts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /Error \[ERR_REQUIRE_ESM\]: require\(\) of ES Module/); + strictEqual(result.code, 1); +}); + +test('execute an .mts file from node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/mts/test-mts-node_modules.mts'), + ]); + + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('execute a .cts file from node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/mts/test-cts-node_modules.mts'), + ]); + + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('execute a .ts file from node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/mts/test-ts-node_modules.mts'), + ]); + + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); diff --git a/test/es-module/test-typescript.mjs b/test/es-module/test-typescript.mjs new file mode 100644 index 00000000000000..1c41215f06c0bc --- /dev/null +++ b/test/es-module/test-typescript.mjs @@ -0,0 +1,229 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { match, strictEqual } from 'node:assert'; +import { test } from 'node:test'; + +test('execute a TypeScript file', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/ts/test-typescript.ts'), + ]); + + match(result.stderr, /Type Stripping is an experimental feature and might change at any time/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a TypeScript file with imports', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', + '--no-warnings', + fixtures.path('typescript/ts/test-import-foo.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a TypeScript file with node_modules', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', + '--no-warnings', + fixtures.path('typescript/ts/test-typescript-node-modules.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect error when executing a TypeScript file with imports with no extensions', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', + fixtures.path('typescript/ts/test-import-no-extension.ts'), + ]); + + match(result.stderr, /Error \[ERR_MODULE_NOT_FOUND\]:/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('expect error when executing a TypeScript file with enum', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/ts/test-enums.ts'), + ]); + + // This error should be thrown during transformation + match(result.stderr, /TypeScript enum is not supported in strip-only mode/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('expect error when executing a TypeScript file with experimental decorators', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/ts/test-experimental-decorators.ts'), + ]); + // This error should be thrown at runtime + match(result.stderr, /Invalid or unexpected token/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('expect error when executing a TypeScript file with namespaces', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/ts/test-namespaces.ts'), + ]); + // This error should be thrown during transformation + match(result.stderr, /TypeScript namespace declaration is not supported in strip-only mode/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('execute a TypeScript file with type definition', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/ts/test-import-types.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a TypeScript file with type definition but no type keyword', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', + fixtures.path('typescript/ts/test-import-no-type-keyword.ts'), + ]); + + match(result.stderr, /does not provide an export named 'MyType'/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('execute a TypeScript file with CommonJS syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/ts/test-commonjs-parsing.ts'), + ]); + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a TypeScript file with ES module syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', + '--no-warnings', + fixtures.path('typescript/ts/test-module-typescript.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect failure of a TypeScript file requiring ES module syntax', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-require-module', + fixtures.path('typescript/ts/test-require-module.ts'), + ]); + + match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('expect stack trace of a TypeScript file to be correct', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/ts/test-whitespacing.ts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /test-whitespacing\.ts:5:7/); + strictEqual(result.code, 1); +}); + +test('execute CommonJS TypeScript file from node_modules with require-module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-default-type=module', + '--experimental-strip-types', + fixtures.path('typescript/ts/test-import-ts-node-modules.ts'), + ]); + + match(result.stderr, /ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING/); + strictEqual(result.stdout, ''); + strictEqual(result.code, 1); +}); + +test('execute a TypeScript file with CommonJS syntax but default type module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=module', + fixtures.path('typescript/ts/test-commonjs-parsing.ts'), + ]); + strictEqual(result.stdout, ''); + match(result.stderr, /require is not defined in ES module scope, you can use import instead/); + strictEqual(result.code, 1); +}); + +test('execute a TypeScript file with CommonJS syntax requiring .cts', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--no-warnings', + fixtures.path('typescript/ts/test-require-cts.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a TypeScript file with CommonJS syntax requiring .mts', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + fixtures.path('typescript/ts/test-require-mts.ts'), + ]); + + strictEqual(result.stdout, ''); + match(result.stderr, /Error \[ERR_REQUIRE_ESM\]: require\(\) of ES Module/); + strictEqual(result.code, 1); +}); + +test('execute a TypeScript file with CommonJS syntax requiring .mts with require-module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-require-module', + fixtures.path('typescript/ts/test-require-mts.ts'), + ]); + + match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); + +test('execute a TypeScript file with CommonJS syntax requiring .mts with require-module', async () => { + const result = await spawnPromisified(process.execPath, [ + '--experimental-strip-types', + '--experimental-default-type=commonjs', + '--no-warnings', + fixtures.path('typescript/ts/test-require-cts.ts'), + ]); + + strictEqual(result.stderr, ''); + match(result.stdout, /Hello, TypeScript!/); + strictEqual(result.code, 0); +}); diff --git a/test/fixtures/typescript/cts/node_modules/bar/bar.ts b/test/fixtures/typescript/cts/node_modules/bar/bar.ts new file mode 100644 index 00000000000000..e0716bfd567a70 --- /dev/null +++ b/test/fixtures/typescript/cts/node_modules/bar/bar.ts @@ -0,0 +1,5 @@ +const bar: string = "Hello, TypeScript!"; + +module.exports = { + bar, +}; diff --git a/test/fixtures/typescript/cts/node_modules/bar/package.json b/test/fixtures/typescript/cts/node_modules/bar/package.json new file mode 100644 index 00000000000000..18ef424398b114 --- /dev/null +++ b/test/fixtures/typescript/cts/node_modules/bar/package.json @@ -0,0 +1,13 @@ +{ + "name": "bar", + "version": "1.0.0", + "main": "bar.ts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} \ No newline at end of file diff --git a/test/fixtures/typescript/cts/node_modules/baz/baz.mts b/test/fixtures/typescript/cts/node_modules/baz/baz.mts new file mode 100644 index 00000000000000..746109acf8cede --- /dev/null +++ b/test/fixtures/typescript/cts/node_modules/baz/baz.mts @@ -0,0 +1 @@ +export const baz: string = 'Hello, TypeScript!'; diff --git a/test/fixtures/typescript/cts/node_modules/baz/package.json b/test/fixtures/typescript/cts/node_modules/baz/package.json new file mode 100644 index 00000000000000..5260f9a33d8156 --- /dev/null +++ b/test/fixtures/typescript/cts/node_modules/baz/package.json @@ -0,0 +1,14 @@ +{ + "name": "baz", + "version": "1.0.0", + "type": "module", + "main": "baz.mts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} \ No newline at end of file diff --git a/test/fixtures/typescript/cts/node_modules/foo/foo.cts b/test/fixtures/typescript/cts/node_modules/foo/foo.cts new file mode 100644 index 00000000000000..c7dbc8680aa5a1 --- /dev/null +++ b/test/fixtures/typescript/cts/node_modules/foo/foo.cts @@ -0,0 +1,5 @@ +const foo: string = 'Hello, TypeScript!'; + +module.exports = { + foo +}; diff --git a/test/fixtures/typescript/cts/node_modules/foo/package.json b/test/fixtures/typescript/cts/node_modules/foo/package.json new file mode 100644 index 00000000000000..3036e618506c3b --- /dev/null +++ b/test/fixtures/typescript/cts/node_modules/foo/package.json @@ -0,0 +1,14 @@ +{ + "name": "foo", + "version": "1.0.0", + "type": "commonjs", + "main": "foo.cts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} \ No newline at end of file diff --git a/test/fixtures/typescript/cts/test-commonjs-export.ts b/test/fixtures/typescript/cts/test-commonjs-export.ts new file mode 100644 index 00000000000000..27d36ea1fe4347 --- /dev/null +++ b/test/fixtures/typescript/cts/test-commonjs-export.ts @@ -0,0 +1,3 @@ +const foo: string = 'Hello, TypeScript!'; + +module.exports = { foo }; diff --git a/test/fixtures/typescript/cts/test-cts-but-module-syntax.cts b/test/fixtures/typescript/cts/test-cts-but-module-syntax.cts new file mode 100644 index 00000000000000..87c784633d267e --- /dev/null +++ b/test/fixtures/typescript/cts/test-cts-but-module-syntax.cts @@ -0,0 +1,5 @@ +import util from 'node:util'; + +export const text: string = 'Hello, TypeScript!'; + +console.log(util.styleText(['bold', 'red'], text)); diff --git a/test/fixtures/typescript/cts/test-cts-export-foo.cts b/test/fixtures/typescript/cts/test-cts-export-foo.cts new file mode 100644 index 00000000000000..27d36ea1fe4347 --- /dev/null +++ b/test/fixtures/typescript/cts/test-cts-export-foo.cts @@ -0,0 +1,3 @@ +const foo: string = 'Hello, TypeScript!'; + +module.exports = { foo }; diff --git a/test/fixtures/typescript/cts/test-cts-node_modules.cts b/test/fixtures/typescript/cts/test-cts-node_modules.cts new file mode 100644 index 00000000000000..d27bb40c6d6419 --- /dev/null +++ b/test/fixtures/typescript/cts/test-cts-node_modules.cts @@ -0,0 +1,5 @@ +const { foo } = require('foo'); + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/cts/test-extensionless-require.ts b/test/fixtures/typescript/cts/test-extensionless-require.ts new file mode 100644 index 00000000000000..20e2ffdc7cfef4 --- /dev/null +++ b/test/fixtures/typescript/cts/test-extensionless-require.ts @@ -0,0 +1,3 @@ +const { foo } = require('./test-commonjs-export'); + +console.log(foo); diff --git a/test/fixtures/typescript/cts/test-mts-node_modules.cts b/test/fixtures/typescript/cts/test-mts-node_modules.cts new file mode 100644 index 00000000000000..125d98571adff1 --- /dev/null +++ b/test/fixtures/typescript/cts/test-mts-node_modules.cts @@ -0,0 +1,5 @@ +const { baz } = require('baz'); + +interface Foo { }; + +console.log(baz); diff --git a/test/fixtures/typescript/cts/test-require-commonjs.cts b/test/fixtures/typescript/cts/test-require-commonjs.cts new file mode 100644 index 00000000000000..ee0f4410d76a8c --- /dev/null +++ b/test/fixtures/typescript/cts/test-require-commonjs.cts @@ -0,0 +1,5 @@ +const { foo } = require('./test-cts-export-foo.cts'); + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/cts/test-require-mts-module.cts b/test/fixtures/typescript/cts/test-require-mts-module.cts new file mode 100644 index 00000000000000..0b40b3b566bd13 --- /dev/null +++ b/test/fixtures/typescript/cts/test-require-mts-module.cts @@ -0,0 +1,5 @@ +const { foo } = require('../mts/test-mts-export-foo.mts'); + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/cts/test-require-ts-file.cts b/test/fixtures/typescript/cts/test-require-ts-file.cts new file mode 100644 index 00000000000000..08015a6cdcb2fc --- /dev/null +++ b/test/fixtures/typescript/cts/test-require-ts-file.cts @@ -0,0 +1,5 @@ +const { foo } = require('./test-commonjs-export.ts'); + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/cts/test-ts-node_modules.cts b/test/fixtures/typescript/cts/test-ts-node_modules.cts new file mode 100644 index 00000000000000..c565a00a9b57e5 --- /dev/null +++ b/test/fixtures/typescript/cts/test-ts-node_modules.cts @@ -0,0 +1,5 @@ +const { bar } = require('bar'); + +interface Foo { }; + +console.log(bar); diff --git a/test/fixtures/typescript/mts/node_modules/bar/bar.ts b/test/fixtures/typescript/mts/node_modules/bar/bar.ts new file mode 100644 index 00000000000000..a0f11c62ba8950 --- /dev/null +++ b/test/fixtures/typescript/mts/node_modules/bar/bar.ts @@ -0,0 +1,5 @@ +const bar: string = 'Hello, TypeScript!' + +module.exports = { + bar +}; diff --git a/test/fixtures/typescript/mts/node_modules/bar/package.json b/test/fixtures/typescript/mts/node_modules/bar/package.json new file mode 100644 index 00000000000000..18ef424398b114 --- /dev/null +++ b/test/fixtures/typescript/mts/node_modules/bar/package.json @@ -0,0 +1,13 @@ +{ + "name": "bar", + "version": "1.0.0", + "main": "bar.ts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} \ No newline at end of file diff --git a/test/fixtures/typescript/mts/node_modules/baz/baz.mts b/test/fixtures/typescript/mts/node_modules/baz/baz.mts new file mode 100644 index 00000000000000..746109acf8cede --- /dev/null +++ b/test/fixtures/typescript/mts/node_modules/baz/baz.mts @@ -0,0 +1 @@ +export const baz: string = 'Hello, TypeScript!'; diff --git a/test/fixtures/typescript/mts/node_modules/baz/package.json b/test/fixtures/typescript/mts/node_modules/baz/package.json new file mode 100644 index 00000000000000..e44b74aa1824ca --- /dev/null +++ b/test/fixtures/typescript/mts/node_modules/baz/package.json @@ -0,0 +1,13 @@ +{ + "name": "baz", + "version": "1.0.0", + "main": "baz.mts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} \ No newline at end of file diff --git a/test/fixtures/typescript/mts/node_modules/foo/foo.cts b/test/fixtures/typescript/mts/node_modules/foo/foo.cts new file mode 100644 index 00000000000000..c7dbc8680aa5a1 --- /dev/null +++ b/test/fixtures/typescript/mts/node_modules/foo/foo.cts @@ -0,0 +1,5 @@ +const foo: string = 'Hello, TypeScript!'; + +module.exports = { + foo +}; diff --git a/test/fixtures/typescript/mts/node_modules/foo/package.json b/test/fixtures/typescript/mts/node_modules/foo/package.json new file mode 100644 index 00000000000000..3036e618506c3b --- /dev/null +++ b/test/fixtures/typescript/mts/node_modules/foo/package.json @@ -0,0 +1,14 @@ +{ + "name": "foo", + "version": "1.0.0", + "type": "commonjs", + "main": "foo.cts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} \ No newline at end of file diff --git a/test/fixtures/typescript/mts/test-cts-node_modules.mts b/test/fixtures/typescript/mts/test-cts-node_modules.mts new file mode 100644 index 00000000000000..410daa11b6881a --- /dev/null +++ b/test/fixtures/typescript/mts/test-cts-node_modules.mts @@ -0,0 +1,5 @@ +import { foo } from 'foo'; + +interface Foo { }; + +console.log(foo); diff --git a/test/fixtures/typescript/mts/test-import-commonjs.mts b/test/fixtures/typescript/mts/test-import-commonjs.mts new file mode 100644 index 00000000000000..1a18d4f4d93885 --- /dev/null +++ b/test/fixtures/typescript/mts/test-import-commonjs.mts @@ -0,0 +1,5 @@ +import { foo } from '../cts/test-cts-export-foo.cts'; + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/mts/test-import-module.mts b/test/fixtures/typescript/mts/test-import-module.mts new file mode 100644 index 00000000000000..24e15fedae4edb --- /dev/null +++ b/test/fixtures/typescript/mts/test-import-module.mts @@ -0,0 +1,5 @@ +import { foo } from './test-mts-export-foo.mts'; + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/mts/test-import-ts-file.mts b/test/fixtures/typescript/mts/test-import-ts-file.mts new file mode 100644 index 00000000000000..2cfc1a9156dccc --- /dev/null +++ b/test/fixtures/typescript/mts/test-import-ts-file.mts @@ -0,0 +1,5 @@ +import { foo } from './test-module-export.ts'; + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/mts/test-module-export.ts b/test/fixtures/typescript/mts/test-module-export.ts new file mode 100644 index 00000000000000..4ed5c6cfde6867 --- /dev/null +++ b/test/fixtures/typescript/mts/test-module-export.ts @@ -0,0 +1 @@ +export const foo: string = 'Hello, TypeScript!'; diff --git a/test/fixtures/typescript/mts/test-mts-but-commonjs-syntax.mts b/test/fixtures/typescript/mts/test-mts-but-commonjs-syntax.mts new file mode 100644 index 00000000000000..bb2973190dd616 --- /dev/null +++ b/test/fixtures/typescript/mts/test-mts-but-commonjs-syntax.mts @@ -0,0 +1,9 @@ +const util = require('node:util'); + +const text: string = 'Hello, TypeScript!'; + +console.log(util.styleText(['bold', 'red'], text)); + +module.exports = { + text +}; diff --git a/test/fixtures/typescript/mts/test-mts-export-foo.mts b/test/fixtures/typescript/mts/test-mts-export-foo.mts new file mode 100644 index 00000000000000..4ed5c6cfde6867 --- /dev/null +++ b/test/fixtures/typescript/mts/test-mts-export-foo.mts @@ -0,0 +1 @@ +export const foo: string = 'Hello, TypeScript!'; diff --git a/test/fixtures/typescript/mts/test-mts-node_modules.mts b/test/fixtures/typescript/mts/test-mts-node_modules.mts new file mode 100644 index 00000000000000..8c49583f2046f0 --- /dev/null +++ b/test/fixtures/typescript/mts/test-mts-node_modules.mts @@ -0,0 +1,5 @@ +import { baz } from 'baz'; + +interface Foo {}; + +console.log(baz); diff --git a/test/fixtures/typescript/mts/test-ts-node_modules.mts b/test/fixtures/typescript/mts/test-ts-node_modules.mts new file mode 100644 index 00000000000000..fe4f3b743478de --- /dev/null +++ b/test/fixtures/typescript/mts/test-ts-node_modules.mts @@ -0,0 +1,5 @@ +import { bar } from 'bar'; + +interface Foo {}; + +console.log(bar); diff --git a/test/fixtures/typescript/ts/node_modules/bar/bar.ts b/test/fixtures/typescript/ts/node_modules/bar/bar.ts new file mode 100644 index 00000000000000..7e3a197411ce7e --- /dev/null +++ b/test/fixtures/typescript/ts/node_modules/bar/bar.ts @@ -0,0 +1,3 @@ +const bar: string = 'Hello, TypeScript!'; + +module.exports = { bar }; \ No newline at end of file diff --git a/test/fixtures/typescript/ts/node_modules/bar/package.json b/test/fixtures/typescript/ts/node_modules/bar/package.json new file mode 100644 index 00000000000000..ff1ab7524e4743 --- /dev/null +++ b/test/fixtures/typescript/ts/node_modules/bar/package.json @@ -0,0 +1,13 @@ +{ + "name": "bar", + "version": "1.0.0", + "main": "bar.ts", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/test/fixtures/typescript/ts/node_modules/foo/foo.js b/test/fixtures/typescript/ts/node_modules/foo/foo.js new file mode 100644 index 00000000000000..a4b27706c7b6e9 --- /dev/null +++ b/test/fixtures/typescript/ts/node_modules/foo/foo.js @@ -0,0 +1 @@ +export const foo = "Hello, TypeScript!" diff --git a/test/fixtures/typescript/ts/node_modules/foo/package.json b/test/fixtures/typescript/ts/node_modules/foo/package.json new file mode 100644 index 00000000000000..7182125d8b0e14 --- /dev/null +++ b/test/fixtures/typescript/ts/node_modules/foo/package.json @@ -0,0 +1,14 @@ +{ + "name": "foo", + "version": "1.0.0", + "type": "module", + "main": "foo.js", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/test/fixtures/typescript/ts/test-commonjs-parsing.ts b/test/fixtures/typescript/ts/test-commonjs-parsing.ts new file mode 100644 index 00000000000000..bb2973190dd616 --- /dev/null +++ b/test/fixtures/typescript/ts/test-commonjs-parsing.ts @@ -0,0 +1,9 @@ +const util = require('node:util'); + +const text: string = 'Hello, TypeScript!'; + +console.log(util.styleText(['bold', 'red'], text)); + +module.exports = { + text +}; diff --git a/test/fixtures/typescript/ts/test-enums.ts b/test/fixtures/typescript/ts/test-enums.ts new file mode 100644 index 00000000000000..52fc4cfebd54e1 --- /dev/null +++ b/test/fixtures/typescript/ts/test-enums.ts @@ -0,0 +1,13 @@ +enum Color { + Red, + Green, + Blue, +} + +console.log(Color.Red); +console.log(Color.Green); +console.log(Color.Blue); + +console.log(Color[0]); +console.log(Color[1]); +console.log(Color[2]); diff --git a/test/fixtures/typescript/ts/test-experimental-decorators.ts b/test/fixtures/typescript/ts/test-experimental-decorators.ts new file mode 100644 index 00000000000000..073ceb0fbb1ff6 --- /dev/null +++ b/test/fixtures/typescript/ts/test-experimental-decorators.ts @@ -0,0 +1,14 @@ +function sealed(constructor: Function) { + Object.seal(constructor); + Object.seal(constructor.prototype); +} + +@sealed +class BugReport { + type = "report"; + title: string; + + constructor(t: string) { + this.title = t; + } +} diff --git a/test/fixtures/typescript/ts/test-export-foo.ts b/test/fixtures/typescript/ts/test-export-foo.ts new file mode 100644 index 00000000000000..3b94da93c43461 --- /dev/null +++ b/test/fixtures/typescript/ts/test-export-foo.ts @@ -0,0 +1 @@ +export const foo: string = "Hello, TypeScript!"; diff --git a/test/fixtures/typescript/ts/test-import-foo.ts b/test/fixtures/typescript/ts/test-import-foo.ts new file mode 100644 index 00000000000000..da66e19ef20e3b --- /dev/null +++ b/test/fixtures/typescript/ts/test-import-foo.ts @@ -0,0 +1,5 @@ +import { foo } from './test-export-foo.ts'; + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/ts/test-import-no-extension.ts b/test/fixtures/typescript/ts/test-import-no-extension.ts new file mode 100644 index 00000000000000..738ceddbaade44 --- /dev/null +++ b/test/fixtures/typescript/ts/test-import-no-extension.ts @@ -0,0 +1,5 @@ +import { foo } from './test-no-extensions'; + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/ts/test-import-no-type-keyword.ts b/test/fixtures/typescript/ts/test-import-no-type-keyword.ts new file mode 100644 index 00000000000000..278eb3e032931f --- /dev/null +++ b/test/fixtures/typescript/ts/test-import-no-type-keyword.ts @@ -0,0 +1,7 @@ +import { MyType } from './test-types.d.ts'; + +const myVar: MyType = { + foo: 'Hello, TypeScript!' +}; + +console.log(myVar.foo); diff --git a/test/fixtures/typescript/ts/test-import-ts-node-modules.ts b/test/fixtures/typescript/ts/test-import-ts-node-modules.ts new file mode 100644 index 00000000000000..864987c7cf0f60 --- /dev/null +++ b/test/fixtures/typescript/ts/test-import-ts-node-modules.ts @@ -0,0 +1,5 @@ +import { bar } from 'bar'; + +interface Bar {}; + +console.log(bar); diff --git a/test/fixtures/typescript/ts/test-import-types.ts b/test/fixtures/typescript/ts/test-import-types.ts new file mode 100644 index 00000000000000..ec32b315636a73 --- /dev/null +++ b/test/fixtures/typescript/ts/test-import-types.ts @@ -0,0 +1,7 @@ +import type { MyType } from './test-types.d.ts'; + +const myVar: MyType = { + foo: 'Hello, TypeScript!' +}; + +console.log(myVar.foo); diff --git a/test/fixtures/typescript/ts/test-module-typescript.ts b/test/fixtures/typescript/ts/test-module-typescript.ts new file mode 100644 index 00000000000000..145910853cbdfa --- /dev/null +++ b/test/fixtures/typescript/ts/test-module-typescript.ts @@ -0,0 +1,5 @@ +import util from 'node:util'; + +export const text: string = 'Hello, TypeScript!'; + +console.log(util.styleText("red", text)); diff --git a/test/fixtures/typescript/ts/test-namespaces.ts b/test/fixtures/typescript/ts/test-namespaces.ts new file mode 100644 index 00000000000000..eb4e4b3961402c --- /dev/null +++ b/test/fixtures/typescript/ts/test-namespaces.ts @@ -0,0 +1,9 @@ +/// +namespace Validation { + const lettersRegexp = /^[A-Za-z]+$/; + export class LettersOnlyValidator { + isAcceptable(s: string) { + return lettersRegexp.test(s); + } + } +} diff --git a/test/fixtures/typescript/ts/test-no-extensions.ts b/test/fixtures/typescript/ts/test-no-extensions.ts new file mode 100644 index 00000000000000..4ed5c6cfde6867 --- /dev/null +++ b/test/fixtures/typescript/ts/test-no-extensions.ts @@ -0,0 +1 @@ +export const foo: string = 'Hello, TypeScript!'; diff --git a/test/fixtures/typescript/ts/test-require-cts.ts b/test/fixtures/typescript/ts/test-require-cts.ts new file mode 100644 index 00000000000000..46efff06169fe8 --- /dev/null +++ b/test/fixtures/typescript/ts/test-require-cts.ts @@ -0,0 +1,5 @@ +const { foo } = require('../cts/test-cts-export-foo.cts'); + +interface Foo {}; + +console.log(foo); diff --git a/test/fixtures/typescript/ts/test-require-module.ts b/test/fixtures/typescript/ts/test-require-module.ts new file mode 100644 index 00000000000000..52dc9d4c631e35 --- /dev/null +++ b/test/fixtures/typescript/ts/test-require-module.ts @@ -0,0 +1,3 @@ +const { foo } = require('../mts/test-mts-export-foo.mts'); + +console.log(foo); diff --git a/test/fixtures/typescript/ts/test-require-mts.ts b/test/fixtures/typescript/ts/test-require-mts.ts new file mode 100644 index 00000000000000..2048760b78b68e --- /dev/null +++ b/test/fixtures/typescript/ts/test-require-mts.ts @@ -0,0 +1,5 @@ +const { foo } = require('../mts/test-mts-export-foo.mts'); + +interface Foo { }; + +console.log(foo); diff --git a/test/fixtures/typescript/ts/test-types.d.ts b/test/fixtures/typescript/ts/test-types.d.ts new file mode 100644 index 00000000000000..d048d12d1fdb62 --- /dev/null +++ b/test/fixtures/typescript/ts/test-types.d.ts @@ -0,0 +1,3 @@ +export type MyType = { + foo: string; +}; diff --git a/test/fixtures/typescript/ts/test-typescript-node-modules.ts b/test/fixtures/typescript/ts/test-typescript-node-modules.ts new file mode 100644 index 00000000000000..8c16fd8873029e --- /dev/null +++ b/test/fixtures/typescript/ts/test-typescript-node-modules.ts @@ -0,0 +1,3 @@ +import { foo } from 'foo'; + +console.log(foo); diff --git a/test/fixtures/typescript/ts/test-typescript.ts b/test/fixtures/typescript/ts/test-typescript.ts new file mode 100644 index 00000000000000..41338c9845eba6 --- /dev/null +++ b/test/fixtures/typescript/ts/test-typescript.ts @@ -0,0 +1,5 @@ +const str: string = "Hello, TypeScript!"; +interface Foo { + bar: string; +} +console.log(str); diff --git a/test/fixtures/typescript/ts/test-whitespacing.ts b/test/fixtures/typescript/ts/test-whitespacing.ts new file mode 100644 index 00000000000000..63bdd1648f9303 --- /dev/null +++ b/test/fixtures/typescript/ts/test-whitespacing.ts @@ -0,0 +1,5 @@ +interface Foo { + bar: string; +} + +throw new Error("Whitespacing"); diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index 108ea9d67d7c70..41186c1a1afe08 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -24,6 +24,7 @@ const expected_keys = [ 'ada', 'cjs_module_lexer', 'nbytes', + 'amaro', ]; const hasUndici = process.config.variables.node_builtin_shareable_builtins.includes('deps/undici/undici.js'); diff --git a/tools/dep_updaters/update-amaro.sh b/tools/dep_updaters/update-amaro.sh new file mode 100755 index 00000000000000..0169e9304bc0a4 --- /dev/null +++ b/tools/dep_updaters/update-amaro.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +# Shell script to update amaro in the source tree to the latest release. + +# This script must be in the tools directory when it runs because it uses the +# script source file path to determine directories to work in. + +set -ex + +BASE_DIR=$(cd "$(dirname "$0")/../.." && pwd) +[ -z "$NODE" ] && NODE="$BASE_DIR/out/Release/node" +[ -x "$NODE" ] || NODE=$(command -v node) +DEPS_DIR="$BASE_DIR/deps" +NPM="$DEPS_DIR/npm/bin/npm-cli.js" + +# shellcheck disable=SC1091 +. "$BASE_DIR/tools/dep_updaters/utils.sh" + +NEW_VERSION=$("$NODE" "$NPM" view amaro dist-tags.latest) + +CURRENT_VERSION=$("$NODE" -p "require('./deps/amaro/package.json').version") + +# This function exit with 0 if new version and current version are the same +compare_dependency_version "amaro" "$NEW_VERSION" "$CURRENT_VERSION" + +cd "$( dirname "$0" )/../.." || exit + +echo "Making temporary workspace..." + +WORKSPACE=$(mktemp -d 2> /dev/null || mktemp -d -t 'tmp') + +cleanup () { + EXIT_CODE=$? + [ -d "$WORKSPACE" ] && rm -rf "$WORKSPACE" + exit $EXIT_CODE +} + +trap cleanup INT TERM EXIT + +cd "$WORKSPACE" + +echo "Fetching amaro source archive..." + +"$NODE" "$NPM" pack "amaro@$NEW_VERSION" + +amaro_TGZ="amaro-$NEW_VERSION.tgz" + +log_and_verify_sha256sum "amaro" "$amaro_TGZ" + +cp ./* "$DEPS_DIR/amaro/LICENSE" + +rm -r "$DEPS_DIR/amaro"/* + +tar -xf "$amaro_TGZ" + +cd package + +rm -rf node_modules + +mv ./* "$DEPS_DIR/amaro" + +# update version information in src/undici_version.h +cat > "$ROOT/src/amaro_version.h" < Date: Wed, 24 Jul 2024 09:48:40 -0700 Subject: [PATCH 075/120] doc, test: tracing channel hasSubscribers getter follow up work for https://github.com/nodejs/node/pull/51915 PR-URL: https://github.com/nodejs/node/pull/52908 Reviewed-By: Stephen Belanger Reviewed-By: James M Snell --- doc/api/diagnostics_channel.md | 37 ++++++++++++++ ...channel-tracing-channel-has-subscribers.js | 51 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 test/parallel/test-diagnostics-channel-tracing-channel-has-subscribers.js diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 0ec452bec3dbdb..603d41d532bd5b 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -977,6 +977,43 @@ channels.asyncStart.bindStore(myStore, (data) => { }); ``` +#### `tracingChannel.hasSubscribers` + + + +> Stability: 1 - Experimental + +* Returns: {boolean} `true` if any of the individual channels has a subscriber, + `false` if not. + +This is a helper method available on a [`TracingChannel`][] instance to check if +any of the [TracingChannel Channels][] have subscribers. A `true` is returned if +any of them have at least one subscriber, a `false` is returned otherwise. + +```mjs +import diagnostics_channel from 'node:diagnostics_channel'; + +const channels = diagnostics_channel.tracingChannel('my-channel'); + +if (channels.hasSubscribers) { + // Do something +} +``` + +```cjs +const diagnostics_channel = require('node:diagnostics_channel'); + +const channels = diagnostics_channel.tracingChannel('my-channel'); + +if (channels.hasSubscribers) { + // Do something +} +``` + ### TracingChannel Channels A TracingChannel is a collection of several diagnostics\_channels representing diff --git a/test/parallel/test-diagnostics-channel-tracing-channel-has-subscribers.js b/test/parallel/test-diagnostics-channel-tracing-channel-has-subscribers.js new file mode 100644 index 00000000000000..2ae25d9848c82c --- /dev/null +++ b/test/parallel/test-diagnostics-channel-tracing-channel-has-subscribers.js @@ -0,0 +1,51 @@ +'use strict'; + +const common = require('../common'); +const dc = require('diagnostics_channel'); +const assert = require('assert'); + +const handler = common.mustNotCall(); + +{ + const handlers = { + start: common.mustNotCall() + }; + + const channel = dc.tracingChannel('test'); + + assert.strictEqual(channel.hasSubscribers, false); + + channel.subscribe(handlers); + assert.strictEqual(channel.hasSubscribers, true); + + channel.unsubscribe(handlers); + assert.strictEqual(channel.hasSubscribers, false); + + channel.start.subscribe(handler); + assert.strictEqual(channel.hasSubscribers, true); + + channel.start.unsubscribe(handler); + assert.strictEqual(channel.hasSubscribers, false); +} + +{ + const handlers = { + asyncEnd: common.mustNotCall() + }; + + const channel = dc.tracingChannel('test'); + + assert.strictEqual(channel.hasSubscribers, false); + + channel.subscribe(handlers); + assert.strictEqual(channel.hasSubscribers, true); + + channel.unsubscribe(handlers); + assert.strictEqual(channel.hasSubscribers, false); + + channel.asyncEnd.subscribe(handler); + assert.strictEqual(channel.hasSubscribers, true); + + channel.asyncEnd.unsubscribe(handler); + assert.strictEqual(channel.hasSubscribers, false); +} From 117631022691faa779c5b42ea38a1f20b719e35e Mon Sep 17 00:00:00 2001 From: Cheng Date: Thu, 25 Jul 2024 11:39:01 +0900 Subject: [PATCH 076/120] deps: add gn build files for ncrypto PR-URL: https://github.com/nodejs/node/pull/53940 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- deps/ncrypto/unofficial.gni | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 deps/ncrypto/unofficial.gni diff --git a/deps/ncrypto/unofficial.gni b/deps/ncrypto/unofficial.gni new file mode 100644 index 00000000000000..2d4edb079b241f --- /dev/null +++ b/deps/ncrypto/unofficial.gni @@ -0,0 +1,31 @@ +# This file is used by GN for building, which is NOT the build system used for +# building official binaries. +# Please edit the gyp files if you are making changes to build system. + +import("../../node.gni") +import("$node_v8_path/gni/v8.gni") + +# The actual configurations are put inside a template in unofficial.gni to +# prevent accidental edits from contributors. +template("ncrypto_gn_build") { + config("ncrypto_config") { + include_dirs = [ "." ] + cflags = [ + "-Wno-deprecated-declarations", + "-Wno-pessimizing-move", + "-Wno-shadow", + ] + } + + gypi_values = exec_script("../../tools/gypi_to_gn.py", + [ rebase_path("ncrypto.gyp") ], + "scope", + [ "ncrypto.gyp" ]) + + source_set(target_name) { + forward_variables_from(invoker, "*") + public_configs = [ ":ncrypto_config" ] + sources = gypi_values.ncrypto_sources + deps = [ "../openssl" ] + } +} From 21098856deb087cd76d7ff0c423440527b61f843 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Thu, 25 Jul 2024 04:23:06 -0400 Subject: [PATCH 077/120] meta: move tsc member to emeritus Based on TSC discussion. Signed-off-by: Michael Dawson PR-URL: https://github.com/nodejs/node/pull/54029 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Antoine du Hamel Reviewed-By: Moshe Atlow Reviewed-By: Ruben Bridgewater Reviewed-By: Richard Lau Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli Reviewed-By: Luigi Pinca Reviewed-By: Marco Ippolito --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc044f5773086a..8cdd6422082120 100644 --- a/README.md +++ b/README.md @@ -166,8 +166,6 @@ For information about the governance of the Node.js project, see **Antoine du Hamel** <> (he/him) * [anonrig](https://github.com/anonrig) - **Yagiz Nizipli** <> (he/him) -* [apapirovski](https://github.com/apapirovski) - - **Anatoli Papirovski** <> (he/him) * [benjamingr](https://github.com/benjamingr) - **Benjamin Gruenbaum** <> * [BridgeAR](https://github.com/BridgeAR) - @@ -207,6 +205,8 @@ For information about the governance of the Node.js project, see #### TSC regular members +* [apapirovski](https://github.com/apapirovski) - + **Anatoli Papirovski** <> (he/him) * [BethGriggs](https://github.com/BethGriggs) - **Beth Griggs** <> (she/her) * [bnoordhuis](https://github.com/bnoordhuis) - From f4a7ac5e1842c0f4629a0bebfda38f2502a2ee41 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 25 Jul 2024 12:08:09 +0200 Subject: [PATCH 078/120] deps: V8: cherry-pick 35888fee7bba Original commit message: [base] fix builds with GCC 12 on certain Linux distributions With GCC 12 on certain Linux distributions (at least Debian 12, Alpine 3.18, Fedora 37, that ships GCC 12.2), std::is_trivially_copyable is broken and as a result, V8 fails to compile. This patch uses the same polyfill on MSVC to make it compile with GCC 12.2. See https://github.com/nodejs/node/pull/45427 for more context. Refs: https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=aeba3e009b0abfccaf01797556445dbf891cc8dc Change-Id: Ie0ab1bb1ec105bacbd80b341adf7dbd8569f031f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5679182 Commit-Queue: Joyee Cheung Reviewed-by: Nico Hartmann Cr-Commit-Position: refs/heads/main@{#95181} Refs: https://github.com/v8/v8/commit/35888fee7bbaaaf1f02ccc88a95c9a336fc790bc PR-URL: https://github.com/nodejs/node/pull/53728 Refs: https://github.com/nodejs/node/pull/45427 Refs: https://github.com/nodejs/help/issues/4406 Refs: https://github.com/nodejs/node/issues/53633 Refs: https://github.com/nodejs/help/issues/4430 Reviewed-By: Richard Lau Reviewed-By: Santiago Gimeno Reviewed-By: Daeyeon Jeong Reviewed-By: Jiawen Geng Reviewed-By: Luigi Pinca Reviewed-By: Chengzhong Wu Reviewed-By: James M Snell --- common.gypi | 2 +- deps/v8/src/base/macros.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/common.gypi b/common.gypi index 6cc1285ba52de1..32a298414b5392 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.17', + 'v8_embedder_string': '-node.18', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/base/macros.h b/deps/v8/src/base/macros.h index 210885af3c3c0a..d404b6120ab86f 100644 --- a/deps/v8/src/base/macros.h +++ b/deps/v8/src/base/macros.h @@ -173,7 +173,7 @@ namespace base { // base::is_trivially_copyable will differ for these cases. template struct is_trivially_copyable { -#if V8_CC_MSVC +#if V8_CC_MSVC || (__GNUC__ == 12 && __GNUC_MINOR__ <= 2) // Unfortunately, MSVC 2015 is broken in that std::is_trivially_copyable can // be false even though it should be true according to the standard. // (status at 2018-02-26, observed on the msvc waterfall bot). @@ -181,6 +181,11 @@ struct is_trivially_copyable { // intended, so we reimplement this according to the standard. // See also https://developercommunity.visualstudio.com/content/problem/ // 170883/msvc-type-traits-stdis-trivial-is-bugged.html. + // + // GCC 12.1 and 12.2 are broken too, they are shipped by some stable Linux + // distributions, so the same polyfill is also used. + // See + // https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=aeba3e009b0abfccaf01797556445dbf891cc8dc static constexpr bool value = // Copy constructor is trivial or deleted. (std::is_trivially_copy_constructible::value || From 7c417c6cf41fc3d8652fc979fc2f01c4f401ef04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20A=C3=A7acak?= <110401522+huseyinacacak-janea@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:08:16 +0300 Subject: [PATCH 079/120] build: avoid compiling with VS v17.10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs: https://github.com/nodejs/build/issues/3739 PR-URL: https://github.com/nodejs/node/pull/53863 Reviewed-By: Michaël Zasso Reviewed-By: Yagiz Nizipli Reviewed-By: Gerhard Stöbich --- vcbuild.bat | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vcbuild.bat b/vcbuild.bat index 5853c74c9e5dfb..428577c5c3d1e9 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -281,6 +281,14 @@ goto exit :msbuild-found +@rem Visual Studio v17.10 has a bug that causes the build to fail. +@rem Check if the version is v17.10 and exit if it is. +echo %VSCMD_VER% | findstr /b /c:"17.10" >nul +if %errorlevel% neq 1 ( + echo Node.js doesn't compile with Visual Studio 17.10 Please use a different version. + goto exit +) + @rem check if the clang-cl build is requested if not defined clang_cl goto clang-skip @rem x64 is hard coded as it is used for both cross and native compilation. From 546dab29c1b6e39f1f460bbe2a5fb620e2e26b20 Mon Sep 17 00:00:00 2001 From: HEESEUNG Date: Thu, 25 Jul 2024 21:12:35 +0900 Subject: [PATCH 080/120] lib: optimize copyError with ObjectAssign in primordials optimized the copyError function by using ObjectAssign from primordials. this change replaces the for-loop with ObjectAssign, which improves memory usage and performance. this change updates the copyError function in internal/assert.js to use ObjectAssign for copying properties. PR-URL: https://github.com/nodejs/node/pull/53999 Reviewed-By: James M Snell Reviewed-By: Antoine du Hamel Reviewed-By: Daeyeon Jeong --- lib/internal/assert/assertion_error.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/internal/assert/assertion_error.js b/lib/internal/assert/assertion_error.js index f12243790b0506..5d8c45040af0fe 100644 --- a/lib/internal/assert/assertion_error.js +++ b/lib/internal/assert/assertion_error.js @@ -6,9 +6,9 @@ const { Error, ErrorCaptureStackTrace, MathMax, + ObjectAssign, ObjectDefineProperty, ObjectGetPrototypeOf, - ObjectKeys, String, StringPrototypeEndsWith, StringPrototypeRepeat, @@ -46,11 +46,7 @@ const kReadableOperator = { const kMaxShortLength = 12; function copyError(source) { - const keys = ObjectKeys(source); - const target = { __proto__: ObjectGetPrototypeOf(source) }; - for (const key of keys) { - target[key] = source[key]; - } + const target = ObjectAssign({ __proto__: ObjectGetPrototypeOf(source) }, source); ObjectDefineProperty(target, 'message', { __proto__: null, value: source.message }); return target; } From fc67abd97ec899771dd944cd9e21eac8c7b3422a Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Thu, 25 Jul 2024 08:12:41 -0400 Subject: [PATCH 081/120] test: mark 'test/parallel/test-sqlite.js' as flaky The current test is large and can time out. It should be split into multiple smaller tests as done in #54014. However, that approach appears to change GC behavior such that the database files are not cleaned up quickly enough on Windows. Forcing any unfinalized SQL statements to be GC'ed appears to fix the problem. Mark the original test as flaky until the necessary code changes are made. PR-URL: https://github.com/nodejs/node/pull/54031 Reviewed-By: Moshe Atlow Reviewed-By: Jake Yuesong Li Reviewed-By: Stefan Stojanovic Reviewed-By: Richard Lau --- test/parallel/parallel.status | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index da93f6ca89ac83..831f543f8e01d2 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -19,6 +19,9 @@ test-fs-read-stream-concurrent-reads: PASS, FLAKY # https://github.com/nodejs/node/issues/52630 test-error-serdes: PASS, FLAKY +# https://github.com/nodejs/node/issues/54006 +test-sqlite: PASS, FLAKY + [$system==win32] # Windows on x86 From ee82f224ff6fdcd81301e7959d12ee055e805a98 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 25 Jul 2024 05:28:23 -0700 Subject: [PATCH 082/120] src: remove redundant RsaPointer (use RSAPointer) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/54003 Reviewed-By: Yagiz Nizipli Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca --- src/crypto/crypto_rsa.cc | 2 +- src/crypto/crypto_util.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc index 23b2b8c56dec8a..100f94460686bd 100644 --- a/src/crypto/crypto_rsa.cc +++ b/src/crypto/crypto_rsa.cc @@ -441,7 +441,7 @@ std::shared_ptr ImportJWKRsaKey( KeyType type = d_value->IsString() ? kKeyTypePrivate : kKeyTypePublic; - RsaPointer rsa(RSA_new()); + RSAPointer rsa(RSA_new()); ByteSource n = ByteSource::FromEncodedString(env, n_value.As()); ByteSource e = ByteSource::FromEncodedString(env, e_value.As()); diff --git a/src/crypto/crypto_util.h b/src/crypto/crypto_util.h index a98d17787887d5..c7aeaa7753c90a 100644 --- a/src/crypto/crypto_util.h +++ b/src/crypto/crypto_util.h @@ -75,7 +75,6 @@ using DHPointer = ncrypto::DHPointer; using ECDSASigPointer = ncrypto::ECDSASigPointer; using HMACCtxPointer = ncrypto::HMACCtxPointer; using CipherCtxPointer = ncrypto::CipherCtxPointer; -using RsaPointer = ncrypto::RSAPointer; using DsaPointer = ncrypto::DSAPointer; using DsaSigPointer = ncrypto::DSASigPointer; From c1634c721314532a9ab71b95e42b65e3192b268a Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Thu, 25 Jul 2024 14:58:08 +0200 Subject: [PATCH 083/120] doc: correct typescript stdin support PR-URL: https://github.com/nodejs/node/pull/54036 Fixes: https://github.com/nodejs/node/issues/54035 Reviewed-By: Geoffrey Booth Reviewed-By: Yagiz Nizipli Reviewed-By: Matteo Collina --- doc/api/typescript.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/typescript.md b/doc/api/typescript.md index 5f5e8b2552333e..69711d36a39349 100644 --- a/doc/api/typescript.md +++ b/doc/api/typescript.md @@ -123,10 +123,10 @@ import { fn, FnParams } from './fn.ts'; ### Non-file forms of input -Type stripping can be enabled for `--eval` and STDIN input. The module system +Type stripping can be enabled for `--eval`. The module system will be determined by `--input-type`, as it is for JavaScript. -TypeScript syntax is unsupported in the REPL, `--print`, `--check`, and +TypeScript syntax is unsupported in the REPL, STDIN input, `--print`, `--check`, and `inspect`. ### Source maps From dd3c66be0a8fe366e6e8a0bccbc1fbbdfcf2b5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Thu, 25 Jul 2024 16:54:37 +0200 Subject: [PATCH 084/120] src: simplify AESCipherTraits::AdditionalConfig Instead of a giant switch statement and a lot of duplicate code, add the NID and the block cipher mode of operation to the VARIANTS list and use those fields to perform configuration appropriately. PR-URL: https://github.com/nodejs/node/pull/53890 Reviewed-By: Yagiz Nizipli --- src/crypto/crypto_aes.cc | 99 +++++++++++----------------------------- src/crypto/crypto_aes.h | 35 ++++++++------ 2 files changed, 48 insertions(+), 86 deletions(-) diff --git a/src/crypto/crypto_aes.cc b/src/crypto/crypto_aes.cc index 6ec43c0a461e78..774030d408711c 100644 --- a/src/crypto/crypto_aes.cc +++ b/src/crypto/crypto_aes.cc @@ -476,83 +476,38 @@ Maybe AESCipherTraits::AdditionalConfig( params->variant = static_cast(args[offset].As()->Value()); + AESCipherMode cipher_op_mode; int cipher_nid; +#define V(name, _, mode, nid) \ + case kKeyVariantAES_##name: { \ + cipher_op_mode = mode; \ + cipher_nid = nid; \ + break; \ + } switch (params->variant) { - case kKeyVariantAES_CTR_128: - if (!ValidateIV(env, mode, args[offset + 1], params) || - !ValidateCounter(env, args[offset + 2], params)) { - return Nothing(); - } - cipher_nid = NID_aes_128_ctr; - break; - case kKeyVariantAES_CTR_192: - if (!ValidateIV(env, mode, args[offset + 1], params) || - !ValidateCounter(env, args[offset + 2], params)) { - return Nothing(); - } - cipher_nid = NID_aes_192_ctr; - break; - case kKeyVariantAES_CTR_256: - if (!ValidateIV(env, mode, args[offset + 1], params) || - !ValidateCounter(env, args[offset + 2], params)) { - return Nothing(); - } - cipher_nid = NID_aes_256_ctr; - break; - case kKeyVariantAES_CBC_128: - if (!ValidateIV(env, mode, args[offset + 1], params)) - return Nothing(); - cipher_nid = NID_aes_128_cbc; - break; - case kKeyVariantAES_CBC_192: - if (!ValidateIV(env, mode, args[offset + 1], params)) - return Nothing(); - cipher_nid = NID_aes_192_cbc; - break; - case kKeyVariantAES_CBC_256: - if (!ValidateIV(env, mode, args[offset + 1], params)) - return Nothing(); - cipher_nid = NID_aes_256_cbc; - break; - case kKeyVariantAES_KW_128: - UseDefaultIV(params); - cipher_nid = NID_id_aes128_wrap; - break; - case kKeyVariantAES_KW_192: - UseDefaultIV(params); - cipher_nid = NID_id_aes192_wrap; - break; - case kKeyVariantAES_KW_256: - UseDefaultIV(params); - cipher_nid = NID_id_aes256_wrap; - break; - case kKeyVariantAES_GCM_128: - if (!ValidateIV(env, mode, args[offset + 1], params) || - !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || - !ValidateAdditionalData(env, mode, args[offset + 3], params)) { - return Nothing(); - } - cipher_nid = NID_aes_128_gcm; - break; - case kKeyVariantAES_GCM_192: - if (!ValidateIV(env, mode, args[offset + 1], params) || - !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || - !ValidateAdditionalData(env, mode, args[offset + 3], params)) { + VARIANTS(V) + default: + UNREACHABLE(); + } +#undef V + + if (cipher_op_mode != AESCipherMode::KW) { + if (!ValidateIV(env, mode, args[offset + 1], params)) { + return Nothing(); + } + if (cipher_op_mode == AESCipherMode::CTR) { + if (!ValidateCounter(env, args[offset + 2], params)) { return Nothing(); } - cipher_nid = NID_aes_192_gcm; - break; - case kKeyVariantAES_GCM_256: - if (!ValidateIV(env, mode, args[offset + 1], params) || - !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || + } else if (cipher_op_mode == AESCipherMode::GCM) { + if (!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) || !ValidateAdditionalData(env, mode, args[offset + 3], params)) { return Nothing(); } - cipher_nid = NID_aes_256_gcm; - break; - default: - UNREACHABLE(); + } + } else { + UseDefaultIV(params); } params->cipher = EVP_get_cipherbynid(cipher_nid); @@ -577,8 +532,8 @@ WebCryptoCipherStatus AESCipherTraits::DoCipher( const AESCipherConfig& params, const ByteSource& in, ByteSource* out) { -#define V(name, fn) \ - case kKeyVariantAES_ ## name: \ +#define V(name, fn, _, __) \ + case kKeyVariantAES_##name: \ return fn(env, key_data.get(), cipher_mode, params, in, out); switch (params.variant) { VARIANTS(V) @@ -591,7 +546,7 @@ WebCryptoCipherStatus AESCipherTraits::DoCipher( void AES::Initialize(Environment* env, Local target) { AESCryptoJob::Initialize(env, target); -#define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name); +#define V(name, _, __, ___) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_##name); VARIANTS(V) #undef V } diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 9dfa5edc6544e7..2ddbc14b8e606e 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -15,22 +15,29 @@ constexpr size_t kAesBlockSize = 16; constexpr unsigned kNoAuthTagLength = static_cast(-1); constexpr const char* kDefaultWrapIV = "\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6"; -#define VARIANTS(V) \ - V(CTR_128, AES_CTR_Cipher) \ - V(CTR_192, AES_CTR_Cipher) \ - V(CTR_256, AES_CTR_Cipher) \ - V(CBC_128, AES_Cipher) \ - V(CBC_192, AES_Cipher) \ - V(CBC_256, AES_Cipher) \ - V(GCM_128, AES_Cipher) \ - V(GCM_192, AES_Cipher) \ - V(GCM_256, AES_Cipher) \ - V(KW_128, AES_Cipher) \ - V(KW_192, AES_Cipher) \ - V(KW_256, AES_Cipher) +enum class AESCipherMode { + CTR, + CBC, + GCM, + KW, +}; + +#define VARIANTS(V) \ + V(CTR_128, AES_CTR_Cipher, AESCipherMode::CTR, NID_aes_128_ctr) \ + V(CTR_192, AES_CTR_Cipher, AESCipherMode::CTR, NID_aes_192_ctr) \ + V(CTR_256, AES_CTR_Cipher, AESCipherMode::CTR, NID_aes_256_ctr) \ + V(CBC_128, AES_Cipher, AESCipherMode::CBC, NID_aes_128_cbc) \ + V(CBC_192, AES_Cipher, AESCipherMode::CBC, NID_aes_192_cbc) \ + V(CBC_256, AES_Cipher, AESCipherMode::CBC, NID_aes_256_cbc) \ + V(GCM_128, AES_Cipher, AESCipherMode::GCM, NID_aes_128_gcm) \ + V(GCM_192, AES_Cipher, AESCipherMode::GCM, NID_aes_192_gcm) \ + V(GCM_256, AES_Cipher, AESCipherMode::GCM, NID_aes_256_gcm) \ + V(KW_128, AES_Cipher, AESCipherMode::KW, NID_id_aes128_wrap) \ + V(KW_192, AES_Cipher, AESCipherMode::KW, NID_id_aes192_wrap) \ + V(KW_256, AES_Cipher, AESCipherMode::KW, NID_id_aes256_wrap) enum AESKeyVariant { -#define V(name, _) kKeyVariantAES_ ## name, +#define V(name, _, __, ___) kKeyVariantAES_##name, VARIANTS(V) #undef V }; From edd80e2bdc29fa95e7c820303004469947f90226 Mon Sep 17 00:00:00 2001 From: Aksinya Bykova <108536260+Aksinya-Bykova@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:26:00 +0300 Subject: [PATCH 085/120] test_runner: do not throw on mocked clearTimeout() PR-URL: https://github.com/nodejs/node/pull/54005 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Colin Ihrig Reviewed-By: Chemi Atlow --- lib/internal/test_runner/mock/mock_timers.js | 2 +- test/parallel/test-runner-mock-timers.js | 23 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 35c9a1ab68aac8..bacfbf41a207d6 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -304,7 +304,7 @@ class MockTimers { } #clearTimer(timer) { - if (timer.priorityQueuePosition !== undefined) { + if (timer?.priorityQueuePosition !== undefined) { this.#executionQueue.removeAt(timer.priorityQueuePosition); timer.priorityQueuePosition = undefined; } diff --git a/test/parallel/test-runner-mock-timers.js b/test/parallel/test-runner-mock-timers.js index 6ce6c28c95e326..3e8d2d79ede52b 100644 --- a/test/parallel/test-runner-mock-timers.js +++ b/test/parallel/test-runner-mock-timers.js @@ -257,6 +257,13 @@ describe('Mock Timers Test Suite', () => { assert.strictEqual(fn.mock.callCount(), 0); }); + + it('clearTimeout does not throw on null and undefined', (t) => { + t.mock.timers.enable({ apis: ['setTimeout'] }); + + nodeTimers.clearTimeout(); + nodeTimers.clearTimeout(null); + }); }); describe('setInterval Suite', () => { @@ -305,6 +312,13 @@ describe('Mock Timers Test Suite', () => { assert.strictEqual(fn.mock.callCount(), 0); }); + + it('clearInterval does not throw on null and undefined', (t) => { + t.mock.timers.enable({ apis: ['setInterval'] }); + + nodeTimers.clearInterval(); + nodeTimers.clearInterval(null); + }); }); describe('setImmediate Suite', () => { @@ -372,6 +386,15 @@ describe('Mock Timers Test Suite', () => { }); }); + describe('clearImmediate Suite', () => { + it('clearImmediate does not throw on null and undefined', (t) => { + t.mock.timers.enable({ apis: ['setImmediate'] }); + + nodeTimers.clearImmediate(); + nodeTimers.clearImmediate(null); + }); + }); + describe('timers/promises', () => { describe('setTimeout Suite', () => { it('should advance in time and trigger timers when calling the .tick function multiple times', async (t) => { From da3573409cdce62f8431748d8cbd8cfbce68717c Mon Sep 17 00:00:00 2001 From: Carlos Espa <43477095+Ceres6@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:12:51 +0200 Subject: [PATCH 086/120] test: add test for one arg timers to increase coverage PR-URL: https://github.com/nodejs/node/pull/54007 Reviewed-By: James M Snell Reviewed-By: Rich Trott Reviewed-By: Luigi Pinca --- test/parallel/test-timers.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/parallel/test-timers.js b/test/parallel/test-timers.js index e04c1f3f184946..11c6e106e85760 100644 --- a/test/parallel/test-timers.js +++ b/test/parallel/test-timers.js @@ -79,3 +79,8 @@ setTimeout(common.mustCall(() => { // Test 10 ms timeout separately. setTimeout(common.mustCall(), 10); setInterval(common.mustCall(function() { clearInterval(this); }), 10); + +// Test no timeout separately +setTimeout(common.mustCall()); +// eslint-disable-next-line no-restricted-syntax +setInterval(common.mustCall(function() { clearInterval(this); })); From 1344bd2d6f3855901b69b20ad183823deb6dfd6e Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Thu, 25 Jul 2024 10:33:38 -0700 Subject: [PATCH 087/120] test: add comments and rename test for timer robustness The name of the test did not make it clear what it was about. (It also used "timer" in the name instead of "timers" like all the other tests.) I also added a comment to be extra clear about the test purpose and a link to the issue that was originally filed about it. PR-URL: https://github.com/nodejs/node/pull/54008 Reviewed-By: Luigi Pinca Reviewed-By: Chengzhong Wu Reviewed-By: Trivikram Kamat Reviewed-By: James M Snell Reviewed-By: Jake Yuesong Li --- ...est-timer-immediate.js => test-timers-process-tampering.js} | 3 +++ 1 file changed, 3 insertions(+) rename test/parallel/{test-timer-immediate.js => test-timers-process-tampering.js} (51%) diff --git a/test/parallel/test-timer-immediate.js b/test/parallel/test-timers-process-tampering.js similarity index 51% rename from test/parallel/test-timer-immediate.js rename to test/parallel/test-timers-process-tampering.js index b0f52db1b713d3..766cc9f3560c82 100644 --- a/test/parallel/test-timer-immediate.js +++ b/test/parallel/test-timers-process-tampering.js @@ -1,3 +1,6 @@ +// Check that setImmediate works even if process is tampered with. +// This is a regression test for https://github.com/nodejs/node/issues/17681. + 'use strict'; const common = require('../common'); global.process = {}; // Boom! From 58aebfd31e000927099e63af341a7855aa8ccf1e Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Thu, 25 Jul 2024 14:37:39 -0700 Subject: [PATCH 088/120] doc: move GeoffreyBooth to TSC regular member PR-URL: https://github.com/nodejs/node/pull/54047 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Moshe Atlow --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8cdd6422082120..59590f632725f1 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,6 @@ For information about the governance of the Node.js project, see **Benjamin Gruenbaum** <> * [BridgeAR](https://github.com/BridgeAR) - **Ruben Bridgewater** <> (he/him) -* [GeoffreyBooth](https://github.com/GeoffreyBooth) - - **Geoffrey Booth** <> (he/him) * [gireeshpunathil](https://github.com/gireeshpunathil) - **Gireesh Punathil** <> (he/him) * [jasnell](https://github.com/jasnell) - @@ -215,6 +213,8 @@ For information about the governance of the Node.js project, see **Colin Ihrig** <> (he/him) * [codebytere](https://github.com/codebytere) - **Shelley Vohr** <> (she/her) +* [GeoffreyBooth](https://github.com/GeoffreyBooth) - + **Geoffrey Booth** <> (he/him) * [Trott](https://github.com/Trott) - **Rich Trott** <> (he/him) From 15816bd0ddb0f1dcde21dcff3e412271d4464d10 Mon Sep 17 00:00:00 2001 From: Austin Wright Date: Fri, 26 Jul 2024 01:09:23 -0700 Subject: [PATCH 089/120] stream: expose DuplexPair API PR-URL: https://github.com/nodejs/node/pull/34111 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Matteo Collina --- doc/api/stream.md | 32 +++++++- lib/internal/streams/duplexpair.js | 62 ++++++++++++++++ lib/stream.js | 1 + test/common/README.md | 9 --- test/common/duplexpair.js | 48 ------------ test/parallel/test-bootstrap-modules.js | 1 + test/parallel/test-gc-tls-external-memory.js | 4 +- .../test-http-agent-domain-reused-gc.js | 4 +- test/parallel/test-http-generic-streams.js | 12 +-- .../test-http-insecure-parser-per-stream.js | 10 +-- .../test-http-max-header-size-per-stream.js | 10 +-- ...t-http-sync-write-error-during-continue.js | 4 +- test/parallel/test-http2-backpressure.js | 4 +- .../test-http2-generic-streams-sendfile.js | 4 +- test/parallel/test-http2-generic-streams.js | 4 +- test/parallel/test-http2-padding-aligned.js | 4 +- .../test-http2-perform-server-handshake.js | 4 +- test/parallel/test-http2-sensitive-headers.js | 4 +- ...-http2-session-gc-while-write-scheduled.js | 4 +- test/parallel/test-http2-session-unref.js | 4 +- ...tp2-write-finishes-after-stream-destroy.js | 4 +- .../test-https-insecure-parse-per-stream.js | 7 +- .../test-https-max-header-size-per-stream.js | 7 +- test/parallel/test-stream-duplexpair.js | 74 +++++++++++++++++++ test/parallel/test-tls-destroy-stream.js | 4 +- test/parallel/test-tls-error-servername.js | 4 +- test/parallel/test-tls-generic-stream.js | 4 +- ...t-tls-socket-snicallback-without-server.js | 4 +- .../test-tls-streamwrap-buffersize.js | 4 +- ...test-tls-transport-destroy-after-own-gc.js | 4 +- .../test-worker-http2-stream-terminate.js | 4 +- ...orker-terminate-http2-respond-with-file.js | 4 +- 32 files changed, 230 insertions(+), 123 deletions(-) create mode 100644 lib/internal/streams/duplexpair.js delete mode 100644 test/common/duplexpair.js create mode 100644 test/parallel/test-stream-duplexpair.js diff --git a/doc/api/stream.md b/doc/api/stream.md index c189df15c42957..1cf07d00093a26 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -45,8 +45,11 @@ There are four fundamental stream types within Node.js: is written and read (for example, [`zlib.createDeflate()`][]). Additionally, this module includes the utility functions -[`stream.pipeline()`][], [`stream.finished()`][], [`stream.Readable.from()`][] -and [`stream.addAbortSignal()`][]. +[`stream.duplexPair()`][], +[`stream.pipeline()`][], +[`stream.finished()`][] +[`stream.Readable.from()`][], and +[`stream.addAbortSignal()`][]. ### Streams Promises API @@ -2675,6 +2678,30 @@ unless `emitClose` is set in false. Once `destroy()` has been called, any further calls will be a no-op and no further errors except from `_destroy()` may be emitted as `'error'`. +#### `stream.duplexPair([options])` + + + +* `options` {Object} A value to pass to both [`Duplex`][] constructors, + to set options such as buffering. +* Returns: {Array} of two [`Duplex`][] instances. + +The utility function `duplexPair` returns an Array with two items, +each being a `Duplex` stream connected to the other side: + +```js +const [ sideA, sideB ] = duplexPair(); +``` + +Whatever is written to one stream is made readable on the other. It provides +behavior analogous to a network connection, where the data written by the client +becomes readable by the server, and vice-versa. + +The Duplex streams are symmetrical; one or the other may be used without any +difference in behavior. + ### `stream.finished(stream[, options], callback)` ```c -napi_status napi_set_instance_data(node_api_nogc_env env, +napi_status napi_set_instance_data(node_api_basic_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); @@ -509,7 +509,7 @@ napiVersion: 6 --> ```c -napi_status napi_get_instance_data(node_api_nogc_env env, +napi_status napi_get_instance_data(node_api_basic_env env, void** data); ``` @@ -611,16 +611,16 @@ when an instance of a native addon is unloaded. Notification of this event is delivered through the callbacks given to [`napi_add_env_cleanup_hook`][] and [`napi_set_instance_data`][]. -### `node_api_nogc_env` +### `node_api_basic_env` > Stability: 1 - Experimental This variant of `napi_env` is passed to synchronous finalizers -([`node_api_nogc_finalize`][]). There is a subset of Node-APIs which accept -a parameter of type `node_api_nogc_env` as their first argument. These APIs do +([`node_api_basic_finalize`][]). There is a subset of Node-APIs which accept +a parameter of type `node_api_basic_env` as their first argument. These APIs do not access the state of the JavaScript engine and are thus safe to call from synchronous finalizers. Passing a parameter of type `napi_env` to these APIs is -allowed, however, passing a parameter of type `node_api_nogc_env` to APIs that +allowed, however, passing a parameter of type `node_api_basic_env` to APIs that access the JavaScript engine state is not allowed. Attempting to do so without a cast will produce a compiler warning or an error when add-ons are compiled with flags which cause them to emit warnings and/or errors when incorrect @@ -791,7 +791,7 @@ typedef napi_value (*napi_callback)(napi_env, napi_callback_info); Unless for reasons discussed in [Object Lifetime Management][], creating a handle and/or callback scope inside a `napi_callback` is not necessary. -#### `node_api_nogc_finalize` +#### `node_api_basic_finalize` ```c -NODE_EXTERN napi_status napi_add_env_cleanup_hook(node_api_nogc_env env, +NODE_EXTERN napi_status napi_add_env_cleanup_hook(node_api_basic_env env, napi_cleanup_hook fun, void* arg); ``` @@ -1915,7 +1915,7 @@ napiVersion: 3 --> ```c -NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(node_api_nogc_env env, +NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(node_api_basic_env env, void (*fun)(void* arg), void* arg); ``` @@ -1944,7 +1944,7 @@ changes: ```c NAPI_EXTERN napi_status napi_add_async_cleanup_hook( - node_api_nogc_env env, + node_api_basic_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle); @@ -5533,7 +5533,7 @@ napiVersion: 5 napi_status napi_add_finalizer(napi_env env, napi_value js_object, void* finalize_data, - node_api_nogc_finalize finalize_cb, + node_api_basic_finalize finalize_cb, void* finalize_hint, napi_ref* result); ``` @@ -5574,7 +5574,7 @@ added: > Stability: 1 - Experimental ```c -napi_status node_api_post_finalizer(node_api_nogc_env env, +napi_status node_api_post_finalizer(node_api_basic_env env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint); @@ -5644,7 +5644,7 @@ Once created the async worker can be queued for execution using the [`napi_queue_async_work`][] function: ```c -napi_status napi_queue_async_work(node_api_nogc_env env, +napi_status napi_queue_async_work(node_api_basic_env env, napi_async_work work); ``` @@ -5736,7 +5736,7 @@ napiVersion: 1 --> ```c -napi_status napi_queue_async_work(node_api_nogc_env env, +napi_status napi_queue_async_work(node_api_basic_env env, napi_async_work work); ``` @@ -5757,7 +5757,7 @@ napiVersion: 1 --> ```c -napi_status napi_cancel_async_work(node_api_nogc_env env, +napi_status napi_cancel_async_work(node_api_basic_env env, napi_async_work work); ``` @@ -5961,7 +5961,7 @@ typedef struct { const char* release; } napi_node_version; -napi_status napi_get_node_version(node_api_nogc_env env, +napi_status napi_get_node_version(node_api_basic_env env, const napi_node_version** version); ``` @@ -5984,7 +5984,7 @@ napiVersion: 1 --> ```c -napi_status napi_get_version(node_api_nogc_env env, +napi_status napi_get_version(node_api_basic_env env, uint32_t* result); ``` @@ -6017,7 +6017,7 @@ napiVersion: 1 --> ```c -NAPI_EXTERN napi_status napi_adjust_external_memory(node_api_nogc_env env, +NAPI_EXTERN napi_status napi_adjust_external_memory(node_api_basic_env env, int64_t change_in_bytes, int64_t* result); ``` @@ -6234,7 +6234,7 @@ napiVersion: 2 --> ```c -NAPI_EXTERN napi_status napi_get_uv_event_loop(node_api_nogc_env env, +NAPI_EXTERN napi_status napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop); ``` @@ -6554,7 +6554,7 @@ napiVersion: 4 ```c NAPI_EXTERN napi_status -napi_ref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func); +napi_ref_threadsafe_function(node_api_basic_env env, napi_threadsafe_function func); ``` * `[in] env`: The environment that the API is invoked under. @@ -6580,7 +6580,7 @@ napiVersion: 4 ```c NAPI_EXTERN napi_status -napi_unref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func); +napi_unref_threadsafe_function(node_api_basic_env env, napi_threadsafe_function func); ``` * `[in] env`: The environment that the API is invoked under. @@ -6606,7 +6606,7 @@ napiVersion: 9 ```c NAPI_EXTERN napi_status -node_api_get_module_file_name(node_api_nogc_env env, const char** result); +node_api_get_module_file_name(node_api_basic_env env, const char** result); ``` @@ -6731,10 +6731,10 @@ the add-on's file name during loading. [`napi_wrap`]: #napi_wrap [`node-addon-api`]: https://github.com/nodejs/node-addon-api [`node_api.h`]: https://github.com/nodejs/node/blob/HEAD/src/node_api.h +[`node_api_basic_finalize`]: #node_api_basic_finalize [`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1 [`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16 [`node_api_create_syntax_error`]: #node_api_create_syntax_error -[`node_api_nogc_finalize`]: #node_api_nogc_finalize [`node_api_post_finalizer`]: #node_api_post_finalizer [`node_api_throw_syntax_error`]: #node_api_throw_syntax_error [`process.release`]: process.md#processrelease diff --git a/src/js_native_api.h b/src/js_native_api.h index c5114651dc6b00..1558a9f996a069 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -50,7 +50,7 @@ EXTERN_C_START NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info( - node_api_nogc_env env, const napi_extended_error_info** result); + node_api_basic_env env, const napi_extended_error_info** result); // Getters for defined singletons NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, @@ -94,19 +94,19 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, napi_value* result); #ifdef NAPI_EXPERIMENTAL #define NODE_API_EXPERIMENTAL_HAS_EXTERNAL_STRINGS -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_external_string_latin1(napi_env env, - char* str, - size_t length, - node_api_nogc_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied); +NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1( + napi_env env, + char* str, + size_t length, + node_api_basic_finalize finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied); NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_utf16(napi_env env, char16_t* str, size_t length, - node_api_nogc_finalize finalize_callback, + node_api_basic_finalize finalize_callback, void* finalize_hint, napi_value* result, bool* copied); @@ -318,12 +318,13 @@ napi_define_class(napi_env env, napi_value* result); // Methods to work with external data objects -NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env, - napi_value js_object, - void* native_object, - node_api_nogc_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_wrap(napi_env env, + napi_value js_object, + void* native_object, + node_api_basic_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, napi_value js_object, void** result); @@ -333,7 +334,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_create_external(napi_env env, void* data, - node_api_nogc_finalize finalize_cb, + node_api_basic_finalize finalize_cb, void* finalize_hint, napi_value* result); NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, @@ -432,7 +433,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, - node_api_nogc_finalize finalize_cb, + node_api_basic_finalize finalize_cb, void* finalize_hint, napi_value* result); #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED @@ -474,7 +475,7 @@ napi_get_dataview_info(napi_env env, size_t* byte_offset); // version management -NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_nogc_env env, +NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env, uint32_t* result); // Promises @@ -498,7 +499,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, // Memory management NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( - node_api_nogc_env env, int64_t change_in_bytes, int64_t* adjusted_value); + node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value); #if NAPI_VERSION >= 5 @@ -520,7 +521,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env, napi_value js_object, void* finalize_data, - node_api_nogc_finalize finalize_cb, + node_api_basic_finalize finalize_cb, void* finalize_hint, napi_ref* result); @@ -530,7 +531,7 @@ napi_add_finalizer(napi_env env, #define NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER NAPI_EXTERN napi_status NAPI_CDECL -node_api_post_finalizer(node_api_nogc_env env, +node_api_post_finalizer(node_api_basic_env env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint); @@ -575,13 +576,13 @@ napi_get_all_property_names(napi_env env, // Instance data NAPI_EXTERN napi_status NAPI_CDECL -napi_set_instance_data(node_api_nogc_env env, +napi_set_instance_data(node_api_basic_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(node_api_nogc_env env, - void** data); +NAPI_EXTERN napi_status NAPI_CDECL +napi_get_instance_data(node_api_basic_env env, void** data); #endif // NAPI_VERSION >= 6 #if NAPI_VERSION >= 7 diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 7cb5b080cc377a..43e7bb77ff94e7 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -27,7 +27,7 @@ typedef struct napi_env__* napi_env; // meaning that they do not affect the state of the JS engine, and can // therefore be called synchronously from a finalizer that itself runs // synchronously during GC. Such APIs can receive either a `napi_env` or a -// `node_api_nogc_env` as their first parameter, because we should be able to +// `node_api_basic_env` as their first parameter, because we should be able to // also call them during normal, non-garbage-collecting operations, whereas // APIs that affect the state of the JS engine can only receive a `napi_env` as // their first parameter, because we must not call them during GC. In lieu of @@ -37,19 +37,21 @@ typedef struct napi_env__* napi_env; // expecting a non-const value. // // In conjunction with appropriate CFLAGS to warn us if we're passing a const -// (nogc) environment into an API that expects a non-const environment, and the -// definition of nogc finalizer function pointer types below, which receive a -// nogc environment as their first parameter, and can thus only call nogc APIs -// (unless the user explicitly casts the environment), we achieve the ability -// to ensure at compile time that we do not call APIs that affect the state of -// the JS engine from a synchronous (nogc) finalizer. +// (basic) environment into an API that expects a non-const environment, and +// the definition of basic finalizer function pointer types below, which +// receive a basic environment as their first parameter, and can thus only call +// basic APIs (unless the user explicitly casts the environment), we achieve +// the ability to ensure at compile time that we do not call APIs that affect +// the state of the JS engine from a synchronous (basic) finalizer. #if !defined(NAPI_EXPERIMENTAL) || \ (defined(NAPI_EXPERIMENTAL) && \ - defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT)) + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) typedef struct napi_env__* node_api_nogc_env; #else typedef const struct napi_env__* node_api_nogc_env; #endif +typedef node_api_nogc_env node_api_basic_env; typedef struct napi_value__* napi_value; typedef struct napi_ref__* napi_ref; @@ -147,13 +149,15 @@ typedef void(NAPI_CDECL* napi_finalize)(napi_env env, #if !defined(NAPI_EXPERIMENTAL) || \ (defined(NAPI_EXPERIMENTAL) && \ - defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT)) + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) typedef napi_finalize node_api_nogc_finalize; #else typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env, void* finalize_data, void* finalize_hint); #endif +typedef node_api_nogc_finalize node_api_basic_finalize; typedef struct { // One of utf8name or name should be NULL. diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 44270df350c174..c03e1570ca6c0c 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -909,8 +909,8 @@ static const char* error_messages[] = { }; napi_status NAPI_CDECL napi_get_last_error_info( - node_api_nogc_env nogc_env, const napi_extended_error_info** result) { - napi_env env = const_cast(nogc_env); + node_api_basic_env basic_env, const napi_extended_error_info** result) { + napi_env env = const_cast(basic_env); CHECK_ENV(env); CHECK_ARG(env, result); @@ -1652,12 +1652,12 @@ napi_status NAPI_CDECL node_api_create_external_string_latin1( napi_env env, char* str, size_t length, - node_api_nogc_finalize nogc_finalize_callback, + node_api_basic_finalize basic_finalize_callback, void* finalize_hint, napi_value* result, bool* copied) { napi_finalize finalize_callback = - reinterpret_cast(nogc_finalize_callback); + reinterpret_cast(basic_finalize_callback); return v8impl::NewExternalString( env, str, @@ -1681,12 +1681,12 @@ napi_status NAPI_CDECL node_api_create_external_string_utf16( napi_env env, char16_t* str, size_t length, - node_api_nogc_finalize nogc_finalize_callback, + node_api_basic_finalize basic_finalize_callback, void* finalize_hint, napi_value* result, bool* copied) { napi_finalize finalize_callback = - reinterpret_cast(nogc_finalize_callback); + reinterpret_cast(basic_finalize_callback); return v8impl::NewExternalString( env, str, @@ -2560,10 +2560,11 @@ GEN_COERCE_FUNCTION(STRING, String, string) napi_status NAPI_CDECL napi_wrap(napi_env env, napi_value js_object, void* native_object, - node_api_nogc_finalize nogc_finalize_cb, + node_api_basic_finalize basic_finalize_cb, void* finalize_hint, napi_ref* result) { - napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); + napi_finalize finalize_cb = + reinterpret_cast(basic_finalize_cb); return v8impl::Wrap( env, js_object, native_object, finalize_cb, finalize_hint, result); } @@ -2583,10 +2584,11 @@ napi_status NAPI_CDECL napi_remove_wrap(napi_env env, napi_status NAPI_CDECL napi_create_external(napi_env env, void* data, - node_api_nogc_finalize nogc_finalize_cb, + node_api_basic_finalize basic_finalize_cb, void* finalize_hint, napi_value* result) { - napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); + napi_finalize finalize_cb = + reinterpret_cast(basic_finalize_cb); NAPI_PREAMBLE(env); CHECK_ARG(env, result); @@ -3027,7 +3029,7 @@ napi_status NAPI_CDECL napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, - node_api_nogc_finalize finalize_cb, + node_api_basic_finalize finalize_cb, void* finalize_hint, napi_value* result) { // The API contract here is that the cleanup function runs on the JS thread, @@ -3292,7 +3294,7 @@ napi_status NAPI_CDECL napi_get_dataview_info(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_version(node_api_nogc_env env, +napi_status NAPI_CDECL napi_get_version(node_api_basic_env env, uint32_t* result) { CHECK_ENV(env); CHECK_ARG(env, result); @@ -3414,12 +3416,13 @@ napi_status NAPI_CDECL napi_add_finalizer(napi_env env, napi_value js_object, void* finalize_data, - node_api_nogc_finalize nogc_finalize_cb, + node_api_basic_finalize basic_finalize_cb, void* finalize_hint, napi_ref* result) { // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw // JS exceptions. - napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); + napi_finalize finalize_cb = + reinterpret_cast(basic_finalize_cb); CHECK_ENV_NOT_IN_GC(env); CHECK_ARG(env, js_object); CHECK_ARG(env, finalize_cb); @@ -3443,11 +3446,11 @@ napi_add_finalizer(napi_env env, #ifdef NAPI_EXPERIMENTAL -napi_status NAPI_CDECL node_api_post_finalizer(node_api_nogc_env nogc_env, +napi_status NAPI_CDECL node_api_post_finalizer(node_api_basic_env basic_env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint) { - napi_env env = const_cast(nogc_env); + napi_env env = const_cast(basic_env); CHECK_ENV(env); env->EnqueueFinalizer(v8impl::TrackedFinalizer::New( env, finalize_cb, finalize_data, finalize_hint)); @@ -3456,7 +3459,7 @@ napi_status NAPI_CDECL node_api_post_finalizer(node_api_nogc_env nogc_env, #endif -napi_status NAPI_CDECL napi_adjust_external_memory(node_api_nogc_env env, +napi_status NAPI_CDECL napi_adjust_external_memory(node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value) { CHECK_ENV(env); @@ -3468,11 +3471,11 @@ napi_status NAPI_CDECL napi_adjust_external_memory(node_api_nogc_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_set_instance_data(node_api_nogc_env nogc_env, +napi_status NAPI_CDECL napi_set_instance_data(node_api_basic_env basic_env, void* data, napi_finalize finalize_cb, void* finalize_hint) { - napi_env env = const_cast(nogc_env); + napi_env env = const_cast(basic_env); CHECK_ENV(env); v8impl::RefBase* old_data = static_cast(env->instance_data); @@ -3488,7 +3491,7 @@ napi_status NAPI_CDECL napi_set_instance_data(node_api_nogc_env nogc_env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_instance_data(node_api_nogc_env env, +napi_status NAPI_CDECL napi_get_instance_data(node_api_basic_env env, void** data) { CHECK_ENV(env); CHECK_ARG(env, data); diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index 1974c3f6873ef7..1817226b2daac4 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -4,7 +4,7 @@ #include "js_native_api_types.h" #include "js_native_api_v8_internals.h" -inline napi_status napi_clear_last_error(node_api_nogc_env env); +inline napi_status napi_clear_last_error(node_api_basic_env env); namespace v8impl { @@ -172,8 +172,8 @@ struct napi_env__ { virtual ~napi_env__() = default; }; -inline napi_status napi_clear_last_error(node_api_nogc_env nogc_env) { - napi_env env = const_cast(nogc_env); +inline napi_status napi_clear_last_error(node_api_basic_env basic_env) { + napi_env env = const_cast(basic_env); env->last_error.error_code = napi_ok; env->last_error.engine_error_code = 0; env->last_error.engine_reserved = nullptr; @@ -181,11 +181,11 @@ inline napi_status napi_clear_last_error(node_api_nogc_env nogc_env) { return napi_ok; } -inline napi_status napi_set_last_error(node_api_nogc_env nogc_env, +inline napi_status napi_set_last_error(node_api_basic_env basic_env, napi_status error_code, uint32_t engine_error_code = 0, void* engine_reserved = nullptr) { - napi_env env = const_cast(nogc_env); + napi_env env = const_cast(basic_env); env->last_error.error_code = error_code; env->last_error.engine_error_code = engine_error_code; env->last_error.engine_reserved = engine_reserved; diff --git a/src/node_api.cc b/src/node_api.cc index 27f67d8c7a3875..3952b814f36ed7 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -765,7 +765,7 @@ void NAPI_CDECL napi_module_register(napi_module* mod) { node::node_module_register(nm); } -napi_status NAPI_CDECL napi_add_env_cleanup_hook(node_api_nogc_env env, +napi_status NAPI_CDECL napi_add_env_cleanup_hook(node_api_basic_env env, napi_cleanup_hook fun, void* arg) { CHECK_ENV(env); @@ -776,7 +776,7 @@ napi_status NAPI_CDECL napi_add_env_cleanup_hook(node_api_nogc_env env, return napi_ok; } -napi_status NAPI_CDECL napi_remove_env_cleanup_hook(node_api_nogc_env env, +napi_status NAPI_CDECL napi_remove_env_cleanup_hook(node_api_basic_env env, napi_cleanup_hook fun, void* arg) { CHECK_ENV(env); @@ -823,11 +823,11 @@ struct napi_async_cleanup_hook_handle__ { }; napi_status NAPI_CDECL -napi_add_async_cleanup_hook(node_api_nogc_env nogc_env, +napi_add_async_cleanup_hook(node_api_basic_env basic_env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle) { - napi_env env = const_cast(nogc_env); + napi_env env = const_cast(basic_env); CHECK_ENV(env); CHECK_ARG(env, hook); @@ -1042,10 +1042,11 @@ napi_status NAPI_CDECL napi_create_external_buffer(napi_env env, size_t length, void* data, - node_api_nogc_finalize nogc_finalize_cb, + node_api_basic_finalize basic_finalize_cb, void* finalize_hint, napi_value* result) { - napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); + napi_finalize finalize_cb = + reinterpret_cast(basic_finalize_cb); NAPI_PREAMBLE(env); CHECK_ARG(env, result); @@ -1131,7 +1132,7 @@ napi_status NAPI_CDECL napi_get_buffer_info(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_node_version(node_api_nogc_env env, +napi_status NAPI_CDECL napi_get_node_version(node_api_basic_env env, const napi_node_version** result) { CHECK_ENV(env); CHECK_ARG(env, result); @@ -1275,16 +1276,16 @@ napi_status NAPI_CDECL napi_delete_async_work(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_uv_event_loop(node_api_nogc_env nogc_env, +napi_status NAPI_CDECL napi_get_uv_event_loop(node_api_basic_env basic_env, uv_loop_t** loop) { - napi_env env = const_cast(nogc_env); + napi_env env = const_cast(basic_env); CHECK_ENV(env); CHECK_ARG(env, loop); *loop = reinterpret_cast(env)->node_env()->event_loop(); return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_queue_async_work(node_api_nogc_env env, +napi_status NAPI_CDECL napi_queue_async_work(node_api_basic_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); @@ -1299,7 +1300,7 @@ napi_status NAPI_CDECL napi_queue_async_work(node_api_nogc_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_cancel_async_work(node_api_nogc_env env, +napi_status NAPI_CDECL napi_cancel_async_work(node_api_basic_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); @@ -1405,20 +1406,20 @@ napi_status NAPI_CDECL napi_release_threadsafe_function( } napi_status NAPI_CDECL napi_unref_threadsafe_function( - node_api_nogc_env env, napi_threadsafe_function func) { + node_api_basic_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Unref(); } napi_status NAPI_CDECL napi_ref_threadsafe_function( - node_api_nogc_env env, napi_threadsafe_function func) { + node_api_basic_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Ref(); } -napi_status NAPI_CDECL node_api_get_module_file_name(node_api_nogc_env nogc_env, - const char** result) { - napi_env env = const_cast(nogc_env); +napi_status NAPI_CDECL node_api_get_module_file_name( + node_api_basic_env basic_env, const char** result) { + napi_env env = const_cast(basic_env); CHECK_ENV(env); CHECK_ARG(env, result); diff --git a/src/node_api.h b/src/node_api.h index e94ee486392840..526cdd5d406eb6 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -131,7 +131,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_external_buffer(napi_env env, size_t length, void* data, - node_api_nogc_finalize finalize_cb, + node_api_basic_finalize finalize_cb, void* finalize_hint, napi_value* result); #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED @@ -159,20 +159,20 @@ napi_create_async_work(napi_env env, napi_async_work* result); NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_nogc_env env, +NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_basic_env env, napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL napi_cancel_async_work(node_api_nogc_env env, - napi_async_work work); +NAPI_EXTERN napi_status NAPI_CDECL +napi_cancel_async_work(node_api_basic_env env, napi_async_work work); // version management -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_node_version(node_api_nogc_env env, const napi_node_version** version); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_node_version( + node_api_basic_env env, const napi_node_version** version); #if NAPI_VERSION >= 2 // Return the current libuv event loop for a given environment NAPI_EXTERN napi_status NAPI_CDECL -napi_get_uv_event_loop(node_api_nogc_env env, struct uv_loop_s** loop); +napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop); #endif // NAPI_VERSION >= 2 @@ -182,10 +182,10 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env, napi_value err); NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook( - node_api_nogc_env env, napi_cleanup_hook fun, void* arg); + node_api_basic_env env, napi_cleanup_hook fun, void* arg); NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook( - node_api_nogc_env env, napi_cleanup_hook fun, void* arg); + node_api_basic_env env, napi_cleanup_hook fun, void* arg); NAPI_EXTERN napi_status NAPI_CDECL napi_open_callback_scope(napi_env env, @@ -229,17 +229,17 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function( napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function( - node_api_nogc_env env, napi_threadsafe_function func); + node_api_basic_env env, napi_threadsafe_function func); NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function( - node_api_nogc_env env, napi_threadsafe_function func); + node_api_basic_env env, napi_threadsafe_function func); #endif // NAPI_VERSION >= 4 #if NAPI_VERSION >= 8 NAPI_EXTERN napi_status NAPI_CDECL -napi_add_async_cleanup_hook(node_api_nogc_env env, +napi_add_async_cleanup_hook(node_api_basic_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle); @@ -252,7 +252,7 @@ napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle); #if NAPI_VERSION >= 9 NAPI_EXTERN napi_status NAPI_CDECL -node_api_get_module_file_name(node_api_nogc_env env, const char** result); +node_api_get_module_file_name(node_api_basic_env env, const char** result); #endif // NAPI_VERSION >= 9 diff --git a/test/js-native-api/common.h b/test/js-native-api/common.h index 1308088e7872fb..49cdc066ea6f34 100644 --- a/test/js-native-api/common.h +++ b/test/js-native-api/common.h @@ -23,7 +23,7 @@ } \ } while (0) -// The nogc version of GET_AND_THROW_LAST_ERROR. We cannot access any +// The basic version of GET_AND_THROW_LAST_ERROR. We cannot access any // exceptions and we cannot fail by way of JS exception, so we abort. #define FATALLY_FAIL_WITH_LAST_ERROR(env) \ do { \ @@ -47,7 +47,7 @@ } \ } while (0) -#define NODE_API_NOGC_ASSERT_BASE(assertion, message, ret_val) \ +#define NODE_API_BASIC_ASSERT_BASE(assertion, message, ret_val) \ do { \ if (!(assertion)) { \ fprintf(stderr, "assertion (" #assertion ") failed: " message); \ @@ -66,8 +66,8 @@ #define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \ NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING) -#define NODE_API_NOGC_ASSERT_RETURN_VOID(assertion, message) \ - NODE_API_NOGC_ASSERT_BASE(assertion, message, NODE_API_RETVAL_NOTHING) +#define NODE_API_BASIC_ASSERT_RETURN_VOID(assertion, message) \ + NODE_API_BASIC_ASSERT_BASE(assertion, message, NODE_API_RETVAL_NOTHING) #define NODE_API_CALL_BASE(env, the_call, ret_val) \ do { \ @@ -77,7 +77,7 @@ } \ } while (0) -#define NODE_API_NOGC_CALL_BASE(env, the_call, ret_val) \ +#define NODE_API_BASIC_CALL_BASE(env, the_call, ret_val) \ do { \ if ((the_call) != napi_ok) { \ FATALLY_FAIL_WITH_LAST_ERROR((env)); \ @@ -93,8 +93,8 @@ #define NODE_API_CALL_RETURN_VOID(env, the_call) \ NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) -#define NODE_API_NOGC_CALL_RETURN_VOID(env, the_call) \ - NODE_API_NOGC_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) +#define NODE_API_BASIC_CALL_RETURN_VOID(env, the_call) \ + NODE_API_BASIC_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) #define NODE_API_CHECK_STATUS(the_call) \ do { \ diff --git a/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c b/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c index 813e59918e7b3a..9a4b9547493505 100644 --- a/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c +++ b/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c @@ -7,10 +7,10 @@ static void Finalize(napi_env env, void* data, void* hint) { napi_value global, set_timeout; napi_ref* ref = data; - NODE_API_NOGC_ASSERT_RETURN_VOID( + NODE_API_BASIC_ASSERT_RETURN_VOID( napi_delete_reference(env, *ref) == napi_ok, "deleting reference in finalizer should succeed"); - NODE_API_NOGC_ASSERT_RETURN_VOID( + NODE_API_BASIC_ASSERT_RETURN_VOID( napi_get_global(env, &global) == napi_ok, "getting global reference in finalizer should succeed"); napi_status result = @@ -23,12 +23,12 @@ static void Finalize(napi_env env, void* data, void* hint) { // the point of view of the addon. #ifdef NAPI_EXPERIMENTAL - NODE_API_NOGC_ASSERT_RETURN_VOID( + NODE_API_BASIC_ASSERT_RETURN_VOID( result == napi_cannot_run_js || result == napi_ok, "getting named property from global in finalizer should succeed " "or return napi_cannot_run_js"); #else - NODE_API_NOGC_ASSERT_RETURN_VOID( + NODE_API_BASIC_ASSERT_RETURN_VOID( result == napi_pending_exception || result == napi_ok, "getting named property from global in finalizer should succeed " "or return napi_pending_exception"); @@ -36,9 +36,9 @@ static void Finalize(napi_env env, void* data, void* hint) { free(ref); } -static void NogcFinalize(node_api_nogc_env env, void* data, void* hint) { +static void BasicFinalize(node_api_basic_env env, void* data, void* hint) { #ifdef NAPI_EXPERIMENTAL - NODE_API_NOGC_CALL_RETURN_VOID( + NODE_API_BASIC_CALL_RETURN_VOID( env, node_api_post_finalizer(env, Finalize, data, hint)); #else Finalize(env, data, hint); @@ -55,7 +55,8 @@ static napi_value CreateRef(napi_env env, napi_callback_info info) { NODE_API_CALL(env, napi_typeof(env, cb, &value_type)); NODE_API_ASSERT( env, value_type == napi_function, "argument must be function"); - NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, NogcFinalize, NULL, ref)); + NODE_API_CALL(env, + napi_add_finalizer(env, cb, ref, BasicFinalize, NULL, ref)); return cb; } diff --git a/test/js-native-api/test_finalizer/test_finalizer.c b/test/js-native-api/test_finalizer/test_finalizer.c index b9b046484a5288..721ca12c7bb9dc 100644 --- a/test/js-native-api/test_finalizer/test_finalizer.c +++ b/test/js-native-api/test_finalizer/test_finalizer.c @@ -11,17 +11,17 @@ typedef struct { napi_ref js_func; } FinalizerData; -static void finalizerOnlyCallback(node_api_nogc_env env, +static void finalizerOnlyCallback(node_api_basic_env env, void* finalize_data, void* finalize_hint) { FinalizerData* data = (FinalizerData*)finalize_data; int32_t count = ++data->finalize_count; // It is safe to access instance data - NODE_API_NOGC_CALL_RETURN_VOID(env, - napi_get_instance_data(env, (void**)&data)); - NODE_API_NOGC_ASSERT_RETURN_VOID(count = data->finalize_count, - "Expected to be the same FinalizerData"); + NODE_API_BASIC_CALL_RETURN_VOID(env, + napi_get_instance_data(env, (void**)&data)); + NODE_API_BASIC_ASSERT_RETURN_VOID(count = data->finalize_count, + "Expected to be the same FinalizerData"); } static void finalizerCallingJSCallback(napi_env env, @@ -40,20 +40,20 @@ static void finalizerCallingJSCallback(napi_env env, } // Schedule async finalizer to run JavaScript-touching code. -static void finalizerWithJSCallback(node_api_nogc_env env, +static void finalizerWithJSCallback(node_api_basic_env env, void* finalize_data, void* finalize_hint) { - NODE_API_NOGC_CALL_RETURN_VOID( + NODE_API_BASIC_CALL_RETURN_VOID( env, node_api_post_finalizer( env, finalizerCallingJSCallback, finalize_data, finalize_hint)); } -static void finalizerWithFailedJSCallback(node_api_nogc_env nogc_env, +static void finalizerWithFailedJSCallback(node_api_basic_env basic_env, void* finalize_data, void* finalize_hint) { // Intentionally cast to a napi_env to test the fatal failure. - napi_env env = (napi_env)nogc_env; + napi_env env = (napi_env)basic_env; napi_value obj; FinalizerData* data = (FinalizerData*)finalize_data; ++data->finalize_count; diff --git a/test/js-native-api/test_string/test_string.c b/test/js-native-api/test_string/test_string.c index 48d70bedde554b..57353b9f6303f2 100644 --- a/test/js-native-api/test_string/test_string.c +++ b/test/js-native-api/test_string/test_string.c @@ -87,7 +87,7 @@ static napi_value TestTwoByteImpl(napi_env env, return output; } -static void free_string(node_api_nogc_env env, void* data, void* hint) { +static void free_string(node_api_basic_env env, void* data, void* hint) { free(data); } diff --git a/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c b/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c index f9110303d2ded4..d1a871949951b2 100644 --- a/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c +++ b/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c @@ -4,12 +4,12 @@ static uint32_t finalizeCount = 0; -static void FreeData(node_api_nogc_env env, void* data, void* hint) { - NODE_API_NOGC_ASSERT_RETURN_VOID(data != NULL, "Expects non-NULL data."); +static void FreeData(node_api_basic_env env, void* data, void* hint) { + NODE_API_BASIC_ASSERT_RETURN_VOID(data != NULL, "Expects non-NULL data."); free(data); } -static void Finalize(node_api_nogc_env env, void* data, void* hint) { +static void Finalize(node_api_basic_env env, void* data, void* hint) { ++finalizeCount; } From e471e32d46a2621cd1cdd520df9eaa88a6363cdb Mon Sep 17 00:00:00 2001 From: Chengzhong Wu Date: Sat, 27 Jul 2024 13:41:31 +0100 Subject: [PATCH 094/120] test: skip sea tests with more accurate available disk space estimation PR-URL: https://github.com/nodejs/node/pull/53996 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli Reviewed-By: Luigi Pinca --- test/common/sea.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/common/sea.js b/test/common/sea.js index 6de0ea2e4f8a87..53bfd93d927842 100644 --- a/test/common/sea.js +++ b/test/common/sea.js @@ -5,7 +5,7 @@ const fixtures = require('../common/fixtures'); const tmpdir = require('../common/tmpdir'); const { inspect } = require('util'); -const { readFileSync, copyFileSync } = require('fs'); +const { readFileSync, copyFileSync, statSync } = require('fs'); const { spawnSyncAndExitWithoutError, } = require('../common/child_process'); @@ -61,9 +61,12 @@ function skipIfSingleExecutableIsNotSupported() { tmpdir.refresh(); // The SEA tests involve making a copy of the executable and writing some fixtures - // to the tmpdir. To be safe, ensure that at least 120MB disk space is available. - if (!tmpdir.hasEnoughSpace(120 * 1024 * 1024)) { - common.skip('Available disk space < 120MB'); + // to the tmpdir. To be safe, ensure that the disk space has at least a copy of the + // executable and some extra space for blobs and configs is available. + const stat = statSync(process.execPath); + const expectedSpace = stat.size + 10 * 1024 * 1024; + if (!tmpdir.hasEnoughSpace(expectedSpace)) { + common.skip(`Available disk space < ${Math.floor(expectedSpace / 1024 / 1024)} MB`); } } From 3fb97a90eed9e552af7a802ee0eb5230c1c9729c Mon Sep 17 00:00:00 2001 From: Colin Ihrig Date: Sat, 27 Jul 2024 09:27:03 -0400 Subject: [PATCH 095/120] test_runner: remove redundant bootstrap boolean The test runner bootstrap process awaits a Promise and then sets a boolean flag. This commit consolidates the Promise and boolean into a single value. This commit also ensures that the globalRoot test is always assigned in createTestTree() in order to better consolidate the CLI/run() and non-CLI configuration. PR-URL: https://github.com/nodejs/node/pull/54013 Reviewed-By: Chemi Atlow Reviewed-By: Yagiz Nizipli Reviewed-By: Moshe Atlow --- lib/internal/test_runner/harness.js | 39 ++++++++++++++--------------- lib/internal/test_runner/runner.js | 2 +- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index e595f2ed1dff61..ac52307cc38be5 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -28,18 +28,20 @@ const { } = require('internal/test_runner/utils'); const { queueMicrotask } = require('internal/process/task_queues'); const { bigint: hrtime } = process.hrtime; - +const resolvedPromise = PromiseResolve(); const testResources = new SafeMap(); +let globalRoot; testResources.set(reporterScope.asyncId(), reporterScope); function createTestTree(options = kEmptyObject) { - return setup(new Test({ __proto__: null, ...options, name: '' })); + globalRoot = setup(new Test({ __proto__: null, ...options, name: '' })); + return globalRoot; } function createProcessEventHandler(eventName, rootTest) { return (err) => { - if (!rootTest.harness.bootstrapComplete) { + if (rootTest.harness.bootstrapPromise) { // Something went wrong during the asynchronous portion of bootstrapping // the test runner. Since the test runner is not setup properly, we can't // do anything but throw the error. @@ -196,7 +198,7 @@ function setup(root) { root.harness = { __proto__: null, allowTestsToRun: false, - bootstrapComplete: false, + bootstrapPromise: resolvedPromise, watching: false, coverage: FunctionPrototypeBind(collectCoverage, null, root, coverage), resetCounters() { @@ -222,33 +224,30 @@ function setup(root) { return root; } -let globalRoot; -let asyncBootstrap; function lazyBootstrapRoot() { if (!globalRoot) { - globalRoot = createTestTree({ __proto__: null, entryFile: process.argv?.[1] }); + // This is where the test runner is bootstrapped when node:test is used + // without the --test flag or the run() API. + createTestTree({ __proto__: null, entryFile: process.argv?.[1] }); globalRoot.reporter.on('test:fail', (data) => { if (data.todo === undefined || data.todo === false) { process.exitCode = kGenericUserError; } }); - asyncBootstrap = setupTestReporters(globalRoot.reporter); + globalRoot.harness.bootstrapPromise = setupTestReporters(globalRoot.reporter); } return globalRoot; } -async function startSubtest(subtest) { - if (asyncBootstrap) { +async function startSubtestAfterBootstrap(subtest) { + if (subtest.root.harness.bootstrapPromise) { // Only incur the overhead of awaiting the Promise once. - await asyncBootstrap; - asyncBootstrap = undefined; - if (!subtest.root.harness.bootstrapComplete) { - subtest.root.harness.bootstrapComplete = true; - queueMicrotask(() => { - subtest.root.harness.allowTestsToRun = true; - subtest.root.processPendingSubtests(); - }); - } + await subtest.root.harness.bootstrapPromise; + subtest.root.harness.bootstrapPromise = null; + queueMicrotask(() => { + subtest.root.harness.allowTestsToRun = true; + subtest.root.processPendingSubtests(); + }); } await subtest.start(); @@ -262,7 +261,7 @@ function runInParentContext(Factory) { return PromiseResolve(); } - return startSubtest(subtest); + return startSubtestAfterBootstrap(subtest); } const test = (name, options, fn) => { diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 31a253c776a81e..7443502848f930 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -596,7 +596,7 @@ function run(options = kEmptyObject) { teardown = undefined; } const runFiles = () => { - root.harness.bootstrapComplete = true; + root.harness.bootstrapPromise = null; root.harness.allowTestsToRun = true; return SafePromiseAllSettledReturnVoid(testFiles, (path) => { const subtest = runTestFile(path, filesWatcher, opts); From a6eedc401da5197ac240c963a5b5daf8ace24f20 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sat, 27 Jul 2024 06:35:40 -0700 Subject: [PATCH 096/120] meta: add `sqlite` to js subsystems PR-URL: https://github.com/nodejs/node/pull/53911 Reviewed-By: Antoine du Hamel Reviewed-By: Jake Yuesong Li --- .github/label-pr-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/label-pr-config.yml b/.github/label-pr-config.yml index 4f2c8bbce7232a..b3aaa0bf2ce9b8 100644 --- a/.github/label-pr-config.yml +++ b/.github/label-pr-config.yml @@ -187,6 +187,7 @@ allJsSubSystems: - readline - repl - report + - sqlite - stream - string_decoder - timers From c6656c9251d7971b7eac89c5dfc4c7d18aa35a6d Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Thu, 25 Jul 2024 04:26:27 -0700 Subject: [PATCH 097/120] test: move shared module to `test/common` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `test/fixtures/process-exit-code-cases.js` is a shared module and should be in `test/common` (so it gets linted, etc.) and documented in `test/common/README.md`. PR-URL: https://github.com/nodejs/node/pull/54042 Reviewed-By: Yagiz Nizipli Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: Moshe Atlow --- test/common/README.md | 54 +++++++++++++------ .../process-exit-code-cases.js | 4 +- test/parallel/test-process-exit-code.js | 2 +- test/parallel/test-worker-exit-code.js | 2 +- 4 files changed, 42 insertions(+), 20 deletions(-) rename test/{fixtures => common}/process-exit-code-cases.js (97%) diff --git a/test/common/README.md b/test/common/README.md index 3e0dcd6ca73584..6d74d6c4f455ec 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -2,7 +2,7 @@ This directory contains modules used to test the Node.js implementation. -## Table of Contents +## Table of contents * [ArrayStream module](#arraystream-module) * [Benchmark module](#benchmark-module) @@ -19,13 +19,14 @@ This directory contains modules used to test the Node.js implementation. * [HTTP2 module](#http2-module) * [Internet module](#internet-module) * [ongc module](#ongc-module) +* [process-exit-code-test-cases module](#process-exit-code-test-cases-module) * [Report module](#report-module) * [tick module](#tick-module) * [tmpdir module](#tmpdir-module) * [UDP pair helper](#udp-pair-helper) * [WPT module](#wpt-module) -## Benchmark Module +## Benchmark module The `benchmark` module is used by tests to run benchmarks. @@ -35,7 +36,7 @@ The `benchmark` module is used by tests to run benchmarks. * `env` [\][] Environment variables to be applied during the run. -## Child Process Module +## Child Process module The `child_process` module is used by tests that launch child processes. @@ -79,7 +80,7 @@ Similar to `expectSyncExit()` with the `status` expected to be 0 and Similar to `spawnSyncAndExitWithoutError()`, but with an additional `expectations` parameter. -## Common Module API +## Common module API The `common` module is used by tests for consistency across repeated tasks. @@ -488,7 +489,7 @@ was compiled with a pointer size smaller than 64 bits. Skip the rest of the tests in the current file when not running on a main thread. -## ArrayStream Module +## ArrayStream module The `ArrayStream` module provides a simple `Stream` that pushes elements from a given array. @@ -503,7 +504,7 @@ stream.run(['a', 'b', 'c']); It can be used within tests as a simple mock stream. -## Countdown Module +## Countdown module The `Countdown` module provides a simple countdown mechanism for tests that require a particular action to be taken after a given number of completed @@ -607,7 +608,7 @@ used to interact with the `node inspect` CLI. These functions are: * `stepCommand()` * `quit()` -## `DNS` Module +## `DNS` module The `DNS` module provides utilities related to the `dns` built-in module. @@ -698,7 +699,7 @@ A comma-separated list of variables names that are appended to the global variable allowlist. Alternatively, if `NODE_TEST_KNOWN_GLOBALS` is set to `'0'`, global leak detection is disabled. -## Fixtures Module +## Fixtures module The `common/fixtures` module provides convenience methods for working with files in the `test/fixtures` directory. @@ -773,7 +774,7 @@ validateSnapshotNodes('TLSWRAP', [ ]); ``` -## hijackstdio Module +## hijackstdio module The `hijackstdio` module provides utility functions for temporarily redirecting `stdout` and `stderr` output. @@ -821,7 +822,7 @@ original state after calling [`hijackstdio.hijackStdErr()`][]. Restore the original `process.stdout.write()`. Used to restore `stdout` to its original state after calling [`hijackstdio.hijackStdOut()`][]. -## HTTP/2 Module +## HTTP/2 module The http2.js module provides a handful of utilities for creating mock HTTP/2 frames for testing of HTTP/2 endpoints @@ -940,7 +941,7 @@ upon initial establishment of a connection. socket.write(http2.kClientMagic); ``` -## Internet Module +## Internet module The `common/internet` module provides utilities for working with internet-related tests. @@ -974,7 +975,7 @@ via `NODE_TEST_*` environment variables. For example, to configure `internet.addresses.INET_HOST`, set the environment variable `NODE_TEST_INET_HOST` to a specified host. -## ongc Module +## ongc module The `ongc` module allows a garbage collection listener to be installed. The module exports a single `onGC()` function. @@ -1002,7 +1003,28 @@ a full `setImmediate()` invocation passes. `listener` is an object to make it easier to use a closure; the target object should not be in scope when `listener.ongc()` is created. -## Report Module +## process-exit-code-test-cases module + +The `process-exit-code-test-cases` module provides a set of shared test cases +for testing the exit codes of the `process` object. The test cases are shared +between `test/parallel/test-process-exit-code.js` and +`test/parallel/test-worker-exit-code.js`. + +### `getTestCases(isWorker)` + +* `isWorker` [\][] +* return [\][] + +Returns an array of test cases for testing the exit codes of the `process`. Each +test case is an object with a `func` property that is a function that runs the +test case, a `result` property that is the expected exit code, and sometimes an +`error` property that is a regular expression that the error message should +match when the test case is run in a worker thread. + +The `isWorker` parameter is used to adjust the test cases for worker threads. +The default value is `false`. + +## Report module The `report` module provides helper functions for testing diagnostic reporting functionality. @@ -1051,7 +1073,7 @@ into `targetExecutable` and sign it if necessary. If `verifyWorkflow` is false (default) and any of the steps fails, it skips the tests. Otherwise, an error is thrown. -## tick Module +## tick module The `tick` module provides a helper function that can be used to call a callback after a given number of event loop "ticks". @@ -1061,7 +1083,7 @@ after a given number of event loop "ticks". * `x` [\][] Number of event loop "ticks". * `cb` [\][] A callback function. -## tmpdir Module +## tmpdir module The `tmpdir` module supports the use of a temporary directory for testing. @@ -1129,7 +1151,7 @@ is an `FakeUDPWrap` connected to the other side. There is no difference between client or server side beyond their names. -## WPT Module +## WPT module ### `harness` diff --git a/test/fixtures/process-exit-code-cases.js b/test/common/process-exit-code-cases.js similarity index 97% rename from test/fixtures/process-exit-code-cases.js rename to test/common/process-exit-code-cases.js index 05b01afd8f4630..4649b6f94201fc 100644 --- a/test/fixtures/process-exit-code-cases.js +++ b/test/common/process-exit-code-cases.js @@ -36,7 +36,7 @@ function getTestCases(isWorker = false) { function exitWithOneOnUncaught() { process.exitCode = 99; process.on('exit', (code) => { - // cannot use assert because it will be uncaughtException -> 1 exit code + // Cannot use assert because it will be uncaughtException -> 1 exit code // that will render this test useless if (code !== 1 || process.exitCode !== 1) { console.log('wrong code! expected 1 for uncaughtException'); @@ -113,7 +113,7 @@ function getTestCases(isWorker = false) { function exitWithThrowInUncaughtHandler() { process.on('uncaughtException', () => { - throw new Error('ok') + throw new Error('ok'); }); throw new Error('bad'); } diff --git a/test/parallel/test-process-exit-code.js b/test/parallel/test-process-exit-code.js index 51d23c35c5665e..1049f372d7219e 100644 --- a/test/parallel/test-process-exit-code.js +++ b/test/parallel/test-process-exit-code.js @@ -24,7 +24,7 @@ require('../common'); const assert = require('assert'); const debug = require('util').debuglog('test'); -const { getTestCases } = require('../fixtures/process-exit-code-cases'); +const { getTestCases } = require('../common/process-exit-code-cases'); const testCases = getTestCases(false); if (!process.argv[2]) { diff --git a/test/parallel/test-worker-exit-code.js b/test/parallel/test-worker-exit-code.js index bfa3df924bd71c..738a8b038e8285 100644 --- a/test/parallel/test-worker-exit-code.js +++ b/test/parallel/test-worker-exit-code.js @@ -8,7 +8,7 @@ const assert = require('assert'); const worker = require('worker_threads'); const { Worker, parentPort } = worker; -const { getTestCases } = require('../fixtures/process-exit-code-cases'); +const { getTestCases } = require('../common/process-exit-code-cases'); const testCases = getTestCases(true); // Do not use isMainThread so that this test itself can be run inside a Worker. From 10bea42f816efd85ad08a0d30f8b249f9402c6e0 Mon Sep 17 00:00:00 2001 From: "Benjamin E. Coe" Date: Sat, 27 Jul 2024 17:52:23 -0400 Subject: [PATCH 098/120] build: update gcovr to 7.2 and codecov config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/54019 Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Michaël Zasso --- .../workflows/coverage-linux-without-intl.yml | 4 ++-- .github/workflows/coverage-linux.yml | 4 ++-- Makefile | 9 +++++---- codecov.yml | 18 +++++++----------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.github/workflows/coverage-linux-without-intl.yml b/.github/workflows/coverage-linux-without-intl.yml index e26d7a99533898..66c192c95d605b 100644 --- a/.github/workflows/coverage-linux-without-intl.yml +++ b/.github/workflows/coverage-linux-without-intl.yml @@ -60,7 +60,7 @@ jobs: - name: Environment Information run: npx envinfo - name: Install gcovr - run: pip install gcovr==4.2 + run: pip install gcovr==7.2 - name: Build run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn --coverage --without-intl" # TODO(bcoe): fix the couple tests that fail with the inspector enabled. @@ -72,7 +72,7 @@ jobs: env: NODE_OPTIONS: --max-old-space-size=8192 - name: Report C++ - run: cd out && gcovr --gcov-exclude='.*\b(deps|usr|out|obj|cctest|embedding)\b' -v -r Release/obj.target --xml -o ../coverage/coverage-cxx.xml --root=$(cd ../ && pwd) + run: gcovr --object-directory=out -v --filter src --xml -o ./coverage/coverage-cxx.xml --root=./ --gcov-executable="llvm-cov-18 gcov" # Clean temporary output from gcov and c8, so that it's not uploaded: - name: Clean tmp run: rm -rf coverage/tmp && rm -rf out diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml index 63b523638b126b..030eaabbe25efa 100644 --- a/.github/workflows/coverage-linux.yml +++ b/.github/workflows/coverage-linux.yml @@ -60,7 +60,7 @@ jobs: - name: Environment Information run: npx envinfo - name: Install gcovr - run: pip install gcovr==4.2 + run: pip install gcovr==7.2 - name: Build run: make build-ci -j4 V=1 CONFIG_FLAGS="--error-on-warn --coverage" # TODO(bcoe): fix the couple tests that fail with the inspector enabled. @@ -72,7 +72,7 @@ jobs: env: NODE_OPTIONS: --max-old-space-size=8192 - name: Report C++ - run: cd out && gcovr --gcov-exclude='.*\b(deps|usr|out|obj|cctest|embedding)\b' -v -r Release/obj.target --xml -o ../coverage/coverage-cxx.xml --root=$(cd ../ && pwd) + run: gcovr --object-directory=out -v --filter src --xml -o ./coverage/coverage-cxx.xml --root=./ --gcov-executable="llvm-cov-18 gcov" # Clean temporary output from gcov and c8, so that it's not uploaded: - name: Clean tmp run: rm -rf coverage/tmp && rm -rf out diff --git a/Makefile b/Makefile index 7468d23909edf3..dba16e5e2e3fbd 100644 --- a/Makefile +++ b/Makefile @@ -254,7 +254,7 @@ coverage: coverage-test ## Run the tests and generate a coverage report. .PHONY: coverage-build coverage-build: all -$(MAKE) coverage-build-js - if [ ! -d gcovr ]; then $(PYTHON) -m pip install -t gcovr gcovr==4.2; fi + if [ ! -d gcovr ]; then $(PYTHON) -m pip install -t gcovr gcovr==7.2; fi $(MAKE) .PHONY: coverage-build-js @@ -270,9 +270,10 @@ coverage-test: coverage-build -NODE_V8_COVERAGE=coverage/tmp \ TEST_CI_ARGS="$(TEST_CI_ARGS) --type=coverage" $(MAKE) $(COVTESTS) $(MAKE) coverage-report-js - -(cd out && PYTHONPATH=../gcovr $(PYTHON) -m gcovr \ - --gcov-exclude='.*\b(deps|usr|out|cctest|embedding)\b' -v \ - -r ../src/ --object-directory Release/obj.target \ + -(PYTHONPATH=./gcovr $(PYTHON) -m gcovr \ + --object-directory=out \ + --filter src -v \ + --root ./ \ --html --html-details -o ../coverage/cxxcoverage.html \ --gcov-executable="$(GCOV)") @printf "Javascript coverage %%: " diff --git a/codecov.yml b/codecov.yml index 910f4b32192146..05410f5c18957b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,20 +1,16 @@ -# TODO(bcoe): re-enable coverage report comments, once we can figure out -# how to make them more accurate for the Node.js project, -# See: https://github.com/nodejs/node/issues/35759 -comment: false -# # Only show diff and files changed: -# layout: "diff, files" -# # Don't post if no changes in coverage: -# require_changes: true +comment: + # Only show diff and files changed: + layout: diff, files + # Don't post if no changes in coverage: + require_changes: true codecov: - branch: main notify: # Wait for all coverage builds: # - coverage-linux.yml - # - coverage-windows.yml + # - coverage-windows.yml [manually disabled see #50489] # - coverage-linux-without-intl.yml - after_n_builds: 3 + after_n_builds: 2 coverage: # Useful for blocking Pull Requests that don't meet a particular coverage threshold. From 3999021653d0476b60854f2809b2fb93693e714d Mon Sep 17 00:00:00 2001 From: Emil Tayeb <49617556+Emiltayeb@users.noreply.github.com> Date: Sun, 28 Jul 2024 09:22:10 +0300 Subject: [PATCH 099/120] test_runner: switched to internal readline interface Switched to using internal interface after PR-URL: https://github.com/nodejs/node/pull/54000 Reviewed-By: Chemi Atlow Reviewed-By: Moshe Atlow Reviewed-By: Benjamin Gruenbaum --- lib/internal/test_runner/runner.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 7443502848f930..a14cc97ce8690c 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -32,8 +32,7 @@ const { spawn } = require('child_process'); const { finished } = require('internal/streams/end-of-stream'); const { resolve } = require('path'); const { DefaultDeserializer, DefaultSerializer } = require('v8'); -// TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern. -const { createInterface } = require('readline'); +const { Interface } = require('internal/readline/interface'); const { deserializeError } = require('internal/error_serdes'); const { Buffer } = require('buffer'); const { FilesWatcher } = require('internal/watch_mode/files_watcher'); @@ -354,7 +353,7 @@ function runTestFile(path, filesWatcher, opts) { subtest.parseMessage(data); }); - const rl = createInterface({ __proto__: null, input: child.stderr }); + const rl = new Interface({ __proto__: null, input: child.stderr }); rl.on('line', (line) => { if (isInspectorMessage(line)) { process.stderr.write(line + '\n'); From 5e03c17aaeaa6e40626cc2aaecb4f958b063a3c5 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 22 Jul 2024 12:00:39 -0400 Subject: [PATCH 100/120] fs: optimize `fs.cpSync` js calls PR-URL: https://github.com/nodejs/node/pull/53614 Reviewed-By: Matteo Collina Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell --- lib/internal/fs/cp/cp-sync.js | 164 ++++++---------------------- src/node_errors.h | 7 ++ src/node_file.cc | 128 ++++++++++++++++++++++ test/fixtures/permission/fs-read.js | 8 +- typings/internalBinding/fs.d.ts | 3 + 5 files changed, 172 insertions(+), 138 deletions(-) diff --git a/lib/internal/fs/cp/cp-sync.js b/lib/internal/fs/cp/cp-sync.js index aebc575365dd2e..1e922b7805fc8c 100644 --- a/lib/internal/fs/cp/cp-sync.js +++ b/lib/internal/fs/cp/cp-sync.js @@ -2,33 +2,25 @@ // This file is a modified version of the fs-extra's copySync method. -const { areIdentical, isSrcSubdir } = require('internal/fs/cp/cp'); +const fsBinding = internalBinding('fs'); +const { isSrcSubdir } = require('internal/fs/cp/cp'); const { codes: { - ERR_FS_CP_DIR_TO_NON_DIR, ERR_FS_CP_EEXIST, ERR_FS_CP_EINVAL, - ERR_FS_CP_FIFO_PIPE, - ERR_FS_CP_NON_DIR_TO_DIR, - ERR_FS_CP_SOCKET, ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY, - ERR_FS_CP_UNKNOWN, - ERR_FS_EISDIR, ERR_INVALID_RETURN_VALUE, } } = require('internal/errors'); const { os: { errno: { EEXIST, - EISDIR, EINVAL, - ENOTDIR, }, }, } = internalBinding('constants'); const { chmodSync, copyFileSync, - existsSync, lstatSync, mkdirSync, opendirSync, @@ -42,7 +34,6 @@ const { dirname, isAbsolute, join, - parse, resolve, } = require('path'); const { isPromise } = require('util/types'); @@ -54,145 +45,38 @@ function cpSyncFn(src, dest, opts) { 'node is not recommended'; process.emitWarning(warning, 'TimestampPrecisionWarning'); } - const { srcStat, destStat, skipped } = checkPathsSync(src, dest, opts); - if (skipped) return; - checkParentPathsSync(src, srcStat, dest); - return checkParentDir(destStat, src, dest, opts); -} - -function checkPathsSync(src, dest, opts) { if (opts.filter) { const shouldCopy = opts.filter(src, dest); if (isPromise(shouldCopy)) { throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy); } - if (!shouldCopy) return { __proto__: null, skipped: true }; + if (!shouldCopy) return; } - const { srcStat, destStat } = getStatsSync(src, dest, opts); - if (destStat) { - if (areIdentical(srcStat, destStat)) { - throw new ERR_FS_CP_EINVAL({ - message: 'src and dest cannot be the same', - path: dest, - syscall: 'cp', - errno: EINVAL, - code: 'EINVAL', - }); - } - if (srcStat.isDirectory() && !destStat.isDirectory()) { - throw new ERR_FS_CP_DIR_TO_NON_DIR({ - message: `cannot overwrite non-directory ${dest} ` + - `with directory ${src}`, - path: dest, - syscall: 'cp', - errno: EISDIR, - code: 'EISDIR', - }); - } - if (!srcStat.isDirectory() && destStat.isDirectory()) { - throw new ERR_FS_CP_NON_DIR_TO_DIR({ - message: `cannot overwrite directory ${dest} ` + - `with non-directory ${src}`, - path: dest, - syscall: 'cp', - errno: ENOTDIR, - code: 'ENOTDIR', - }); - } - } - - if (srcStat.isDirectory() && isSrcSubdir(src, dest)) { - throw new ERR_FS_CP_EINVAL({ - message: `cannot copy ${src} to a subdirectory of self ${dest}`, - path: dest, - syscall: 'cp', - errno: EINVAL, - code: 'EINVAL', - }); - } - return { __proto__: null, srcStat, destStat, skipped: false }; -} + fsBinding.cpSyncCheckPaths(src, dest, opts.dereference, opts.recursive); -function getStatsSync(src, dest, opts) { - const statFunc = opts.dereference ? statSync : lstatSync; - const srcStat = statFunc(src, { bigint: true, throwIfNoEntry: true }); - const destStat = statFunc(dest, { bigint: true, throwIfNoEntry: false }); - return { srcStat, destStat }; + return getStats(src, dest, opts); } -function checkParentPathsSync(src, srcStat, dest) { - const srcParent = resolve(dirname(src)); - const destParent = resolve(dirname(dest)); - if (destParent === srcParent || destParent === parse(destParent).root) return; - const destStat = statSync(destParent, { bigint: true, throwIfNoEntry: false }); - - if (destStat === undefined) { - return; - } - - if (areIdentical(srcStat, destStat)) { - throw new ERR_FS_CP_EINVAL({ - message: `cannot copy ${src} to a subdirectory of self ${dest}`, - path: dest, - syscall: 'cp', - errno: EINVAL, - code: 'EINVAL', - }); - } - return checkParentPathsSync(src, srcStat, destParent); -} - -function checkParentDir(destStat, src, dest, opts) { - const destParent = dirname(dest); - if (!existsSync(destParent)) mkdirSync(destParent, { recursive: true }); - return getStats(destStat, src, dest, opts); -} - -function getStats(destStat, src, dest, opts) { +function getStats(src, dest, opts) { + // TODO(@anonrig): Avoid making two stat calls. const statSyncFn = opts.dereference ? statSync : lstatSync; const srcStat = statSyncFn(src); + const destStat = statSyncFn(dest, { bigint: true, throwIfNoEntry: false }); if (srcStat.isDirectory() && opts.recursive) { return onDir(srcStat, destStat, src, dest, opts); - } else if (srcStat.isDirectory()) { - throw new ERR_FS_EISDIR({ - message: `${src} is a directory (not copied)`, - path: src, - syscall: 'cp', - errno: EINVAL, - code: 'EISDIR', - }); } else if (srcStat.isFile() || srcStat.isCharacterDevice() || srcStat.isBlockDevice()) { return onFile(srcStat, destStat, src, dest, opts); } else if (srcStat.isSymbolicLink()) { - return onLink(destStat, src, dest, opts); - } else if (srcStat.isSocket()) { - throw new ERR_FS_CP_SOCKET({ - message: `cannot copy a socket file: ${dest}`, - path: dest, - syscall: 'cp', - errno: EINVAL, - code: 'EINVAL', - }); - } else if (srcStat.isFIFO()) { - throw new ERR_FS_CP_FIFO_PIPE({ - message: `cannot copy a FIFO pipe: ${dest}`, - path: dest, - syscall: 'cp', - errno: EINVAL, - code: 'EINVAL', - }); + return onLink(destStat, src, dest, opts.verbatimSymlinks); } - throw new ERR_FS_CP_UNKNOWN({ - message: `cannot copy an unknown file type: ${dest}`, - path: dest, - syscall: 'cp', - errno: EINVAL, - code: 'EINVAL', - }); + + // It is not possible to get here because all possible cases are handled above. + const assert = require('internal/assert'); + assert.fail('Unreachable code'); } function onFile(srcStat, destStat, src, dest, opts) { @@ -200,6 +84,7 @@ function onFile(srcStat, destStat, src, dest, opts) { return mayCopyFile(srcStat, src, dest, opts); } +// TODO(@anonrig): Move this function to C++. function mayCopyFile(srcStat, src, dest, opts) { if (opts.force) { unlinkSync(dest); @@ -249,6 +134,7 @@ function setDestTimestamps(src, dest) { return utimesSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime); } +// TODO(@anonrig): Move this function to C++. function onDir(srcStat, destStat, src, dest, opts) { if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts); return copyDir(src, dest, opts); @@ -260,6 +146,7 @@ function mkDirAndCopy(srcMode, src, dest, opts) { return setDestMode(dest, srcMode); } +// TODO(@anonrig): Move this function to C++. function copyDir(src, dest, opts) { const dir = opendirSync(src); @@ -270,17 +157,28 @@ function copyDir(src, dest, opts) { const { name } = dirent; const srcItem = join(src, name); const destItem = join(dest, name); - const { destStat, skipped } = checkPathsSync(srcItem, destItem, opts); - if (!skipped) getStats(destStat, srcItem, destItem, opts); + let shouldCopy = true; + + if (opts.filter) { + shouldCopy = opts.filter(srcItem, destItem); + if (isPromise(shouldCopy)) { + throw new ERR_INVALID_RETURN_VALUE('boolean', 'filter', shouldCopy); + } + } + + if (shouldCopy) { + getStats(srcItem, destItem, opts); + } } } finally { dir.closeSync(); } } -function onLink(destStat, src, dest, opts) { +// TODO(@anonrig): Move this function to C++. +function onLink(destStat, src, dest, verbatimSymlinks) { let resolvedSrc = readlinkSync(src); - if (!opts.verbatimSymlinks && !isAbsolute(resolvedSrc)) { + if (!verbatimSymlinks && !isAbsolute(resolvedSrc)) { resolvedSrc = resolve(dirname(src), resolvedSrc); } if (!destStat) { diff --git a/src/node_errors.h b/src/node_errors.h index 0a74373cf5d333..8d70680171b1a8 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -70,6 +70,13 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details); V(ERR_DLOPEN_FAILED, Error) \ V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \ V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \ + V(ERR_FS_CP_EINVAL, Error) \ + V(ERR_FS_CP_DIR_TO_NON_DIR, Error) \ + V(ERR_FS_CP_NON_DIR_TO_DIR, Error) \ + V(ERR_FS_EISDIR, Error) \ + V(ERR_FS_CP_SOCKET, Error) \ + V(ERR_FS_CP_FIFO_PIPE, Error) \ + V(ERR_FS_CP_UNKNOWN, Error) \ V(ERR_ILLEGAL_CONSTRUCTOR, Error) \ V(ERR_INVALID_ADDRESS, Error) \ V(ERR_INVALID_ARG_VALUE, TypeError) \ diff --git a/src/node_file.cc b/src/node_file.cc index 7040a17bfa2edf..c13c364665f4bc 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -3016,6 +3016,130 @@ static void GetFormatOfExtensionlessFile( return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT); } +static void CpSyncCheckPaths(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); + + CHECK_EQ(args.Length(), 4); // src, dest, dereference, recursive + + BufferValue src(isolate, args[0]); + CHECK_NOT_NULL(*src); + ToNamespacedPath(env, &src); + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, permission::PermissionScope::kFileSystemRead, src.ToStringView()); + auto src_path = std::filesystem::path(src.ToStringView()); + + BufferValue dest(isolate, args[1]); + CHECK_NOT_NULL(*dest); + ToNamespacedPath(env, &dest); + THROW_IF_INSUFFICIENT_PERMISSIONS( + env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView()); + auto dest_path = std::filesystem::path(dest.ToStringView()); + + bool dereference = args[2]->IsTrue(); + bool recursive = args[3]->IsTrue(); + + std::error_code error_code; + auto src_status = dereference + ? std::filesystem::symlink_status(src_path, error_code) + : std::filesystem::status(src_path, error_code); + if (error_code) { + return env->ThrowUVException(EEXIST, "lstat", nullptr, src.out()); + } + auto dest_status = + dereference ? std::filesystem::symlink_status(dest_path, error_code) + : std::filesystem::status(dest_path, error_code); + + bool dest_exists = !error_code && dest_status.type() != + std::filesystem::file_type::not_found; + bool src_is_dir = src_status.type() == std::filesystem::file_type::directory; + + if (!error_code) { + // Check if src and dest are identical. + if (std::filesystem::equivalent(src_path, dest_path)) { + std::string message = + "src and dest cannot be the same " + dest_path.string(); + return THROW_ERR_FS_CP_EINVAL(env, message.c_str()); + } + + const bool dest_is_dir = + dest_status.type() == std::filesystem::file_type::directory; + + if (src_is_dir && !dest_is_dir) { + std::string message = "Cannot overwrite non-directory " + + src_path.string() + " with directory " + + dest_path.string(); + return THROW_ERR_FS_CP_DIR_TO_NON_DIR(env, message.c_str()); + } + + if (!src_is_dir && dest_is_dir) { + std::string message = "Cannot overwrite directory " + dest_path.string() + + " with non-directory " + src_path.string(); + return THROW_ERR_FS_CP_NON_DIR_TO_DIR(env, message.c_str()); + } + } + + std::string dest_path_str = dest_path.string(); + // Check if dest_path is a subdirectory of src_path. + if (src_is_dir && dest_path_str.starts_with(src_path.string())) { + std::string message = "Cannot copy " + src_path.string() + + " to a subdirectory of self " + dest_path.string(); + return THROW_ERR_FS_CP_EINVAL(env, message.c_str()); + } + + auto dest_parent = dest_path.parent_path(); + // "/" parent is itself. Therefore, we need to check if the parent is the same + // as itself. + while (src_path.parent_path() != dest_parent && + dest_parent.has_parent_path() && + dest_parent.parent_path() != dest_parent) { + if (std::filesystem::equivalent( + src_path, dest_path.parent_path(), error_code)) { + std::string message = "Cannot copy " + src_path.string() + + " to a subdirectory of self " + dest_path.string(); + return THROW_ERR_FS_CP_EINVAL(env, message.c_str()); + } + + // If equivalent fails, it's highly likely that dest_parent does not exist + if (error_code) { + break; + } + + dest_parent = dest_parent.parent_path(); + } + + if (src_is_dir && !recursive) { + std::string message = + "Recursive option not enabled, cannot copy a directory: " + + src_path.string(); + return THROW_ERR_FS_EISDIR(env, message.c_str()); + } + + switch (src_status.type()) { + case std::filesystem::file_type::socket: { + std::string message = "Cannot copy a socket file: " + dest_path.string(); + return THROW_ERR_FS_CP_SOCKET(env, message.c_str()); + } + case std::filesystem::file_type::fifo: { + std::string message = "Cannot copy a FIFO pipe: " + dest_path.string(); + return THROW_ERR_FS_CP_FIFO_PIPE(env, message.c_str()); + } + case std::filesystem::file_type::unknown: { + std::string message = + "Cannot copy an unknown file type: " + dest_path.string(); + return THROW_ERR_FS_CP_UNKNOWN(env, message.c_str()); + } + default: + break; + } + + // Optimization opportunity: Check if this "exists" call is good for + // performance. + if (!dest_exists || !std::filesystem::exists(dest_path.parent_path())) { + std::filesystem::create_directories(dest_path.parent_path(), error_code); + } +} + BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile( Environment* env, const std::string& file_path) { THROW_IF_INSUFFICIENT_PERMISSIONS( @@ -3364,6 +3488,8 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data, SetMethod(isolate, target, "mkdtemp", Mkdtemp); + SetMethod(isolate, target, "cpSyncCheckPaths", CpSyncCheckPaths); + StatWatcher::CreatePerIsolateProperties(isolate_data, target); BindingData::CreatePerIsolateProperties(isolate_data, target); @@ -3473,6 +3599,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(RealPath); registry->Register(CopyFile); + registry->Register(CpSyncCheckPaths); + registry->Register(Chmod); registry->Register(FChmod); diff --git a/test/fixtures/permission/fs-read.js b/test/fixtures/permission/fs-read.js index 92e53c0b046124..0ce7d65b21be1a 100644 --- a/test/fixtures/permission/fs-read.js +++ b/test/fixtures/permission/fs-read.js @@ -161,23 +161,21 @@ const regularFile = __filename; }, common.expectsError({ code: 'ERR_ACCESS_DENIED', permission: 'FileSystemRead', - // cpSync calls lstatSync before reading blockedFile - resource: blockedFile, + resource: path.toNamespacedPath(blockedFile), })); assert.throws(() => { fs.cpSync(blockedFileURL, path.join(blockedFolder, 'any-other-file')); }, common.expectsError({ code: 'ERR_ACCESS_DENIED', permission: 'FileSystemRead', - // cpSync calls lstatSync before reading blockedFile - resource: blockedFile, + resource: path.toNamespacedPath(blockedFile), })); assert.throws(() => { fs.cpSync(blockedFile, path.join(__dirname, 'any-other-file')); }, common.expectsError({ code: 'ERR_ACCESS_DENIED', permission: 'FileSystemRead', - resource: blockedFile, + resource: path.toNamespacedPath(blockedFile), })); } diff --git a/typings/internalBinding/fs.d.ts b/typings/internalBinding/fs.d.ts index db8eac020ea717..51c8e462a7b8ca 100644 --- a/typings/internalBinding/fs.d.ts +++ b/typings/internalBinding/fs.d.ts @@ -76,6 +76,8 @@ declare namespace InternalFSBinding { function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, req: undefined, ctx: FSSyncContext): void; function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, usePromises: typeof kUsePromises): Promise; + function cpSyncCheckPaths(src: StringOrBuffer, dest: StringOrBuffer, dereference: boolean, recursive: boolean): void; + function fchmod(fd: number, mode: number, req: FSReqCallback): void; function fchmod(fd: number, mode: number): void; function fchmod(fd: number, mode: number, usePromises: typeof kUsePromises): Promise; @@ -255,6 +257,7 @@ export interface FsBinding { chown: typeof InternalFSBinding.chown; close: typeof InternalFSBinding.close; copyFile: typeof InternalFSBinding.copyFile; + cpSyncCheckPaths: typeof InternalFSBinding.cpSyncCheckPaths; fchmod: typeof InternalFSBinding.fchmod; fchown: typeof InternalFSBinding.fchown; fdatasync: typeof InternalFSBinding.fdatasync; From 68e444d2d8d087098feef8966c1a6e5e2768fbeb Mon Sep 17 00:00:00 2001 From: Kohei Ueno Date: Sun, 28 Jul 2024 18:57:38 +0900 Subject: [PATCH 101/120] http: add diagnostics channel `http.client.request.error` PR-URL: https://github.com/nodejs/node/pull/54054 Reviewed-By: Paolo Insogna Reviewed-By: Matteo Collina Reviewed-By: Marco Ippolito Reviewed-By: Ethan Arrowood --- doc/api/diagnostics_channel.md | 7 ++++++ lib/_http_client.js | 23 ++++++++++++++----- .../parallel/test-diagnostics-channel-http.js | 15 +++++++++++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/doc/api/diagnostics_channel.md b/doc/api/diagnostics_channel.md index 603d41d532bd5b..36ee045703016a 100644 --- a/doc/api/diagnostics_channel.md +++ b/doc/api/diagnostics_channel.md @@ -1129,6 +1129,13 @@ independently. Emitted when client starts a request. +`http.client.request.error` + +* `request` {http.ClientRequest} +* `error` {Error} + +Emitted when an error occurs during a client request. + `http.client.response.finish` * `request` {http.ClientRequest} diff --git a/lib/_http_client.js b/lib/_http_client.js index 0ad234044f6adb..6d3b3591e7ba40 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -90,8 +90,19 @@ const kClientRequestStatistics = Symbol('ClientRequestStatistics'); const dc = require('diagnostics_channel'); const onClientRequestStartChannel = dc.channel('http.client.request.start'); +const onClientRequestErrorChannel = dc.channel('http.client.request.error'); const onClientResponseFinishChannel = dc.channel('http.client.response.finish'); +function emitErrorEvent(request, error) { + if (onClientRequestErrorChannel.hasSubscribers) { + onClientRequestErrorChannel.publish({ + request, + error, + }); + } + request.emit('error', error); +} + const { addAbortSignal, finished } = require('stream'); let debug = require('internal/util/debuglog').debuglog('http', (fn) => { @@ -343,7 +354,7 @@ function ClientRequest(input, options, cb) { if (typeof opts.createConnection === 'function') { const oncreate = once((err, socket) => { if (err) { - process.nextTick(() => this.emit('error', err)); + process.nextTick(() => emitErrorEvent(this, err)); } else { this.onSocket(socket); } @@ -465,7 +476,7 @@ function socketCloseListener() { // receive a response. The error needs to // fire on the request. req.socket._hadError = true; - req.emit('error', new ConnResetException('socket hang up')); + emitErrorEvent(req, new ConnResetException('socket hang up')); } req._closed = true; req.emit('close'); @@ -492,7 +503,7 @@ function socketErrorListener(err) { // For Safety. Some additional errors might fire later on // and we need to make sure we don't double-fire the error event. req.socket._hadError = true; - req.emit('error', err); + emitErrorEvent(req, err); } const parser = socket.parser; @@ -516,7 +527,7 @@ function socketOnEnd() { // If we don't have a response then we know that the socket // ended prematurely and we need to emit an error on the request. req.socket._hadError = true; - req.emit('error', new ConnResetException('socket hang up')); + emitErrorEvent(req, new ConnResetException('socket hang up')); } if (parser) { parser.finish(); @@ -541,7 +552,7 @@ function socketOnData(d) { socket.removeListener('end', socketOnEnd); socket.destroy(); req.socket._hadError = true; - req.emit('error', ret); + emitErrorEvent(req, ret); } else if (parser.incoming && parser.incoming.upgrade) { // Upgrade (if status code 101) or CONNECT const bytesParsed = ret; @@ -872,7 +883,7 @@ function onSocketNT(req, socket, err) { err = new ConnResetException('socket hang up'); } if (err) { - req.emit('error', err); + emitErrorEvent(req, err); } req._closed = true; req.emit('close'); diff --git a/test/parallel/test-diagnostics-channel-http.js b/test/parallel/test-diagnostics-channel-http.js index c2e84444e2866e..e134b9ac05a85d 100644 --- a/test/parallel/test-diagnostics-channel-http.js +++ b/test/parallel/test-diagnostics-channel-http.js @@ -1,5 +1,6 @@ 'use strict'; const common = require('../common'); +const { addresses } = require('../common/internet'); const assert = require('assert'); const http = require('http'); const net = require('net'); @@ -9,9 +10,15 @@ const isHTTPServer = (server) => server instanceof http.Server; const isIncomingMessage = (object) => object instanceof http.IncomingMessage; const isOutgoingMessage = (object) => object instanceof http.OutgoingMessage; const isNetSocket = (socket) => socket instanceof net.Socket; +const isError = (error) => error instanceof Error; dc.subscribe('http.client.request.start', common.mustCall(({ request }) => { assert.strictEqual(isOutgoingMessage(request), true); +}, 2)); + +dc.subscribe('http.client.request.error', common.mustCall(({ request, error }) => { + assert.strictEqual(isOutgoingMessage(request), true); + assert.strictEqual(isError(error), true); })); dc.subscribe('http.client.response.finish', common.mustCall(({ @@ -50,8 +57,14 @@ const server = http.createServer(common.mustCall((req, res) => { res.end('done'); })); -server.listen(() => { +server.listen(async () => { const { port } = server.address(); + const invalidRequest = http.get({ + host: addresses.INVALID_HOST, + }); + await new Promise((resolve) => { + invalidRequest.on('error', resolve); + }); http.get(`http://localhost:${port}`, (res) => { res.resume(); res.on('end', () => { From 063f46dc2a36a924c8429ad5538690aa5fa5c6d4 Mon Sep 17 00:00:00 2001 From: Pietro Marchini Date: Sun, 28 Jul 2024 15:13:59 +0200 Subject: [PATCH 102/120] assert: use isError instead of instanceof in innerOk Co-Authored-By: Ruben Bridgewater Co-Authored-By: Nihar Phansalkar PR-URL: https://github.com/nodejs/node/pull/53980 Fixes: https://github.com/nodejs/node/issues/50780 Reviewed-By: James M Snell Reviewed-By: Ruben Bridgewater Reviewed-By: Marco Ippolito --- lib/assert.js | 2 +- test/parallel/test-assert.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/assert.js b/lib/assert.js index 9dfcf80a913942..eadc3844c20128 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -393,7 +393,7 @@ function innerOk(fn, argLen, value, message) { } else if (message == null) { generatedMessage = true; message = getErrMessage(message, fn); - } else if (message instanceof Error) { + } else if (isError(message)) { throw message; } diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 2964a475c81436..1679edb2941808 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -55,6 +55,16 @@ assert.throws(() => a.ok(false), a.AssertionError, 'ok(false)'); assert.ok(threw, 'Error: ok(false)'); } +// Errors created in different contexts are handled as any other custom error +{ + const context = vm.createContext(); + const error = vm.runInContext('new SyntaxError("custom error")', context); + + assert.throws(() => assert(false, error), { + message: 'custom error', + name: 'SyntaxError' + }); +} a(true); a('test', 'ok(\'test\')'); From 15a94e67b1c8db1a3b6e05abf6cda4ab8df15b93 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Sun, 28 Jul 2024 13:33:05 -0300 Subject: [PATCH 103/120] lib,src: drop --experimental-network-imports PR-URL: https://github.com/nodejs/node/pull/53822 Reviewed-By: Matteo Collina Reviewed-By: Geoffrey Booth Reviewed-By: Marco Ippolito Reviewed-By: James M Snell Reviewed-By: Stephen Belanger Reviewed-By: Antoine du Hamel Reviewed-By: Joyee Cheung --- doc/api/cli.md | 13 - doc/api/errors.md | 17 - doc/api/esm.md | 68 +--- doc/api/module.md | 3 +- doc/node.1 | 3 - lib/internal/errors.js | 4 - lib/internal/modules/esm/get_format.js | 23 -- lib/internal/modules/esm/load.js | 31 +- lib/internal/modules/esm/loader.js | 5 +- lib/internal/modules/esm/resolve.js | 89 +---- src/node_options.cc | 4 - src/node_options.h | 1 - .../test-esm-experimental-warnings.mjs | 1 - test/es-module/test-http-imports-cli.mjs | 48 --- test/es-module/test-http-imports.mjs | 311 ------------------ .../test-require-module-special-import.js | 11 - test/fixtures/es-modules/network-import.mjs | 1 - 17 files changed, 10 insertions(+), 623 deletions(-) delete mode 100644 test/es-module/test-http-imports-cli.mjs delete mode 100644 test/es-module/test-http-imports.mjs delete mode 100644 test/es-module/test-require-module-special-import.js delete mode 100644 test/fixtures/es-modules/network-import.mjs diff --git a/doc/api/cli.md b/doc/api/cli.md index 44f25162721f7a..053aebe7d2b6c0 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -988,18 +988,6 @@ changes: Specify the `module` containing exported [module customization hooks][]. `module` may be any string accepted as an [`import` specifier][]. -### `--experimental-network-imports` - - - -> Stability: 1 - Experimental - -Enable experimental support for the `https:` protocol in `import` specifiers. - ### `--experimental-network-inspection` > Stability: 1 - Experimental @@ -1061,7 +1061,7 @@ Enable the experimental [`node:sqlite`][] module. ### `--experimental-strip-types` > Stability: 1.0 - Early development diff --git a/doc/api/errors.md b/doc/api/errors.md index 82225b26efaf74..4a577595ca41d1 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -4004,7 +4004,7 @@ An error occurred trying to allocate memory. This should never happen. #### `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` Type stripping is not supported for files descendent of a `node_modules` directory. diff --git a/doc/api/inspector.md b/doc/api/inspector.md index c1c391eb14889a..6886cc6a7fe467 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -513,7 +513,7 @@ inspector.Network.requestWillBeSent({ > Stability: 1 - Experimental @@ -529,7 +529,7 @@ the application is about to send an HTTP request. > Stability: 1 - Experimental @@ -545,7 +545,7 @@ HTTP response is available. > Stability: 1 - Experimental diff --git a/doc/api/stream.md b/doc/api/stream.md index 1cf07d00093a26..2d21c2b837f1c1 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -2681,7 +2681,7 @@ further errors except from `_destroy()` may be emitted as `'error'`. #### `stream.duplexPair([options])` * `options` {Object} A value to pass to both [`Duplex`][] constructors, diff --git a/doc/api/test.md b/doc/api/test.md index 7c7c591c67c84f..f930cb03220252 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -1239,7 +1239,7 @@ added: - v18.9.0 - v16.19.0 changes: - - version: REPLACEME + - version: v22.6.0 pr-url: https://github.com/nodejs/node/pull/53866 description: Added the `globPatterns` option. - version: v22.0.0 @@ -3203,7 +3203,7 @@ test('top level test', (t) => { ### `context.filePath` The absolute path of the test file that created the current test. If a test file @@ -3446,7 +3446,7 @@ exposed as part of the API. ### `context.filePath` The absolute path of the test file that created the current suite. If a test diff --git a/doc/api/typescript.md b/doc/api/typescript.md index 69711d36a39349..cddd0dd29f1d86 100644 --- a/doc/api/typescript.md +++ b/doc/api/typescript.md @@ -38,7 +38,7 @@ To use TypeScript with full support for all TypeScript features, including ## Type stripping > Stability: 1.0 - Early development diff --git a/doc/changelogs/CHANGELOG_V22.md b/doc/changelogs/CHANGELOG_V22.md index 93338782dd00e8..d705ef199d6514 100644 --- a/doc/changelogs/CHANGELOG_V22.md +++ b/doc/changelogs/CHANGELOG_V22.md @@ -8,6 +8,7 @@ +22.6.0
22.5.1
22.5.0
22.4.1
@@ -44,6 +45,177 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2024-08-06, Version 22.6.0 (Current), @RafaelGSS + +### Experimental TypeScript support via strip types + +Node.js introduces the `--experimental-strip-types` flag for initial TypeScript support. +This feature strips type annotations from .ts files, allowing them to run +without transforming TypeScript-specific syntax. Current limitations include: + +* Supports only inline type annotations, not features like `enums` or `namespaces`. +* Requires explicit file extensions in import and require statements. +* Enforces the use of the type keyword for type imports to avoid runtime errors. +* Disabled for TypeScript in _node\_modules_ by default. + +Thanks [Marco Ippolito](https://github.com/marco-ippolito) for working on this. + +### Experimental Network Inspection Support in Node.js + +This update introduces the initial support for network inspection in Node.js. +Currently, this is an experimental feature, so you need to enable it using the `--experimental-network-inspection` flag. +With this feature enabled, you can inspect network activities occurring within a JavaScript application. + +To use network inspection, start your Node.js application with the following command: + +```console +$ node --inspect-wait --experimental-network-inspection index.js +``` + +Please note that the network inspection capabilities are in active development. +We are actively working on enhancing this feature and will continue to expand its functionality in future updates. + +* Network inspection is limited to the `http` and `https` modules only. +* The Network tab in Chrome DevTools will not be available until the + [feature request on the Chrome DevTools side](https://issues.chromium.org/issues/353924015) is addressed. + +Thanks [Kohei Ueno](https://github.com/cola119) for working on this. + +### Other Notable Changes + +* \[[`15a94e67b1`](https://github.com/nodejs/node/commit/15a94e67b1)] - **lib,src**: drop --experimental-network-imports (Rafael Gonzaga) [#53822](https://github.com/nodejs/node/pull/53822) +* \[[`68e444d2d8`](https://github.com/nodejs/node/commit/68e444d2d8)] - **(SEMVER-MINOR)** **http**: add diagnostics channel `http.client.request.error` (Kohei Ueno) [#54054](https://github.com/nodejs/node/pull/54054) +* \[[`2d982d3dee`](https://github.com/nodejs/node/commit/2d982d3dee)] - **(SEMVER-MINOR)** **deps**: V8: backport 7857eb34db42 (Stephen Belanger) [#53997](https://github.com/nodejs/node/pull/53997) +* \[[`15816bd0dd`](https://github.com/nodejs/node/commit/15816bd0dd)] - **(SEMVER-MINOR)** **stream**: expose DuplexPair API (Austin Wright) [#34111](https://github.com/nodejs/node/pull/34111) +* \[[`893c864542`](https://github.com/nodejs/node/commit/893c864542)] - **(SEMVER-MINOR)** **test\_runner**: fix support watch with run(), add globPatterns option (Matteo Collina) [#53866](https://github.com/nodejs/node/pull/53866) +* \[[`048d421ad1`](https://github.com/nodejs/node/commit/048d421ad1)] - **meta**: add jake to collaborators (jakecastelli) [#54004](https://github.com/nodejs/node/pull/54004) +* \[[`6ad6e01bf3`](https://github.com/nodejs/node/commit/6ad6e01bf3)] - **(SEMVER-MINOR)** **test\_runner**: refactor snapshots to get file from context (Colin Ihrig) [#53853](https://github.com/nodejs/node/pull/53853) +* \[[`698e44f8e7`](https://github.com/nodejs/node/commit/698e44f8e7)] - **(SEMVER-MINOR)** **test\_runner**: add context.filePath (Colin Ihrig) [#53853](https://github.com/nodejs/node/pull/53853) + +### Commits + +* \[[`063f46dc2a`](https://github.com/nodejs/node/commit/063f46dc2a)] - **assert**: use isError instead of instanceof in innerOk (Pietro Marchini) [#53980](https://github.com/nodejs/node/pull/53980) +* \[[`10bea42f81`](https://github.com/nodejs/node/commit/10bea42f81)] - **build**: update gcovr to 7.2 and codecov config (Benjamin E. Coe) [#54019](https://github.com/nodejs/node/pull/54019) +* \[[`7c417c6cf4`](https://github.com/nodejs/node/commit/7c417c6cf4)] - **build**: avoid compiling with VS v17.10 (Hüseyin Açacak) [#53863](https://github.com/nodejs/node/pull/53863) +* \[[`ee97c045b4`](https://github.com/nodejs/node/commit/ee97c045b4)] - **build**: ensure v8\_pointer\_compression\_sandbox is enabled on 64bit (Shelley Vohr) [#53884](https://github.com/nodejs/node/pull/53884) +* \[[`bfbed0afd5`](https://github.com/nodejs/node/commit/bfbed0afd5)] - **build**: fix conflict gyp configs (Chengzhong Wu) [#53605](https://github.com/nodejs/node/pull/53605) +* \[[`0f1fe63e32`](https://github.com/nodejs/node/commit/0f1fe63e32)] - **build**: trigger coverage ci when updating codecov (Yagiz Nizipli) [#53929](https://github.com/nodejs/node/pull/53929) +* \[[`ad62b945f0`](https://github.com/nodejs/node/commit/ad62b945f0)] - **build**: update codecov coverage build count (Yagiz Nizipli) [#53929](https://github.com/nodejs/node/pull/53929) +* \[[`3c40868fd3`](https://github.com/nodejs/node/commit/3c40868fd3)] - **build**: disable test-asan workflow (Michaël Zasso) [#53844](https://github.com/nodejs/node/pull/53844) +* \[[`2a62d6ca57`](https://github.com/nodejs/node/commit/2a62d6ca57)] - **build, tools**: drop leading `/` from `r2dir` (Richard Lau) [#53951](https://github.com/nodejs/node/pull/53951) +* \[[`9c7b009f47`](https://github.com/nodejs/node/commit/9c7b009f47)] - **build,tools**: simplify upload of shasum signatures (Michaël Zasso) [#53892](https://github.com/nodejs/node/pull/53892) +* \[[`057bd44f9f`](https://github.com/nodejs/node/commit/057bd44f9f)] - **child\_process**: fix incomplete prototype pollution hardening (Liran Tal) [#53781](https://github.com/nodejs/node/pull/53781) +* \[[`66f7c595c7`](https://github.com/nodejs/node/commit/66f7c595c7)] - **cli**: document `--inspect` port `0` behavior (Aviv Keller) [#53782](https://github.com/nodejs/node/pull/53782) +* \[[`fad3e74b47`](https://github.com/nodejs/node/commit/fad3e74b47)] - **console**: fix issues with frozen intrinsics (Vinicius Lourenço) [#54070](https://github.com/nodejs/node/pull/54070) +* \[[`e685ecd7ae`](https://github.com/nodejs/node/commit/e685ecd7ae)] - **deps**: update corepack to 0.29.3 (Node.js GitHub Bot) [#54072](https://github.com/nodejs/node/pull/54072) +* \[[`e5f7250e6d`](https://github.com/nodejs/node/commit/e5f7250e6d)] - **deps**: update amaro to 0.0.6 (Node.js GitHub Bot) [#54199](https://github.com/nodejs/node/pull/54199) +* \[[`2c1e9082e8`](https://github.com/nodejs/node/commit/2c1e9082e8)] - **deps**: update amaro to 0.0.5 (Node.js GitHub Bot) [#54199](https://github.com/nodejs/node/pull/54199) +* \[[`2d982d3dee`](https://github.com/nodejs/node/commit/2d982d3dee)] - **(SEMVER-MINOR)** **deps**: V8: backport 7857eb34db42 (Stephen Belanger) [#53997](https://github.com/nodejs/node/pull/53997) +* \[[`1061898462`](https://github.com/nodejs/node/commit/1061898462)] - **deps**: update c-ares to v1.32.3 (Node.js GitHub Bot) [#54020](https://github.com/nodejs/node/pull/54020) +* \[[`f4a7ac5e18`](https://github.com/nodejs/node/commit/f4a7ac5e18)] - **deps**: V8: cherry-pick 35888fee7bba (Joyee Cheung) [#53728](https://github.com/nodejs/node/pull/53728) +* \[[`1176310226`](https://github.com/nodejs/node/commit/1176310226)] - **deps**: add gn build files for ncrypto (Cheng) [#53940](https://github.com/nodejs/node/pull/53940) +* \[[`7a1d5a4f84`](https://github.com/nodejs/node/commit/7a1d5a4f84)] - **deps**: update c-ares to v1.32.2 (Node.js GitHub Bot) [#53865](https://github.com/nodejs/node/pull/53865) +* \[[`66f6a2aec9`](https://github.com/nodejs/node/commit/66f6a2aec9)] - **deps**: V8: cherry-pick 9812cb486e2b (Michaël Zasso) [#53966](https://github.com/nodejs/node/pull/53966) +* \[[`8e66a18ef0`](https://github.com/nodejs/node/commit/8e66a18ef0)] - **deps**: start working on ncrypto dep (James M Snell) [#53803](https://github.com/nodejs/node/pull/53803) +* \[[`c114082b12`](https://github.com/nodejs/node/commit/c114082b12)] - **deps**: fix include\_dirs of nbytes (Cheng) [#53862](https://github.com/nodejs/node/pull/53862) +* \[[`b7315281be`](https://github.com/nodejs/node/commit/b7315281be)] - **doc**: move numCPUs require to top of file in cluster CJS example (Alfredo González) [#53932](https://github.com/nodejs/node/pull/53932) +* \[[`8e7c30c2a4`](https://github.com/nodejs/node/commit/8e7c30c2a4)] - **doc**: update security-release process to automated one (Rafael Gonzaga) [#53877](https://github.com/nodejs/node/pull/53877) +* \[[`52a4206be2`](https://github.com/nodejs/node/commit/52a4206be2)] - **doc**: fix typo in technical-priorities.md (YoonSoo\_Shin) [#54094](https://github.com/nodejs/node/pull/54094) +* \[[`30e18a04a3`](https://github.com/nodejs/node/commit/30e18a04a3)] - **doc**: fix typo in diagnostic tooling support tiers document (Taejin Kim) [#54058](https://github.com/nodejs/node/pull/54058) +* \[[`58aebfd31e`](https://github.com/nodejs/node/commit/58aebfd31e)] - **doc**: move GeoffreyBooth to TSC regular member (Geoffrey Booth) [#54047](https://github.com/nodejs/node/pull/54047) +* \[[`c1634c7213`](https://github.com/nodejs/node/commit/c1634c7213)] - **doc**: correct typescript stdin support (Marco Ippolito) [#54036](https://github.com/nodejs/node/pull/54036) +* \[[`64812d5c22`](https://github.com/nodejs/node/commit/64812d5c22)] - **doc**: fix typo in recognizing-contributors (Marco Ippolito) [#53990](https://github.com/nodejs/node/pull/53990) +* \[[`6b35994b6f`](https://github.com/nodejs/node/commit/6b35994b6f)] - **doc**: fix documentation for `--run` (Aviv Keller) [#53976](https://github.com/nodejs/node/pull/53976) +* \[[`04d203a233`](https://github.com/nodejs/node/commit/04d203a233)] - **doc**: update boxstarter README (Aviv Keller) [#53785](https://github.com/nodejs/node/pull/53785) +* \[[`86fa46db1c`](https://github.com/nodejs/node/commit/86fa46db1c)] - **doc**: add info about prefix-only modules to `module.builtinModules` (Grigory) [#53954](https://github.com/nodejs/node/pull/53954) +* \[[`defdc3c568`](https://github.com/nodejs/node/commit/defdc3c568)] - **doc**: remove `scroll-behavior: smooth;` (Cloyd Lau) [#53942](https://github.com/nodejs/node/pull/53942) +* \[[`e907236dd9`](https://github.com/nodejs/node/commit/e907236dd9)] - **doc**: move --test-coverage-{ex,in}clude to proper location (Colin Ihrig) [#53926](https://github.com/nodejs/node/pull/53926) +* \[[`8bf9960b98`](https://github.com/nodejs/node/commit/8bf9960b98)] - **doc**: add `--experimental-sqlite` note (Aviv Keller) [#53907](https://github.com/nodejs/node/pull/53907) +* \[[`d7615004d8`](https://github.com/nodejs/node/commit/d7615004d8)] - **doc**: update `api_assets` README for new files (Aviv Keller) [#53676](https://github.com/nodejs/node/pull/53676) +* \[[`63cf715aa0`](https://github.com/nodejs/node/commit/63cf715aa0)] - **doc**: add MattiasBuelens to collaborators (Mattias Buelens) [#53895](https://github.com/nodejs/node/pull/53895) +* \[[`5b8dd78112`](https://github.com/nodejs/node/commit/5b8dd78112)] - **doc**: fix release date for 22.5.0 (Antoine du Hamel) [#53889](https://github.com/nodejs/node/pull/53889) +* \[[`dd2c0f349a`](https://github.com/nodejs/node/commit/dd2c0f349a)] - **doc**: fix casing of GitHub handle for two collaborators (Antoine du Hamel) [#53857](https://github.com/nodejs/node/pull/53857) +* \[[`b47c2308e1`](https://github.com/nodejs/node/commit/b47c2308e1)] - **doc**: update release-post nodejs.org script (Rafael Gonzaga) [#53762](https://github.com/nodejs/node/pull/53762) +* \[[`88539527d5`](https://github.com/nodejs/node/commit/88539527d5)] - **doc, test**: tracing channel hasSubscribers getter (Thomas Hunter II) [#52908](https://github.com/nodejs/node/pull/52908) +* \[[`44a08f75b0`](https://github.com/nodejs/node/commit/44a08f75b0)] - **doc,tools**: enforce use of `node:` prefix (Antoine du Hamel) [#53950](https://github.com/nodejs/node/pull/53950) +* \[[`87bab76df2`](https://github.com/nodejs/node/commit/87bab76df2)] - **doc,tty**: add documentation for ReadStream and WriteStream (jakecastelli) [#53567](https://github.com/nodejs/node/pull/53567) +* \[[`dcca9ba560`](https://github.com/nodejs/node/commit/dcca9ba560)] - **esm**: refactor `get_format` (Antoine du Hamel) [#53872](https://github.com/nodejs/node/pull/53872) +* \[[`5e03c17aae`](https://github.com/nodejs/node/commit/5e03c17aae)] - **fs**: optimize `fs.cpSync` js calls (Yagiz Nizipli) [#53614](https://github.com/nodejs/node/pull/53614) +* \[[`e0054ee0a7`](https://github.com/nodejs/node/commit/e0054ee0a7)] - **fs**: ensure consistency for mkdtemp in both fs and fs/promises (YieldRay) [#53776](https://github.com/nodejs/node/pull/53776) +* \[[`8086337ea9`](https://github.com/nodejs/node/commit/8086337ea9)] - **fs**: remove unnecessary option argument validation (Jonas) [#53861](https://github.com/nodejs/node/pull/53861) +* \[[`b377b93a3f`](https://github.com/nodejs/node/commit/b377b93a3f)] - **fs**: correctly pass dirent to exclude `withFileTypes` (RedYetiDev) [#53823](https://github.com/nodejs/node/pull/53823) +* \[[`68e444d2d8`](https://github.com/nodejs/node/commit/68e444d2d8)] - **(SEMVER-MINOR)** **http**: add diagnostics channel `http.client.request.error` (Kohei Ueno) [#54054](https://github.com/nodejs/node/pull/54054) +* \[[`de1fbc292f`](https://github.com/nodejs/node/commit/de1fbc292f)] - **(SEMVER-MINOR)** **inspector**: add initial support for network inspection (Kohei Ueno) [#53593](https://github.com/nodejs/node/pull/53593) +* \[[`744df0be24`](https://github.com/nodejs/node/commit/744df0be24)] - **lib**: support dynamic trace events on debugWithTimer (Vinicius Lourenço) [#53913](https://github.com/nodejs/node/pull/53913) +* \[[`546dab29c1`](https://github.com/nodejs/node/commit/546dab29c1)] - **lib**: optimize copyError with ObjectAssign in primordials (HEESEUNG) [#53999](https://github.com/nodejs/node/pull/53999) +* \[[`494df9835a`](https://github.com/nodejs/node/commit/494df9835a)] - **lib**: improve cluster/primary code (Ehsan Khakifirooz) [#53756](https://github.com/nodejs/node/pull/53756) +* \[[`03f353293b`](https://github.com/nodejs/node/commit/03f353293b)] - **lib**: improve error message when index not found on cjs (Vinicius Lourenço) [#53859](https://github.com/nodejs/node/pull/53859) +* \[[`d8375d6236`](https://github.com/nodejs/node/commit/d8375d6236)] - **lib**: decorate async stack trace in source maps (Chengzhong Wu) [#53860](https://github.com/nodejs/node/pull/53860) +* \[[`15a94e67b1`](https://github.com/nodejs/node/commit/15a94e67b1)] - **lib,src**: drop --experimental-network-imports (Rafael Gonzaga) [#53822](https://github.com/nodejs/node/pull/53822) +* \[[`a6eedc401d`](https://github.com/nodejs/node/commit/a6eedc401d)] - **meta**: add `sqlite` to js subsystems (Alex Yang) [#53911](https://github.com/nodejs/node/pull/53911) +* \[[`21098856de`](https://github.com/nodejs/node/commit/21098856de)] - **meta**: move tsc member to emeritus (Michael Dawson) [#54029](https://github.com/nodejs/node/pull/54029) +* \[[`048d421ad1`](https://github.com/nodejs/node/commit/048d421ad1)] - **meta**: add jake to collaborators (jakecastelli) [#54004](https://github.com/nodejs/node/pull/54004) +* \[[`20a8c96c41`](https://github.com/nodejs/node/commit/20a8c96c41)] - **meta**: remove license for hljs (Aviv Keller) [#53970](https://github.com/nodejs/node/pull/53970) +* \[[`2fd4ac4859`](https://github.com/nodejs/node/commit/2fd4ac4859)] - **meta**: make more bug-report information required (Aviv Keller) [#53718](https://github.com/nodejs/node/pull/53718) +* \[[`b312ec0b0c`](https://github.com/nodejs/node/commit/b312ec0b0c)] - **meta**: reword linter messages (Aviv Keller) [#53949](https://github.com/nodejs/node/pull/53949) +* \[[`d2526126a9`](https://github.com/nodejs/node/commit/d2526126a9)] - **meta**: store actions secrets in environment (Aviv Keller) [#53930](https://github.com/nodejs/node/pull/53930) +* \[[`1688f00dce`](https://github.com/nodejs/node/commit/1688f00dce)] - **meta**: move anonrig to tsc voting members (Yagiz Nizipli) [#53888](https://github.com/nodejs/node/pull/53888) +* \[[`c20e8418de`](https://github.com/nodejs/node/commit/c20e8418de)] - **module**: fix strip-types interaction with detect-module (Marco Ippolito) [#54164](https://github.com/nodejs/node/pull/54164) +* \[[`ab1f0b415f`](https://github.com/nodejs/node/commit/ab1f0b415f)] - **module**: fix extensionless typescript in cjs loader (Marco Ippolito) [#54062](https://github.com/nodejs/node/pull/54062) +* \[[`92439fc160`](https://github.com/nodejs/node/commit/92439fc160)] - **(SEMVER-MINOR)** **module**: add --experimental-strip-types (Marco Ippolito) [#53725](https://github.com/nodejs/node/pull/53725) +* \[[`f755d31bec`](https://github.com/nodejs/node/commit/f755d31bec)] - **node-api**: add property keys benchmark (Chengzhong Wu) [#54012](https://github.com/nodejs/node/pull/54012) +* \[[`7382eefae5`](https://github.com/nodejs/node/commit/7382eefae5)] - **node-api**: rename nogc to basic (Gabriel Schulhof) [#53830](https://github.com/nodejs/node/pull/53830) +* \[[`2c4470625b`](https://github.com/nodejs/node/commit/2c4470625b)] - **process**: unify experimental warning messages (Aviv Keller) [#53704](https://github.com/nodejs/node/pull/53704) +* \[[`98a7ad2e0d`](https://github.com/nodejs/node/commit/98a7ad2e0d)] - **src**: expose LookupAndCompile with parameters (Shelley Vohr) [#53886](https://github.com/nodejs/node/pull/53886) +* \[[`dd3c66be0a`](https://github.com/nodejs/node/commit/dd3c66be0a)] - **src**: simplify AESCipherTraits::AdditionalConfig (Tobias Nießen) [#53890](https://github.com/nodejs/node/pull/53890) +* \[[`ee82f224ff`](https://github.com/nodejs/node/commit/ee82f224ff)] - **src**: remove redundant RsaPointer (use RSAPointer) (James M Snell) [#54003](https://github.com/nodejs/node/pull/54003) +* \[[`2d77bd2929`](https://github.com/nodejs/node/commit/2d77bd2929)] - **src**: fix -Wshadow warning (Shelley Vohr) [#53885](https://github.com/nodejs/node/pull/53885) +* \[[`bd4a9ffe8c`](https://github.com/nodejs/node/commit/bd4a9ffe8c)] - **src**: start using ncrypto for CSPRNG calls (James M Snell) [#53984](https://github.com/nodejs/node/pull/53984) +* \[[`3fdcf7a47d`](https://github.com/nodejs/node/commit/3fdcf7a47d)] - **src**: return `undefined` if no rows are returned in SQLite (Deokjin Kim) [#53981](https://github.com/nodejs/node/pull/53981) +* \[[`ca6854443d`](https://github.com/nodejs/node/commit/ca6854443d)] - **src**: fix slice of slice of file-backed Blob (Josh Lee) [#53972](https://github.com/nodejs/node/pull/53972) +* \[[`c457f9ed5a`](https://github.com/nodejs/node/commit/c457f9ed5a)] - **src**: cache invariant code motion (Rafael Gonzaga) [#53879](https://github.com/nodejs/node/pull/53879) +* \[[`fd0da6c2cf`](https://github.com/nodejs/node/commit/fd0da6c2cf)] - **src**: avoid strcmp in ImportJWKAsymmetricKey (Tobias Nießen) [#53813](https://github.com/nodejs/node/pull/53813) +* \[[`fbf74bcf99`](https://github.com/nodejs/node/commit/fbf74bcf99)] - **src**: switch from ToLocalChecked to ToLocal in node\_webstorage (James M Snell) [#53959](https://github.com/nodejs/node/pull/53959) +* \[[`04bb6778e5`](https://github.com/nodejs/node/commit/04bb6778e5)] - **src**: move `ToNamespacedPath` call of webstorage (Yagiz Nizipli) [#53875](https://github.com/nodejs/node/pull/53875) +* \[[`9ffaf763e9`](https://github.com/nodejs/node/commit/9ffaf763e9)] - **src**: use Maybe\ in SecureContext (Tobias Nießen) [#53883](https://github.com/nodejs/node/pull/53883) +* \[[`a94c3ae06f`](https://github.com/nodejs/node/commit/a94c3ae06f)] - **src**: replace ToLocalChecked uses with ToLocal in node-file (James M Snell) [#53869](https://github.com/nodejs/node/pull/53869) +* \[[`55461be05f`](https://github.com/nodejs/node/commit/55461be05f)] - **src**: refactor webstorage implementation (Yagiz Nizipli) [#53876](https://github.com/nodejs/node/pull/53876) +* \[[`c53cf449a6`](https://github.com/nodejs/node/commit/c53cf449a6)] - **src**: fix env-file flag to ignore spaces before quotes (Mohit Malhotra) [#53786](https://github.com/nodejs/node/pull/53786) +* \[[`bac3a485f6`](https://github.com/nodejs/node/commit/bac3a485f6)] - **src**: fix potential segmentation fault in SQLite (Tobias Nießen) [#53850](https://github.com/nodejs/node/pull/53850) +* \[[`df5083e5f9`](https://github.com/nodejs/node/commit/df5083e5f9)] - **src,lib**: expose getCategoryEnabledBuffer to use on node.http (Vinicius Lourenço) [#53602](https://github.com/nodejs/node/pull/53602) +* \[[`8664b9ad60`](https://github.com/nodejs/node/commit/8664b9ad60)] - **src,test**: disallow unsafe integer coercion in SQLite (Tobias Nießen) [#53851](https://github.com/nodejs/node/pull/53851) +* \[[`15816bd0dd`](https://github.com/nodejs/node/commit/15816bd0dd)] - **(SEMVER-MINOR)** **stream**: expose DuplexPair API (Austin Wright) [#34111](https://github.com/nodejs/node/pull/34111) +* \[[`718f6bc78c`](https://github.com/nodejs/node/commit/718f6bc78c)] - **test**: do not swallow uncaughtException errors in exit code tests (Meghan Denny) [#54039](https://github.com/nodejs/node/pull/54039) +* \[[`c6656c9251`](https://github.com/nodejs/node/commit/c6656c9251)] - **test**: move shared module to `test/common` (Rich Trott) [#54042](https://github.com/nodejs/node/pull/54042) +* \[[`e471e32d46`](https://github.com/nodejs/node/commit/e471e32d46)] - **test**: skip sea tests with more accurate available disk space estimation (Chengzhong Wu) [#53996](https://github.com/nodejs/node/pull/53996) +* \[[`61971ec929`](https://github.com/nodejs/node/commit/61971ec929)] - **test**: remove unnecessary console log (KAYYY) [#53812](https://github.com/nodejs/node/pull/53812) +* \[[`1344bd2d6f`](https://github.com/nodejs/node/commit/1344bd2d6f)] - **test**: add comments and rename test for timer robustness (Rich Trott) [#54008](https://github.com/nodejs/node/pull/54008) +* \[[`da3573409c`](https://github.com/nodejs/node/commit/da3573409c)] - **test**: add test for one arg timers to increase coverage (Carlos Espa) [#54007](https://github.com/nodejs/node/pull/54007) +* \[[`fc67abd97e`](https://github.com/nodejs/node/commit/fc67abd97e)] - **test**: mark 'test/parallel/test-sqlite.js' as flaky (Colin Ihrig) [#54031](https://github.com/nodejs/node/pull/54031) +* \[[`aa0ac3b57c`](https://github.com/nodejs/node/commit/aa0ac3b57c)] - **test**: mark test-pipe-file-to-http as flaky (jakecastelli) [#53751](https://github.com/nodejs/node/pull/53751) +* \[[`52bc8ec360`](https://github.com/nodejs/node/commit/52bc8ec360)] - **test**: compare paths on Windows without considering case (Early Riser) [#53993](https://github.com/nodejs/node/pull/53993) +* \[[`7e8a609579`](https://github.com/nodejs/node/commit/7e8a609579)] - **test**: skip sea tests in large debug builds (Chengzhong Wu) [#53918](https://github.com/nodejs/node/pull/53918) +* \[[`30a94ca0c4`](https://github.com/nodejs/node/commit/30a94ca0c4)] - **test**: skip --title check on IBM i (Abdirahim Musse) [#53952](https://github.com/nodejs/node/pull/53952) +* \[[`5cea7ed706`](https://github.com/nodejs/node/commit/5cea7ed706)] - **test**: reduce flakiness of `test-assert-esm-cjs-message-verify` (Antoine du Hamel) [#53967](https://github.com/nodejs/node/pull/53967) +* \[[`58cb0dd8a6`](https://github.com/nodejs/node/commit/58cb0dd8a6)] - **test**: use `PYTHON` executable from env in `assertSnapshot` (Antoine du Hamel) [#53938](https://github.com/nodejs/node/pull/53938) +* \[[`c247582591`](https://github.com/nodejs/node/commit/c247582591)] - **test**: deflake test-blob-file-backed (Luigi Pinca) [#53920](https://github.com/nodejs/node/pull/53920) +* \[[`3999021653`](https://github.com/nodejs/node/commit/3999021653)] - **test\_runner**: switched to internal readline interface (Emil Tayeb) [#54000](https://github.com/nodejs/node/pull/54000) +* \[[`3fb97a90ee`](https://github.com/nodejs/node/commit/3fb97a90ee)] - **test\_runner**: remove redundant bootstrap boolean (Colin Ihrig) [#54013](https://github.com/nodejs/node/pull/54013) +* \[[`edd80e2bdc`](https://github.com/nodejs/node/commit/edd80e2bdc)] - **test\_runner**: do not throw on mocked clearTimeout() (Aksinya Bykova) [#54005](https://github.com/nodejs/node/pull/54005) +* \[[`893c864542`](https://github.com/nodejs/node/commit/893c864542)] - **(SEMVER-MINOR)** **test\_runner**: fix support watch with run(), add globPatterns option (Matteo Collina) [#53866](https://github.com/nodejs/node/pull/53866) +* \[[`4887213f2e`](https://github.com/nodejs/node/commit/4887213f2e)] - **test\_runner**: added colors to dot reporter (Giovanni) [#53450](https://github.com/nodejs/node/pull/53450) +* \[[`c4848c53e6`](https://github.com/nodejs/node/commit/c4848c53e6)] - **test\_runner**: cleanup global event listeners after run (Eddie Abbondanzio) [#53878](https://github.com/nodejs/node/pull/53878) +* \[[`876e7b3226`](https://github.com/nodejs/node/commit/876e7b3226)] - **test\_runner**: refactor coverage to pass in config options (Colin Ihrig) [#53931](https://github.com/nodejs/node/pull/53931) +* \[[`f45edb4b5e`](https://github.com/nodejs/node/commit/f45edb4b5e)] - **test\_runner**: refactor and simplify internals (Colin Ihrig) [#53921](https://github.com/nodejs/node/pull/53921) +* \[[`6ad6e01bf3`](https://github.com/nodejs/node/commit/6ad6e01bf3)] - **(SEMVER-MINOR)** **test\_runner**: refactor snapshots to get file from context (Colin Ihrig) [#53853](https://github.com/nodejs/node/pull/53853) +* \[[`698e44f8e7`](https://github.com/nodejs/node/commit/698e44f8e7)] - **(SEMVER-MINOR)** **test\_runner**: add context.filePath (Colin Ihrig) [#53853](https://github.com/nodejs/node/pull/53853) +* \[[`97da7ca11b`](https://github.com/nodejs/node/commit/97da7ca11b)] - **test\_runner**: consolidate option parsing (Colin Ihrig) [#53849](https://github.com/nodejs/node/pull/53849) +* \[[`43afcbf9dd`](https://github.com/nodejs/node/commit/43afcbf9dd)] - **tools**: fix `SLACK_TITLE` in invalid commit workflow (Antoine du Hamel) [#53912](https://github.com/nodejs/node/pull/53912) +* \[[`eed0963391`](https://github.com/nodejs/node/commit/eed0963391)] - **typings**: apply lint (1ilsang) [#54065](https://github.com/nodejs/node/pull/54065) +* \[[`e8ea49b256`](https://github.com/nodejs/node/commit/e8ea49b256)] - **typings**: fix typo on quic onSessionDatagram (1ilsang) [#54064](https://github.com/nodejs/node/pull/54064) + ## 2024-07-19, Version 22.5.1 (Current), @richardlau diff --git a/src/node_version.h b/src/node_version.h index 9ddda54d1cd91b..499171daaf993f 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -23,13 +23,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 22 -#define NODE_MINOR_VERSION 5 -#define NODE_PATCH_VERSION 2 +#define NODE_MINOR_VERSION 6 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)