From 303c80c50b945cc8c779f7e4f31b898bd41923bf Mon Sep 17 00:00:00 2001 From: Aviv Keller <38299977+RedYetiDev@users.noreply.github.com> Date: Tue, 7 May 2024 07:12:42 -0400 Subject: [PATCH 1/8] fs: allow 'withFileTypes' to be used with globs PR-URL: https://github.com/nodejs/node/pull/52837 Reviewed-By: Moshe Atlow Reviewed-By: Benjamin Gruenbaum Reviewed-By: Mohammed Keyvanzadeh --- doc/api/fs.md | 18 +++++++++++++ lib/internal/fs/glob.js | 46 +++++++++++++++++++++++----------- lib/internal/fs/utils.js | 1 + test/parallel/test-fs-glob.mjs | 43 +++++++++++++++++++++++++++++-- 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 9dc7f2c8cf940e..d5a03efca2afe6 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1073,6 +1073,10 @@ behavior is similar to `cp dir1/ dir2/`. > Stability: 1 - Experimental @@ -1082,6 +1086,8 @@ added: v22.0.0 * `cwd` {string} current working directory. **Default:** `process.cwd()` * `exclude` {Function} Function to filter out files/directories. Return `true` to exclude the item, `false` to include it. **Default:** `undefined`. + * `withFileTypes` {boolean} `true` if the glob should return paths as Dirents, + `false` otherwise. **Default:** `false`. * Returns: {AsyncIterator} An AsyncIterator that yields the paths of files that match the pattern. @@ -3109,6 +3115,10 @@ descriptor. See [`fs.utimes()`][]. > Stability: 1 - Experimental @@ -3119,6 +3129,8 @@ added: v22.0.0 * `cwd` {string} current working directory. **Default:** `process.cwd()` * `exclude` {Function} Function to filter out files/directories. Return `true` to exclude the item, `false` to include it. **Default:** `undefined`. + * `withFileTypes` {boolean} `true` if the glob should return paths as Dirents, + `false` otherwise. **Default:** `false`. * `callback` {Function} * `err` {Error} @@ -5603,6 +5615,10 @@ Synchronous version of [`fs.futimes()`][]. Returns `undefined`. > Stability: 1 - Experimental @@ -5612,6 +5628,8 @@ added: v22.0.0 * `cwd` {string} current working directory. **Default:** `process.cwd()` * `exclude` {Function} Function to filter out files/directories. Return `true` to exclude the item, `false` to include it. **Default:** `undefined`. + * `withFileTypes` {boolean} `true` if the glob should return paths as Dirents, + `false` otherwise. **Default:** `false`. * Returns: {string\[]} paths of files that match the pattern. ```mjs diff --git a/lib/internal/fs/glob.js b/lib/internal/fs/glob.js index cdf8c4dfbc296b..4dd46ed0b6cb67 100644 --- a/lib/internal/fs/glob.js +++ b/lib/internal/fs/glob.js @@ -16,7 +16,7 @@ const { const { lstatSync, readdirSync } = require('fs'); const { lstat, readdir } = require('fs/promises'); -const { join, resolve } = require('path'); +const { join, resolve, basename, isAbsolute } = require('path'); const { kEmptyObject, @@ -27,6 +27,7 @@ const { validateString, validateStringArray, } = require('internal/validators'); +const { DirentFromStats } = require('internal/fs/utils'); let minimatch; function lazyMinimatch() { @@ -37,6 +38,14 @@ function lazyMinimatch() { const isWindows = process.platform === 'win32'; const isOSX = process.platform === 'darwin'; +async function getDirent(path) { + return new DirentFromStats(basename(path), await lstat(path), path); +} + +function getDirentSync(path) { + return new DirentFromStats(basename(path), lstatSync(path), path); +} + class Cache { #cache = new SafeMap(); #statsCache = new SafeMap(); @@ -47,7 +56,7 @@ class Cache { if (cached) { return cached; } - const promise = PromisePrototypeThen(lstat(path), null, () => null); + const promise = PromisePrototypeThen(getDirent(path), null, () => null); this.#statsCache.set(path, promise); return promise; } @@ -58,7 +67,7 @@ class Cache { } let val; try { - val = lstatSync(path); + val = getDirentSync(path); } catch { val = null; } @@ -175,14 +184,16 @@ class Glob { #queue = []; #subpatterns = new SafeMap(); #patterns; + #withFileTypes; constructor(pattern, options = kEmptyObject) { validateObject(options, 'options'); - const { exclude, cwd } = options; + const { exclude, cwd, withFileTypes } = options; if (exclude != null) { validateFunction(exclude, 'options.exclude'); } this.#root = cwd ?? '.'; this.#exclude = exclude; + this.#withFileTypes = !!withFileTypes; let patterns; if (typeof pattern === 'object') { validateStringArray(pattern, 'patterns'); @@ -222,7 +233,14 @@ class Glob { .forEach((patterns, path) => ArrayPrototypePush(this.#queue, { __proto__: null, path, patterns })); this.#subpatterns.clear(); } - return ArrayFrom(this.#results); + return ArrayFrom( + this.#results, + this.#withFileTypes ? (path) => this.#cache.statSync( + isAbsolute(path) ? + path : + join(this.#root, path), + ) : undefined, + ); } #addSubpattern(path, pattern) { if (!this.#subpatterns.has(path)) { @@ -317,7 +335,7 @@ class Glob { const fromSymlink = pattern.symlinks.has(index); if (current === lazyMinimatch().GLOBSTAR) { - if (entry.name[0] === '.' || (this.#exclude && this.#exclude(entry.name))) { + if (entry.name[0] === '.' || (this.#exclude && this.#exclude(this.#withFileTypes ? entry : entry.name))) { continue; } if (!fromSymlink && entry.isDirectory()) { @@ -460,7 +478,7 @@ class Glob { const result = join(path, p); if (!this.#results.has(result)) { this.#results.add(result); - yield result; + yield this.#withFileTypes ? stat : result; } } if (pattern.indexes.size === 1 && pattern.indexes.has(last)) { @@ -472,7 +490,7 @@ class Glob { // if path is ".", add it only if pattern starts with "." or pattern is exactly "**" if (!this.#results.has(path)) { this.#results.add(path); - yield path; + yield this.#withFileTypes ? stat : path; } } @@ -522,7 +540,7 @@ class Glob { // If ** is last, add to results if (!this.#results.has(entryPath)) { this.#results.add(entryPath); - yield entryPath; + yield this.#withFileTypes ? entry : entryPath; } } @@ -533,7 +551,7 @@ class Glob { // If next pattern is the last one, add to results if (!this.#results.has(entryPath)) { this.#results.add(entryPath); - yield entryPath; + yield this.#withFileTypes ? entry : entryPath; } } else if (nextMatches && entry.isDirectory()) { // Pattern mached, meaning two patterns forward @@ -569,14 +587,14 @@ class Glob { this.#cache.add(path, pattern.child(new SafeSet().add(nextIndex))); if (!this.#results.has(path)) { this.#results.add(path); - yield path; + yield this.#withFileTypes ? this.#cache.statSync(fullpath) : path; } } if (!this.#cache.seen(path, pattern, nextIndex) || !this.#cache.seen(parent, pattern, nextIndex)) { this.#cache.add(parent, pattern.child(new SafeSet().add(nextIndex))); if (!this.#results.has(parent)) { this.#results.add(parent); - yield parent; + yield this.#withFileTypes ? this.#cache.statSync(join(this.#root, parent)) : parent; } } } @@ -592,7 +610,7 @@ class Glob { if (nextIndex === last) { if (!this.#results.has(entryPath)) { this.#results.add(entryPath); - yield entryPath; + yield this.#withFileTypes ? entry : entryPath; } } else { subPatterns.add(nextIndex + 1); @@ -605,7 +623,7 @@ class Glob { if (index === last) { if (!this.#results.has(entryPath)) { this.#results.add(entryPath); - yield entryPath; + yield this.#withFileTypes ? entry : entryPath; } } else if (entry.isDirectory()) { subPatterns.add(nextIndex); diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js index f1d2d28dbfd681..995ce2d1970362 100644 --- a/lib/internal/fs/utils.js +++ b/lib/internal/fs/utils.js @@ -962,6 +962,7 @@ module.exports = { BigIntStats, // for testing copyObject, Dirent, + DirentFromStats, emitRecursiveRmdirWarning, getDirent, getDirents, diff --git a/test/parallel/test-fs-glob.mjs b/test/parallel/test-fs-glob.mjs index c01edf0431849d..7dcb8ecc8373a3 100644 --- a/test/parallel/test-fs-glob.mjs +++ b/test/parallel/test-fs-glob.mjs @@ -1,12 +1,16 @@ import * as common from '../common/index.mjs'; import tmpdir from '../common/tmpdir.js'; -import { resolve, dirname, sep } from 'node:path'; +import { resolve, dirname, sep, basename } from 'node:path'; import { mkdir, writeFile, symlink, glob as asyncGlob } from 'node:fs/promises'; -import { glob, globSync } from 'node:fs'; +import { glob, globSync, Dirent } from 'node:fs'; import { test, describe } from 'node:test'; import { promisify } from 'node:util'; import assert from 'node:assert'; +function assertDirents(dirents) { + assert.ok(dirents.every((dirent) => dirent instanceof Dirent)); +} + tmpdir.refresh(); const fixtureDir = tmpdir.resolve('fixtures'); @@ -333,3 +337,38 @@ describe('fsPromises glob', function() { }); } }); + +describe('glob - withFileTypes', function() { + const promisified = promisify(glob); + for (const [pattern, expected] of Object.entries(patterns)) { + test(pattern, async () => { + const actual = await promisified(pattern, { cwd: fixtureDir, withFileTypes: true }); + assertDirents(actual); + const normalized = expected.filter(Boolean).map((item) => basename(item)).sort(); + assert.deepStrictEqual(actual.map((dirent) => dirent.name).sort(), normalized.sort()); + }); + } +}); + +describe('globSync - withFileTypes', function() { + for (const [pattern, expected] of Object.entries(patterns)) { + test(pattern, () => { + const actual = globSync(pattern, { cwd: fixtureDir, withFileTypes: true }); + assertDirents(actual); + const normalized = expected.filter(Boolean).map((item) => basename(item)).sort(); + assert.deepStrictEqual(actual.map((dirent) => dirent.name).sort(), normalized.sort()); + }); + } +}); + +describe('fsPromises glob - withFileTypes', function() { + for (const [pattern, expected] of Object.entries(patterns)) { + test(pattern, async () => { + const actual = []; + for await (const item of asyncGlob(pattern, { cwd: fixtureDir, withFileTypes: true })) actual.push(item); + assertDirents(actual); + const normalized = expected.filter(Boolean).map((item) => basename(item)).sort(); + assert.deepStrictEqual(actual.map((dirent) => dirent.name).sort(), normalized.sort()); + }); + } +}); From 69f2acea40b2a702911b8ff02444bc108de3214c Mon Sep 17 00:00:00 2001 From: Mihir Bhansali Date: Tue, 23 Apr 2024 12:31:23 -0400 Subject: [PATCH 2/8] test_runner: display failed test stack trace with dot reporter PR-URL: https://github.com/nodejs/node/pull/52655 Reviewed-By: Matteo Collina Reviewed-By: Moshe Atlow Reviewed-By: Chemi Atlow --- lib/internal/test_runner/reporter/dot.js | 14 +- lib/internal/test_runner/reporter/spec.js | 89 ++----- lib/internal/test_runner/reporter/utils.js | 93 ++++++++ .../test-runner/output/dot_reporter.snapshot | 221 ++++++++++++++++++ .../test-runner/output/eval_dot.snapshot | 12 + test/parallel/test-runner-output.mjs | 4 +- test/parallel/test-runner-reporters.js | 27 ++- 7 files changed, 376 insertions(+), 84 deletions(-) create mode 100644 lib/internal/test_runner/reporter/utils.js diff --git a/lib/internal/test_runner/reporter/dot.js b/lib/internal/test_runner/reporter/dot.js index 3b8217caf25c9d..e9ba0b8dc7bd11 100644 --- a/lib/internal/test_runner/reporter/dot.js +++ b/lib/internal/test_runner/reporter/dot.js @@ -1,18 +1,22 @@ 'use strict'; - const { + ArrayPrototypePush, MathMax, } = primordials; +const colors = require('internal/util/colors'); +const { formatTestReport } = require('internal/test_runner/reporter/utils'); module.exports = async function* dot(source) { let count = 0; let columns = getLineLength(); - for await (const { type } of source) { + const failedTests = []; + for await (const { type, data } of source) { if (type === 'test:pass') { yield '.'; } if (type === 'test:fail') { yield 'X'; + ArrayPrototypePush(failedTests, data); } if ((type === 'test:fail' || type === 'test:pass') && ++count === columns) { yield '\n'; @@ -23,6 +27,12 @@ module.exports = async function* dot(source) { } } yield '\n'; + if (failedTests.length > 0) { + yield `\n${colors.red}Failed tests:${colors.white}\n\n`; + for (const test of failedTests) { + yield formatTestReport('test:fail', test); + } + } }; function getLineLength() { diff --git a/lib/internal/test_runner/reporter/spec.js b/lib/internal/test_runner/reporter/spec.js index e0335c5bdd3163..2092d22e3fe77f 100644 --- a/lib/internal/test_runner/reporter/spec.js +++ b/lib/internal/test_runner/reporter/spec.js @@ -1,97 +1,35 @@ 'use strict'; - const { ArrayPrototypeJoin, ArrayPrototypePop, ArrayPrototypePush, ArrayPrototypeShift, ArrayPrototypeUnshift, - RegExpPrototypeSymbolSplit, - SafeMap, - StringPrototypeRepeat, - hardenRegExp, } = primordials; const assert = require('assert'); const Transform = require('internal/streams/transform'); -const { inspectWithNoCustomRetry } = require('internal/errors'); const colors = require('internal/util/colors'); const { kSubtestsFailed } = require('internal/test_runner/test'); const { getCoverageReport } = require('internal/test_runner/utils'); const { relative } = require('path'); +const { + formatTestReport, + indent, + reporterColorMap, + reporterUnicodeSymbolMap, +} = require('internal/test_runner/reporter/utils'); -const symbols = { - '__proto__': null, - 'test:fail': '\u2716 ', - 'test:pass': '\u2714 ', - 'test:diagnostic': '\u2139 ', - 'test:coverage': '\u2139 ', - 'arrow:right': '\u25B6 ', - 'hyphen:minus': '\uFE63 ', -}; class SpecReporter extends Transform { #stack = []; #reported = []; - #indentMemo = new SafeMap(); #failedTests = []; #cwd = process.cwd(); - #inspectOptions; - #colors; constructor() { super({ __proto__: null, writableObjectMode: true }); colors.refresh(); - this.#inspectOptions = { __proto__: null, colors: colors.shouldColorize(process.stdout), breakLength: Infinity }; - this.#colors = { - '__proto__': null, - 'test:fail': colors.red, - 'test:pass': colors.green, - 'test:diagnostic': colors.blue, - }; } - #indent(nesting) { - let value = this.#indentMemo.get(nesting); - if (value === undefined) { - value = StringPrototypeRepeat(' ', nesting); - this.#indentMemo.set(nesting, value); - } - - return value; - } - #formatError(error, indent) { - if (!error) return ''; - const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error; - const message = ArrayPrototypeJoin( - RegExpPrototypeSymbolSplit( - hardenRegExp(/\r?\n/), - inspectWithNoCustomRetry(err, this.#inspectOptions), - ), `\n${indent} `); - return `\n${indent} ${message}\n`; - } - #formatTestReport(type, data, prefix = '', indent = '', hasChildren = false) { - let color = this.#colors[type] ?? colors.white; - let symbol = symbols[type] ?? ' '; - const { skip, todo } = data; - const duration_ms = data.details?.duration_ms ? ` ${colors.gray}(${data.details.duration_ms}ms)${colors.white}` : ''; - let title = `${data.name}${duration_ms}`; - - if (skip !== undefined) { - title += ` # ${typeof skip === 'string' && skip.length ? skip : 'SKIP'}`; - } else if (todo !== undefined) { - title += ` # ${typeof todo === 'string' && todo.length ? todo : 'TODO'}`; - } - const error = this.#formatError(data.details?.error, indent); - if (hasChildren) { - // If this test has had children - it was already reported, so slightly modify the output - const err = !error || data.details?.error?.failureType === 'subtestsFailed' ? '' : `\n${error}`; - return `${prefix}${indent}${color}${symbols['arrow:right']}${colors.white}${title}${err}`; - } - if (skip !== undefined) { - color = colors.gray; - symbol = symbols['hyphen:minus']; - } - return `${prefix}${indent}${color}${symbol}${title}${colors.white}${error}`; - } #handleTestReportEvent(type, data) { const subtest = ArrayPrototypeShift(this.#stack); // This is the matching `test:start` event if (subtest) { @@ -106,15 +44,15 @@ class SpecReporter extends Transform { assert(parent.type === 'test:start'); const msg = parent.data; ArrayPrototypeUnshift(this.#reported, msg); - prefix += `${this.#indent(msg.nesting)}${symbols['arrow:right']}${msg.name}\n`; + prefix += `${indent(msg.nesting)}${reporterUnicodeSymbolMap['arrow:right']}${msg.name}\n`; } let hasChildren = false; if (this.#reported[0] && this.#reported[0].nesting === data.nesting && this.#reported[0].name === data.name) { ArrayPrototypeShift(this.#reported); hasChildren = true; } - const indent = this.#indent(data.nesting); - return `${this.#formatTestReport(type, data, prefix, indent, hasChildren)}\n`; + const indentation = indent(data.nesting); + return `${formatTestReport(type, data, prefix, indentation, hasChildren)}\n`; } #handleEvent({ type, data }) { switch (type) { @@ -132,9 +70,10 @@ class SpecReporter extends Transform { case 'test:stdout': return data.message; case 'test:diagnostic': - return `${this.#colors[type]}${this.#indent(data.nesting)}${symbols[type]}${data.message}${colors.white}\n`; + return `${reporterColorMap[type]}${indent(data.nesting)}${reporterUnicodeSymbolMap[type]}${data.message}${colors.white}\n`; case 'test:coverage': - return getCoverageReport(this.#indent(data.nesting), data.summary, symbols['test:coverage'], colors.blue, true); + return getCoverageReport(indent(data.nesting), data.summary, + reporterUnicodeSymbolMap['test:coverage'], colors.blue, true); } } _transform({ type, data }, encoding, callback) { @@ -145,10 +84,10 @@ class SpecReporter extends Transform { callback(null, ''); return; } - const results = [`\n${this.#colors['test:fail']}${symbols['test:fail']}failing tests:${colors.white}\n`]; + const results = [`\n${reporterColorMap['test:fail']}${reporterUnicodeSymbolMap['test:fail']}failing tests:${colors.white}\n`]; for (let i = 0; i < this.#failedTests.length; i++) { const test = this.#failedTests[i]; - const formattedErr = this.#formatTestReport('test:fail', test); + const formattedErr = formatTestReport('test:fail', test); if (test.file) { const relPath = relative(this.#cwd, test.file); diff --git a/lib/internal/test_runner/reporter/utils.js b/lib/internal/test_runner/reporter/utils.js new file mode 100644 index 00000000000000..42c4ffa3cdaaec --- /dev/null +++ b/lib/internal/test_runner/reporter/utils.js @@ -0,0 +1,93 @@ +'use strict'; +const { + ArrayPrototypeJoin, + RegExpPrototypeSymbolSplit, + SafeMap, + StringPrototypeRepeat, + hardenRegExp, +} = primordials; +const colors = require('internal/util/colors'); +const { inspectWithNoCustomRetry } = require('internal/errors'); +const indentMemo = new SafeMap(); + +const inspectOptions = { + __proto__: null, + colors: colors.shouldColorize(process.stdout), + breakLength: Infinity, +}; + +const reporterUnicodeSymbolMap = { + '__proto__': null, + 'test:fail': '\u2716 ', + 'test:pass': '\u2714 ', + 'test:diagnostic': '\u2139 ', + 'test:coverage': '\u2139 ', + 'arrow:right': '\u25B6 ', + 'hyphen:minus': '\uFE63 ', +}; + +const reporterColorMap = { + '__proto__': null, + get 'test:fail'() { + return colors.red; + }, + get 'test:pass'() { + return colors.green; + }, + get 'test:diagnostic'() { + return colors.blue; + }, +}; + +function indent(nesting) { + let value = indentMemo.get(nesting); + if (value === undefined) { + value = StringPrototypeRepeat(' ', nesting); + indentMemo.set(nesting, value); + } + return value; +} + +function formatError(error, indent) { + if (!error) return ''; + const err = error.code === 'ERR_TEST_FAILURE' ? error.cause : error; + const message = ArrayPrototypeJoin( + RegExpPrototypeSymbolSplit( + hardenRegExp(/\r?\n/), + inspectWithNoCustomRetry(err, inspectOptions), + ), `\n${indent} `); + return `\n${indent} ${message}\n`; +} + +function formatTestReport(type, data, prefix = '', indent = '', hasChildren = false) { + let color = reporterColorMap[type] ?? colors.white; + let symbol = reporterUnicodeSymbolMap[type] ?? ' '; + const { skip, todo } = data; + const duration_ms = data.details?.duration_ms ? ` ${colors.gray}(${data.details.duration_ms}ms)${colors.white}` : ''; + let title = `${data.name}${duration_ms}`; + + if (skip !== undefined) { + title += ` # ${typeof skip === 'string' && skip.length ? skip : 'SKIP'}`; + } else if (todo !== undefined) { + title += ` # ${typeof todo === 'string' && todo.length ? todo : 'TODO'}`; + } + const error = formatError(data.details?.error, indent); + if (hasChildren) { + // If this test has had children - it was already reported, so slightly modify the output + const err = !error || data.details?.error?.failureType === 'subtestsFailed' ? '' : `\n${error}`; + return `${prefix}${indent}${color}${reporterUnicodeSymbolMap['arrow:right']}${colors.white}${title}${err}`; + } + if (skip !== undefined) { + color = colors.gray; + symbol = reporterUnicodeSymbolMap['hyphen:minus']; + } + return `${prefix}${indent}${color}${symbol}${title}${colors.white}${error}`; +} + +module.exports = { + __proto__: null, + reporterUnicodeSymbolMap, + reporterColorMap, + formatTestReport, + indent, +}; diff --git a/test/fixtures/test-runner/output/dot_reporter.snapshot b/test/fixtures/test-runner/output/dot_reporter.snapshot index 7c6b0ff2356b77..5f2bf18e1d0137 100644 --- a/test/fixtures/test-runner/output/dot_reporter.snapshot +++ b/test/fixtures/test-runner/output/dot_reporter.snapshot @@ -2,3 +2,224 @@ XXX.....X..X...X.... .....X...XXX.XX..... .XXXXXXX...XXXXX + +Failed tests: + +✖ sync fail todo (*ms) # TODO + Error: thrown from sync fail todo + * + * + * + * + * + * + * +✖ sync fail todo with message (*ms) # this is a failing todo + Error: thrown from sync fail todo with message + * + * + * + * + * + * + * +✖ sync throw fail (*ms) + Error: thrown from sync throw fail + * + * + * + * + * + * + * +✖ async throw fail (*ms) + Error: thrown from async throw fail + * + * + * + * + * + * + * +﹣ async skip fail (*ms) # SKIP + Error: thrown from async throw fail + * + * + * + * + * + * + * +✖ async assertion fail (*ms) + AssertionError [ERR_ASSERTION]: Expected values to be strictly equal: + + true !== false + + * + * + * + * + * + * + * { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: true, + expected: false, + operator: 'strictEqual' + } +✖ reject fail (*ms) + Error: rejected from reject fail + * + * + * + * + * + * + * +✖ +sync throw fail (*ms) + Error: thrown from subtest sync throw fail + * + * + * + * + * + * + * + * + * + * +✖ subtest sync throw fail (*ms) + '1 subtest failed' +✖ sync throw non-error fail (*ms) + Symbol(thrown symbol from sync throw non-error fail) +✖ +long running (*ms) + 'test did not finish before its parent and was cancelled' +✖ top level (*ms) + '1 subtest failed' +✖ sync skip option is false fail (*ms) + Error: this should be executed + * + * + * + * + * + * + * +✖ callback fail (*ms) + Error: callback failure + * + * +✖ callback also returns a Promise (*ms) + 'passed a callback but also returned a Promise' +✖ callback throw (*ms) + Error: thrown from callback throw + * + * + * + * + * + * + * +✖ callback called twice (*ms) + 'callback invoked multiple times' +✖ callback called twice in future tick (*ms) + Error [ERR_TEST_FAILURE]: callback invoked multiple times + * { + code: 'ERR_TEST_FAILURE', + failureType: 'multipleCallbackInvocations', + cause: 'callback invoked multiple times' + } +✖ callback async throw (*ms) + Error: thrown from callback async throw + * + * +✖ custom inspect symbol fail (*ms) + customized +✖ custom inspect symbol that throws fail (*ms) + { foo: 1, [Symbol(nodejs.util.inspect.custom)]: [Function: [nodejs.util.inspect.custom]] } +✖ sync throw fails at first (*ms) + Error: thrown from subtest sync throw fails at first + * + * + * + * + * + * + * + * + * + * +✖ sync throw fails at second (*ms) + Error: thrown from subtest sync throw fails at second + * + * + * + * + * + * + * + * +✖ subtest sync throw fails (*ms) + '2 subtests failed' +✖ timed out async test (*ms) + 'test timed out after *ms' +✖ timed out callback test (*ms) + 'test timed out after *ms' +✖ rejected thenable (*ms) + 'custom error' +✖ unfinished test with uncaughtException (*ms) + Error: foo + * + * + * +✖ unfinished test with unhandledRejection (*ms) + Error: bar + * + * + * +✖ assertion errors display actual and expected properly (*ms) + AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal: + + { + bar: 1, + baz: { + date: 1970-01-01T00:00:00.000Z, + null: null, + number: 1, + string: 'Hello', + undefined: undefined + }, + boo: [ + 1 + ], + foo: 1 + } + + should loosely deep-equal + + { + baz: { + date: 1970-01-01T00:00:00.000Z, + null: null, + number: 1, + string: 'Hello', + undefined: undefined + }, + boo: [ + 1 + ], + circular: { + bar: 2, + c: [Circular *1] + } + } + * { + generatedMessage: true, + code: 'ERR_ASSERTION', + actual: [Object], + expected: [Object], + operator: 'deepEqual' + } +✖ invalid subtest fail (*ms) + 'test could not be started because its parent finished' diff --git a/test/fixtures/test-runner/output/eval_dot.snapshot b/test/fixtures/test-runner/output/eval_dot.snapshot index 901a287a41ebfa..8a22cfd477b15d 100644 --- a/test/fixtures/test-runner/output/eval_dot.snapshot +++ b/test/fixtures/test-runner/output/eval_dot.snapshot @@ -1 +1,13 @@ .X + +Failed tests: + +✖ fails (*ms) + Error: fail + * + * + * + * + * + * + * diff --git a/test/parallel/test-runner-output.mjs b/test/parallel/test-runner-output.mjs index 1c5b078b23b297..d65e14de1fb0cb 100644 --- a/test/parallel/test-runner-output.mjs +++ b/test/parallel/test-runner-output.mjs @@ -93,7 +93,7 @@ const tests = [ { name: 'test-runner/output/abort_hooks.js' }, { name: 'test-runner/output/describe_it.js' }, { name: 'test-runner/output/describe_nested.js' }, - { name: 'test-runner/output/eval_dot.js' }, + { name: 'test-runner/output/eval_dot.js', transform: specTransform }, { name: 'test-runner/output/eval_spec.js', transform: specTransform }, { name: 'test-runner/output/eval_tap.js' }, { name: 'test-runner/output/hooks.js' }, @@ -110,7 +110,7 @@ const tests = [ { name: 'test-runner/output/no_refs.js' }, { name: 'test-runner/output/no_tests.js' }, { name: 'test-runner/output/only_tests.js' }, - { name: 'test-runner/output/dot_reporter.js' }, + { name: 'test-runner/output/dot_reporter.js', transform: specTransform }, { name: 'test-runner/output/junit_reporter.js', transform: junitTransform }, { name: 'test-runner/output/spec_reporter_successful.js', transform: specTransform }, { name: 'test-runner/output/spec_reporter.js', transform: specTransform }, diff --git a/test/parallel/test-runner-reporters.js b/test/parallel/test-runner-reporters.js index 6d1f9ff6be34e7..a412bd099ec68e 100644 --- a/test/parallel/test-runner-reporters.js +++ b/test/parallel/test-runner-reporters.js @@ -25,7 +25,10 @@ describe('node:test reporters', { concurrency: true }, () => { it('should default destination to stdout when passing a single reporter', async () => { const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', testFile]); assert.strictEqual(child.stderr.toString(), ''); - assert.strictEqual(child.stdout.toString(), '.XX.\n'); + assert.match(child.stdout.toString(), /\.XX\.\n/); + assert.match(child.stdout.toString(), /Failed tests:/); + assert.match(child.stdout.toString(), /✖ failing/); + assert.match(child.stdout.toString(), /✖ nested/); }); it('should throw when passing reporters without a destination', async () => { @@ -44,13 +47,19 @@ describe('node:test reporters', { concurrency: true }, () => { const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stdout', testFile]); assert.strictEqual(child.stderr.toString(), ''); - assert.strictEqual(child.stdout.toString(), '.XX.\n'); + assert.match(child.stdout.toString(), /\.XX\.\n/); + assert.match(child.stdout.toString(), /Failed tests:/); + assert.match(child.stdout.toString(), /✖ failing/); + assert.match(child.stdout.toString(), /✖ nested/); }); it('should support stderr as a destination', async () => { const child = spawnSync(process.execPath, ['--test', '--test-reporter', 'dot', '--test-reporter-destination', 'stderr', testFile]); - assert.strictEqual(child.stderr.toString(), '.XX.\n'); + assert.match(child.stderr.toString(), /\.XX\.\n/); + assert.match(child.stderr.toString(), /Failed tests:/); + assert.match(child.stderr.toString(), /✖ failing/); + assert.match(child.stderr.toString(), /✖ nested/); assert.strictEqual(child.stdout.toString(), ''); }); @@ -60,7 +69,11 @@ describe('node:test reporters', { concurrency: true }, () => { ['--test', '--test-reporter', 'dot', '--test-reporter-destination', file, testFile]); assert.strictEqual(child.stderr.toString(), ''); assert.strictEqual(child.stdout.toString(), ''); - assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n'); + const fileContents = fs.readFileSync(file, 'utf8'); + assert.match(fileContents, /\.XX\.\n/); + assert.match(fileContents, /Failed tests:/); + assert.match(fileContents, /✖ failing/); + assert.match(fileContents, /✖ nested/); }); it('should disallow using v8-serializer as reporter', async () => { @@ -81,7 +94,11 @@ describe('node:test reporters', { concurrency: true }, () => { testFile]); assert.match(child.stdout.toString(), /TAP version 13/); assert.match(child.stdout.toString(), /# duration_ms/); - assert.strictEqual(fs.readFileSync(file, 'utf8'), '.XX.\n'); + const fileContents = fs.readFileSync(file, 'utf8'); + assert.match(fileContents, /\.XX\.\n/); + assert.match(fileContents, /Failed tests:/); + assert.match(fileContents, /✖ failing/); + assert.match(fileContents, /✖ nested/); const file2Contents = fs.readFileSync(file2, 'utf8'); assert.match(file2Contents, /▶ nested/); assert.match(file2Contents, /✔ ok/); From fe7adf56ca2fc7048b646f0c6cea35ca039463c3 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Tue, 7 May 2024 14:42:51 +0200 Subject: [PATCH 3/8] doc: add pimterry to collaborators Fixes: https://github.com/nodejs/node/issues/52665 PR-URL: https://github.com/nodejs/node/pull/52874 Reviewed-By: Moshe Atlow Reviewed-By: Antoine du Hamel Reviewed-By: Marco Ippolito --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a7f7d19d048c31..70e65a44339d01 100644 --- a/README.md +++ b/README.md @@ -411,6 +411,8 @@ For information about the governance of the Node.js project, see **Claudio Wunder** <> (he/they) * [panva](https://github.com/panva) - **Filip Skokan** <> (he/him) +* [pimterry](https://github.com/pimterry) - + **Tim Perry** <> (he/him) * [Qard](https://github.com/Qard) - **Stephen Belanger** <> (he/him) * [RafaelGSS](https://github.com/RafaelGSS) - From 4ba6dc3c3083f95c499c4d9c04fb2f1a88eb73c8 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 7 May 2024 16:54:20 +0300 Subject: [PATCH 4/8] tools: update `gr2m/create-or-update-pull-request-action` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/52843 Refs: https://github.com/gr2m/create-or-update-pull-request-action/releases/tag/v1.9.4 Reviewed-By: Richard Lau Reviewed-By: Michaël Zasso Reviewed-By: Marco Ippolito Reviewed-By: Moshe Atlow Reviewed-By: Ulises Gascón Reviewed-By: Rafael Gonzaga Reviewed-By: Yagiz Nizipli --- .github/workflows/license-builder.yml | 2 +- .github/workflows/tools.yml | 2 +- .github/workflows/update-v8.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/license-builder.yml b/.github/workflows/license-builder.yml index b68e8b2f0e6a46..1e1d8e83fda103 100644 --- a/.github/workflows/license-builder.yml +++ b/.github/workflows/license-builder.yml @@ -21,7 +21,7 @@ jobs: with: persist-credentials: false - run: ./tools/license-builder.sh # Run the license builder tool - - uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5 + - uses: gr2m/create-or-update-pull-request-action@86ec1766034c8173518f61d2075cc2a173fb8c97 # v1.9.4 # Creates a PR or update the Action's existing PR, or # no-op if the base branch is already up-to-date. env: diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 67facfb4933497..33cf0c15686da0 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -314,7 +314,7 @@ jobs: if: env.COMMIT_MSG == '' && (github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id) run: | echo "COMMIT_MSG=${{ matrix.subsystem }}: update ${{ matrix.id }} to ${{ env.NEW_VERSION }}" >> "$GITHUB_ENV" - - uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5 + - uses: gr2m/create-or-update-pull-request-action@86ec1766034c8173518f61d2075cc2a173fb8c97 # v1.9.4 if: github.event_name == 'schedule' || inputs.id == 'all' || inputs.id == matrix.id # Creates a PR or update the Action's existing PR, or # no-op if the base branch is already up-to-date. diff --git a/.github/workflows/update-v8.yml b/.github/workflows/update-v8.yml index 6ec8df17937a4c..3dc11797f853bf 100644 --- a/.github/workflows/update-v8.yml +++ b/.github/workflows/update-v8.yml @@ -41,7 +41,7 @@ jobs: cat temp-output tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true rm temp-output - - uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5 + - uses: gr2m/create-or-update-pull-request-action@86ec1766034c8173518f61d2075cc2a173fb8c97 # v1.9.4 # Creates a PR or update the Action's existing PR, or # no-op if the base branch is already up-to-date. env: From 9a1df15ee72d5f20c767b99126757333fb896fbe Mon Sep 17 00:00:00 2001 From: marco-ippolito Date: Thu, 2 May 2024 11:31:36 +0200 Subject: [PATCH 5/8] 2024-05-07, Version 20.13.0 'Iron' (LTS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Notable changes: benchmark: * add AbortSignal.abort benchmarks (Raz Luvaton) https://github.com/nodejs/node/pull/52408 buffer: * improve `base64` and `base64url` performance (Yagiz Nizipli) https://github.com/nodejs/node/pull/52428 crypto: * deprecate implicitly shortened GCM tags (Tobias Nießen) https://github.com/nodejs/node/pull/52345 deps: * (SEMVER-MINOR) update simdutf to 5.0.0 (Daniel Lemire) https://github.com/nodejs/node/pull/52138 * (SEMVER-MINOR) update undici to 6.3.0 (Node.js GitHub Bot) https://github.com/nodejs/node/pull/51462 * (SEMVER-MINOR) update undici to 6.2.1 (Node.js GitHub Bot) https://github.com/nodejs/node/pull/51278 dns: * (SEMVER-MINOR) add order option and support ipv6first (Paolo Insogna) https://github.com/nodejs/node/pull/52492 doc: * update release gpg keyserver (marco-ippolito) https://github.com/nodejs/node/pull/52257 * add release key for marco-ippolito (marco-ippolito) https://github.com/nodejs/node/pull/52257 * add UlisesGascon as a collaborator (Ulises Gascón) https://github.com/nodejs/node/pull/51991 * (SEMVER-MINOR) deprecate fs.Stats public constructor (Marco Ippolito) https://github.com/nodejs/node/pull/51879 events,doc: * mark CustomEvent as stable (Daeyeon Jeong) https://github.com/nodejs/node/pull/52618 fs: * add stacktrace to fs/promises (翠 / green) https://github.com/nodejs/node/pull/49849 lib, url: * (SEMVER-MINOR) add a `windows` option to path parsing (Aviv Keller) https://github.com/nodejs/node/pull/52509 net: * (SEMVER-MINOR) add CLI option for autoSelectFamilyAttemptTimeout (Paolo Insogna) https://github.com/nodejs/node/pull/52474 report: * (SEMVER-MINOR) add `--report-exclude-network` option (Ethan Arrowood) https://github.com/nodejs/node/pull/51645 src: * (SEMVER-MINOR) add `string_view` overload to snapshot FromBlob (Anna Henningsen) https://github.com/nodejs/node/pull/52595 * (SEMVER-MINOR) add C++ ProcessEmitWarningSync() (Joyee Cheung) https://github.com/nodejs/node/pull/51977 * (SEMVER-MINOR) add uv_get_available_memory to report and process (theanarkh) https://github.com/nodejs/node/pull/52023 * (SEMVER-MINOR) preload function for Environment (Cheng Zhao) https://github.com/nodejs/node/pull/51539 stream: * (SEMVER-MINOR) support typed arrays (IlyasShabi) https://github.com/nodejs/node/pull/51866 test_runner: * (SEMVER-MINOR) add suite() (Colin Ihrig) https://github.com/nodejs/node/pull/52127 * (SEMVER-MINOR) add `test:complete` event to reflect execution order (Moshe Atlow) https://github.com/nodejs/node/pull/51909 util: * (SEMVER-MINOR) support array of formats in util.styleText (Marco Ippolito) https://github.com/nodejs/node/pull/52040 v8: * (SEMVER-MINOR) implement v8.queryObjects() for memory leak regression testing (Joyee Cheung) https://github.com/nodejs/node/pull/51927 watch: * mark as stable (Moshe Atlow) https://github.com/nodejs/node/pull/52074 PR-URL: https://github.com/nodejs/node/pull/52793 --- CHANGELOG.md | 3 +- doc/api/cli.md | 20 +- doc/api/crypto.md | 4 +- doc/api/deprecations.md | 6 +- doc/api/dns.md | 20 +- doc/api/events.md | 12 +- doc/api/fs.md | 4 +- doc/api/process.md | 8 +- doc/api/report.md | 4 +- doc/api/stream.md | 16 +- doc/api/test.md | 16 +- doc/api/url.md | 8 +- doc/api/v8.md | 4 +- doc/changelogs/CHANGELOG_V20.md | 366 ++++++++++++++++++++++++++++++++ 14 files changed, 458 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a08f369f2579d2..6abc3d7cee39af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,7 +57,8 @@ release. 21.0.0
-20.12.2
+20.13.0
+20.12.2
20.12.1
20.12.0
20.11.1
diff --git a/doc/api/cli.md b/doc/api/cli.md index dd5650e6e9c272..363c42f030085b 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -594,7 +594,9 @@ added: - v16.4.0 - v14.18.0 changes: - - version: v22.1.0 + - version: + - v22.1.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52492 description: The `ipv6first` is supported now. - version: v17.0.0 @@ -1338,7 +1340,9 @@ This option is a no-op. It is kept for compatibility. ### `--network-family-autoselection-attempt-timeout` Sets the default value for the network family autoselection attempt timeout. @@ -1777,7 +1781,9 @@ native stack and other runtime environment data. ### `--report-exclude-network` Exclude `header.networkInterfaces` from the diagnostic report. By default @@ -2366,7 +2372,9 @@ added: - v18.11.0 - v16.19.0 changes: - - version: v22.0.0 + - version: + - v22.0.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52074 description: Watch mode is now stable. - version: @@ -2399,7 +2407,9 @@ added: - v18.11.0 - v16.19.0 changes: - - version: v22.0.0 + - version: + - v22.0.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52074 description: Watch mode is now stable. --> diff --git a/doc/api/crypto.md b/doc/api/crypto.md index cfe24000fecfa6..807cb0e81bca80 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -891,7 +891,9 @@ When passing a string as the `buffer`, please consider @@ -3662,7 +3662,7 @@ changes: - version: v22.0.0 pr-url: https://github.com/nodejs/node/pull/52071 description: Runtime deprecation. - - version: v22.0.0 + - version: v20.13.0 pr-url: https://github.com/nodejs/node/pull/51881 description: Documentation-only deprecation. --> @@ -3680,7 +3680,7 @@ changes: - version: REPLACEME pr-url: https://github.com/nodejs/node/pull/52552 description: Runtime deprecation. - - version: v22.0.0 + - version: v20.13.0 pr-url: https://github.com/nodejs/node/pull/52345 description: Documentation-only deprecation. --> diff --git a/doc/api/dns.md b/doc/api/dns.md index 4787c7f7b88105..d2a97581154f9c 100644 --- a/doc/api/dns.md +++ b/doc/api/dns.md @@ -179,7 +179,9 @@ section if a custom port is used. @@ -971,7 +977,9 @@ section if a custom port is used. @@ -1383,7 +1391,9 @@ added: - v16.4.0 - v14.18.0 changes: - - version: v22.1.0 + - version: + - v22.1.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52492 description: The `ipv6first` value is supported now. - version: v17.0.0 diff --git a/doc/api/events.md b/doc/api/events.md index 8983d9d5daba51..1deb04bcffd6fe 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -1662,7 +1662,9 @@ added: - v13.6.0 - v12.16.0 changes: - - version: v22.0.0 + - version: + - v22.0.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52080 description: Support `highWaterMark` and `lowWaterMark` options, For consistency. Old options are still supported. @@ -2427,7 +2429,9 @@ added: - v18.7.0 - v16.17.0 changes: - - version: v22.1.0 + - version: + - v22.1.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52618 description: CustomEvent is now stable. --> @@ -2446,7 +2450,9 @@ added: - v18.7.0 - v16.17.0 changes: - - version: v22.1.0 + - version: + - v22.1.0 + - v20.13.0 pr-url: https://github.com/nodejs/node/pull/52618 description: CustomEvent is now stable. --> diff --git a/doc/api/fs.md b/doc/api/fs.md index d5a03efca2afe6..3c7437bbd4f090 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -7026,7 +7026,9 @@ i.e. before the `'ready'` event is emitted. @@ -1134,7 +1136,9 @@ information. ## `process.availableMemory()` > Stability: 1 - Experimental diff --git a/doc/api/report.md b/doc/api/report.md index 2ff614cf522dd7..19a39400ff9bee 100644 --- a/doc/api/report.md +++ b/doc/api/report.md @@ -10,7 +10,9 @@ diff --git a/doc/api/stream.md b/doc/api/stream.md index 894d4f7593afd9..03a2d8d72d7807 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -727,7 +727,9 @@ console.log(myStream.destroyed); // true * `name` {string} The name of the suite, which is displayed when reporting test @@ -1292,7 +1294,9 @@ The `suite()` function is imported from the `node:test` module. ## `suite.skip([name][, options][, fn])` Shorthand for skipping a suite. This is the same as @@ -1301,7 +1305,9 @@ Shorthand for skipping a suite. This is the same as ## `suite.todo([name][, options][, fn])` Shorthand for marking a suite as `TODO`. This is the same as @@ -1310,7 +1316,9 @@ Shorthand for marking a suite as `TODO`. This is the same as ## `suite.only([name][, options][, fn])` Shorthand for marking a suite as `only`. This is the same as diff --git a/doc/api/url.md b/doc/api/url.md index 430347dd9ba0c7..53484e1a856395 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -1156,7 +1156,9 @@ console.log(url.domainToUnicode('xn--iñvalid.com')); > Stability: 1.1 - Active development diff --git a/doc/changelogs/CHANGELOG_V20.md b/doc/changelogs/CHANGELOG_V20.md index 79c0fcd863de72..5022bc45230e67 100644 --- a/doc/changelogs/CHANGELOG_V20.md +++ b/doc/changelogs/CHANGELOG_V20.md @@ -9,6 +9,7 @@ +20.13.0
20.12.2
20.12.1
20.12.0
@@ -59,6 +60,371 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) + + +## 2024-05-07, Version 20.13.0 'Iron' (LTS), @marco-ippolito + +### buffer: improve `base64` and `base64url` performance + +The performance of the `base64` and `base64url` encoding and decoding functions has been improved significantly. + +Contributed by Yagiz Nizipli in [#52428](https://github.com/nodejs/node/pull/52428) + +### crypto: deprecate implicitly shortened GCM tags + +This release, introduces a doc-only deprecation of using GCM authentication tags that are shorter than the cipher's block size, unless the user specified the `authTagLength` option. + +Contributed by Tobias Nießen in [#52345](https://github.com/nodejs/node/pull/52345) + +### events,doc: mark CustomEvent as stable + +From this release `CustomEvent` has been marked stable. + +Contributed by Daeyeon Jeong in [#52618](https://github.com/nodejs/node/pull/52618) + +### fs: add stacktrace to fs/promises + +Sync functions in fs throwed an error with a stacktrace which is helpful for debugging. But functions in fs/promises throwed an error without a stacktrace. This commit adds stacktraces by calling `Error.captureStacktrace` and re-throwing the error. + +Contributed by 翠 / green in [#49849](https://github.com/nodejs/node/pull/49849) + +### report: add `--report-exclude-network` option + +New option `--report-exclude-network`, also available as `report.excludeNetwork`, enables the user to exclude networking interfaces in their diagnostic report. On some systems, this can cause the report to take minutes to generate so this option can be used to optimize that. + +Contributed by Ethan Arrowood in [#51645](https://github.com/nodejs/node/pull/51645) + +### src: add uv\_get\_available\_memory to report and process + +From this release it is possible to get the available memory in the system by calling `process.getAvailableMemory()`. + +Contributed by theanarkh [#52023](https://github.com/nodejs/node/pull/52023) + +### stream: support typed arrays + +This commit adds support for typed arrays in streams. + +Contributed by IlyasShabi [#51866](https://github.com/nodejs/node/pull/51866) + +### util: support array of formats in util.styleText + +It is now possible to pass an array of format strings to `util.styleText` to apply multiple formats to the same text. + +```js +console.log(util.styleText(['underline', 'italic'], 'My italic underlined message')); +``` + +Contributed by Marco Ippolito in [#52040](https://github.com/nodejs/node/pull/52040) + +### v8: implement v8.queryObjects() for memory leak regression testing + +This is similar to the queryObjects() console API provided by the Chromium DevTools console. It can be used to search for objects that have the matching constructor on its prototype chain in the heap after a full garbage collection, which can be useful for memory leak regression tests. +To avoid surprising results, users should avoid using this API on constructors whose implementation they don't control, or on constructors that can be invoked by other parties in the application. + +To avoid accidental leaks, this API does not return raw references to the objects found. By default, it returns the count of the objects found. If options.format is 'summary', it returns an array containing brief string representations for each object. The visibility provided in this API is similar to what the heap snapshot provides, while users can save the cost of serialization and parsing and directly filer the target objects during the search. + +We have been using this API internally for the test suite, which has been more stable than any other leak regression testing strategies in the CI. With a public implementation we can now use the public API instead. + +```js +const { queryObjects } = require('node:v8'); +class A { foo = 'bar'; } +console.log(queryObjects(A)); // 0 +let a = new A(); +console.log(queryObjects(A)); // 1 +// [ "A { foo: 'bar' }" ] +console.log(queryObjects(A, { format: 'summary' })); + +// Release the object. +a = null; +// Search again. queryObjects() includes a full garbage collection +// so a should disappear. +console.log(queryObjects(A)); // 0 + +class B extends A { bar = 'qux'; } +// The child class B's prototype has A's prototype on its prototype chain +// so the prototype object shows up too. +console.log(queryObjects(A, { format: 'summary' })); // [ A {}' ] +``` + +Contributed by Joyee Cheung in [#51927](https://github.com/nodejs/node/pull/51927) + +### watch: mark as stable + +From this release Watch Mode is considered stable. +When in watch mode, changes in the watched files cause the Node.js process to restart. + +Contributed by Moshe Atlow in [#52074](https://github.com/nodejs/node/pull/52074) + +### Other Notable Changes + +* \[[`f8ad30048d`](https://github.com/nodejs/node/commit/f8ad30048d)] - **benchmark**: add AbortSignal.abort benchmarks (Raz Luvaton) [#52408](https://github.com/nodejs/node/pull/52408) +* \[[`3b41da9a56`](https://github.com/nodejs/node/commit/3b41da9a56)] - **(SEMVER-MINOR)** **deps**: update simdutf to 5.0.0 (Daniel Lemire) [#52138](https://github.com/nodejs/node/pull/52138) +* \[[`0a08c4a7b3`](https://github.com/nodejs/node/commit/0a08c4a7b3)] - **(SEMVER-MINOR)** **deps**: update undici to 6.3.0 (Node.js GitHub Bot) [#51462](https://github.com/nodejs/node/pull/51462) +* \[[`f1b7bda4f5`](https://github.com/nodejs/node/commit/f1b7bda4f5)] - **(SEMVER-MINOR)** **deps**: update undici to 6.2.1 (Node.js GitHub Bot) [#51278](https://github.com/nodejs/node/pull/51278) +* \[[`4acca8ed84`](https://github.com/nodejs/node/commit/4acca8ed84)] - **(SEMVER-MINOR)** **dns**: add order option and support ipv6first (Paolo Insogna) [#52492](https://github.com/nodejs/node/pull/52492) +* \[[`cc67720ff9`](https://github.com/nodejs/node/commit/cc67720ff9)] - **doc**: update release gpg keyserver (marco-ippolito) [#52257](https://github.com/nodejs/node/pull/52257) +* \[[`c2def7df96`](https://github.com/nodejs/node/commit/c2def7df96)] - **doc**: add release key for marco-ippolito (marco-ippolito) [#52257](https://github.com/nodejs/node/pull/52257) +* \[[`807c89cb26`](https://github.com/nodejs/node/commit/807c89cb26)] - **doc**: add UlisesGascon as a collaborator (Ulises Gascón) [#51991](https://github.com/nodejs/node/pull/51991) +* \[[`5e78a20ef9`](https://github.com/nodejs/node/commit/5e78a20ef9)] - **(SEMVER-MINOR)** **doc**: deprecate fs.Stats public constructor (Marco Ippolito) [#51879](https://github.com/nodejs/node/pull/51879) +* \[[`722fe64ff7`](https://github.com/nodejs/node/commit/722fe64ff7)] - **(SEMVER-MINOR)** **lib, url**: add a `windows` option to path parsing (Aviv Keller) [#52509](https://github.com/nodejs/node/pull/52509) +* \[[`d116fa1568`](https://github.com/nodejs/node/commit/d116fa1568)] - **(SEMVER-MINOR)** **net**: add CLI option for autoSelectFamilyAttemptTimeout (Paolo Insogna) [#52474](https://github.com/nodejs/node/pull/52474) +* \[[`6af7b78b0d`](https://github.com/nodejs/node/commit/6af7b78b0d)] - **(SEMVER-MINOR)** **src**: add `string_view` overload to snapshot FromBlob (Anna Henningsen) [#52595](https://github.com/nodejs/node/pull/52595) +* \[[`b3a11b574b`](https://github.com/nodejs/node/commit/b3a11b574b)] - **(SEMVER-MINOR)** **src**: preload function for Environment (Cheng Zhao) [#51539](https://github.com/nodejs/node/pull/51539) +* \[[`41646d9c9e`](https://github.com/nodejs/node/commit/41646d9c9e)] - **(SEMVER-MINOR)** **test\_runner**: add suite() (Colin Ihrig) [#52127](https://github.com/nodejs/node/pull/52127) +* \[[`fc9ba17f6c`](https://github.com/nodejs/node/commit/fc9ba17f6c)] - **(SEMVER-MINOR)** **test\_runner**: add `test:complete` event to reflect execution order (Moshe Atlow) [#51909](https://github.com/nodejs/node/pull/51909) + +### Commits + +* \[[`6fdd748b21`](https://github.com/nodejs/node/commit/6fdd748b21)] - **benchmark**: reduce the buffer size for blob (Debadree Chatterjee) [#52548](https://github.com/nodejs/node/pull/52548) +* \[[`2274d0c868`](https://github.com/nodejs/node/commit/2274d0c868)] - **benchmark**: inherit stdio/stderr instead of pipe (Ali Hassan) [#52456](https://github.com/nodejs/node/pull/52456) +* \[[`0300598315`](https://github.com/nodejs/node/commit/0300598315)] - **benchmark**: add ipc support to spawn stdio config (Ali Hassan) [#52456](https://github.com/nodejs/node/pull/52456) +* \[[`f8ad30048d`](https://github.com/nodejs/node/commit/f8ad30048d)] - **benchmark**: add AbortSignal.abort benchmarks (Raz Luvaton) [#52408](https://github.com/nodejs/node/pull/52408) +* \[[`7508d48736`](https://github.com/nodejs/node/commit/7508d48736)] - **benchmark**: conditionally use spawn with taskset for cpu pinning (Ali Hassan) [#52253](https://github.com/nodejs/node/pull/52253) +* \[[`ea8e72e185`](https://github.com/nodejs/node/commit/ea8e72e185)] - **benchmark**: add toNamespacedPath bench (Rafael Gonzaga) [#52236](https://github.com/nodejs/node/pull/52236) +* \[[`c00715cc1e`](https://github.com/nodejs/node/commit/c00715cc1e)] - **benchmark**: add style-text benchmark (Rafael Gonzaga) [#52004](https://github.com/nodejs/node/pull/52004) +* \[[`1c1a6935ee`](https://github.com/nodejs/node/commit/1c1a6935ee)] - **buffer**: add missing ARG\_TYPE(ArrayBuffer) for isUtf8 (Jungku Lee) [#52477](https://github.com/nodejs/node/pull/52477) +* \[[`1b2aff7dce`](https://github.com/nodejs/node/commit/1b2aff7dce)] - **buffer**: improve `base64` and `base64url` performance (Yagiz Nizipli) [#52428](https://github.com/nodejs/node/pull/52428) +* \[[`328bded5ab`](https://github.com/nodejs/node/commit/328bded5ab)] - **buffer**: improve `btoa` performance (Yagiz Nizipli) [#52427](https://github.com/nodejs/node/pull/52427) +* \[[`e67bc34326`](https://github.com/nodejs/node/commit/e67bc34326)] - **buffer**: use simdutf for `atob` implementation (Yagiz Nizipli) [#52381](https://github.com/nodejs/node/pull/52381) +* \[[`5abddb45d8`](https://github.com/nodejs/node/commit/5abddb45d8)] - **build**: fix typo in node.gyp (Michaël Zasso) [#52719](https://github.com/nodejs/node/pull/52719) +* \[[`7d1f304f5e`](https://github.com/nodejs/node/commit/7d1f304f5e)] - **build**: fix headers install for shared mode on Win (Segev Finer) [#52442](https://github.com/nodejs/node/pull/52442) +* \[[`6826bbf267`](https://github.com/nodejs/node/commit/6826bbf267)] - **build**: fix arm64 cross-compilation bug on non-arm machines (Mahdi Sharifi) [#52559](https://github.com/nodejs/node/pull/52559) +* \[[`6e85bc431a`](https://github.com/nodejs/node/commit/6e85bc431a)] - **build**: temporary disable ubsan (Rafael Gonzaga) [#52560](https://github.com/nodejs/node/pull/52560) +* \[[`297368a1ed`](https://github.com/nodejs/node/commit/297368a1ed)] - **build**: fix arm64 cross-compilation (Michaël Zasso) [#51256](https://github.com/nodejs/node/pull/51256) +* \[[`93bddb598f`](https://github.com/nodejs/node/commit/93bddb598f)] - **build,tools**: add test-ubsan ci (Rafael Gonzaga) [#46297](https://github.com/nodejs/node/pull/46297) +* \[[`20bb16582f`](https://github.com/nodejs/node/commit/20bb16582f)] - **build,tools,node-api**: fix building node-api tests for Windows Debug (Vladimir Morozov) [#52632](https://github.com/nodejs/node/pull/52632) +* \[[`9af15cfff1`](https://github.com/nodejs/node/commit/9af15cfff1)] - **child\_process**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`a4847c4619`](https://github.com/nodejs/node/commit/a4847c4619)] - **crypto**: simplify assertions in Safe\*Print (David Benjamin) [#49709](https://github.com/nodejs/node/pull/49709) +* \[[`0ec4d9d734`](https://github.com/nodejs/node/commit/0ec4d9d734)] - **crypto**: enable NODE\_EXTRA\_CA\_CERTS with BoringSSL (Shelley Vohr) [#52217](https://github.com/nodejs/node/pull/52217) +* \[[`03e05b092c`](https://github.com/nodejs/node/commit/03e05b092c)] - **crypto**: deprecate implicitly shortened GCM tags (Tobias Nießen) [#52345](https://github.com/nodejs/node/pull/52345) +* \[[`0f784c96ba`](https://github.com/nodejs/node/commit/0f784c96ba)] - **crypto**: make timingSafeEqual faster for Uint8Array (Tobias Nießen) [#52341](https://github.com/nodejs/node/pull/52341) +* \[[`739958e472`](https://github.com/nodejs/node/commit/739958e472)] - **crypto**: reject Ed25519/Ed448 in Sign/Verify prototypes (Filip Skokan) [#52340](https://github.com/nodejs/node/pull/52340) +* \[[`197b61f210`](https://github.com/nodejs/node/commit/197b61f210)] - **crypto**: validate RSA-PSS saltLength in subtle.sign and subtle.verify (Filip Skokan) [#52262](https://github.com/nodejs/node/pull/52262) +* \[[`a6eede33f3`](https://github.com/nodejs/node/commit/a6eede33f3)] - **crypto**: fix `input` validation in `crypto.hash` (Antoine du Hamel) [#52070](https://github.com/nodejs/node/pull/52070) +* \[[`bfa4986e5d`](https://github.com/nodejs/node/commit/bfa4986e5d)] - **deps**: update corepack to 0.28.0 (Node.js GitHub Bot) [#52616](https://github.com/nodejs/node/pull/52616) +* \[[`70546698d7`](https://github.com/nodejs/node/commit/70546698d7)] - **deps**: update ada to 2.7.8 (Node.js GitHub Bot) [#52517](https://github.com/nodejs/node/pull/52517) +* \[[`a135027f84`](https://github.com/nodejs/node/commit/a135027f84)] - **deps**: update icu to 75.1 (Node.js GitHub Bot) [#52573](https://github.com/nodejs/node/pull/52573) +* \[[`c96f1043d4`](https://github.com/nodejs/node/commit/c96f1043d4)] - **deps**: update undici to 6.13.0 (Node.js GitHub Bot) [#52493](https://github.com/nodejs/node/pull/52493) +* \[[`9c330b610b`](https://github.com/nodejs/node/commit/9c330b610b)] - **deps**: update zlib to 1.3.0.1-motley-7d77fb7 (Node.js GitHub Bot) [#52516](https://github.com/nodejs/node/pull/52516) +* \[[`7e5bbeebab`](https://github.com/nodejs/node/commit/7e5bbeebab)] - **deps**: update nghttp2 to 1.61.0 (Node.js GitHub Bot) [#52395](https://github.com/nodejs/node/pull/52395) +* \[[`b42a4735d9`](https://github.com/nodejs/node/commit/b42a4735d9)] - **deps**: update minimatch to 9.0.4 (Node.js GitHub Bot) [#52524](https://github.com/nodejs/node/pull/52524) +* \[[`d34fd21bc2`](https://github.com/nodejs/node/commit/d34fd21bc2)] - **deps**: update simdutf to 5.2.4 (Node.js GitHub Bot) [#52473](https://github.com/nodejs/node/pull/52473) +* \[[`ecc180f830`](https://github.com/nodejs/node/commit/ecc180f830)] - **deps**: upgrade npm to 10.5.2 (npm team) [#52458](https://github.com/nodejs/node/pull/52458) +* \[[`606c183344`](https://github.com/nodejs/node/commit/606c183344)] - **deps**: update simdutf to 5.2.3 (Yagiz Nizipli) [#52381](https://github.com/nodejs/node/pull/52381) +* \[[`0a103e99fe`](https://github.com/nodejs/node/commit/0a103e99fe)] - **deps**: upgrade npm to 10.5.1 (npm team) [#52351](https://github.com/nodejs/node/pull/52351) +* \[[`cce861e670`](https://github.com/nodejs/node/commit/cce861e670)] - **deps**: update c-ares to 1.28.1 (Node.js GitHub Bot) [#52285](https://github.com/nodejs/node/pull/52285) +* \[[`5258b547ea`](https://github.com/nodejs/node/commit/5258b547ea)] - **deps**: update undici to 6.11.1 (Node.js GitHub Bot) [#52328](https://github.com/nodejs/node/pull/52328) +* \[[`923a77c80a`](https://github.com/nodejs/node/commit/923a77c80a)] - **deps**: update undici to 6.10.2 (Node.js GitHub Bot) [#52227](https://github.com/nodejs/node/pull/52227) +* \[[`bd3c6a231c`](https://github.com/nodejs/node/commit/bd3c6a231c)] - **deps**: update zlib to 1.3.0.1-motley-24c07df (Node.js GitHub Bot) [#52199](https://github.com/nodejs/node/pull/52199) +* \[[`3b41da9a56`](https://github.com/nodejs/node/commit/3b41da9a56)] - **(SEMVER-MINOR)** **deps**: update simdutf to 5.0.0 (Daniel Lemire) [#52138](https://github.com/nodejs/node/pull/52138) +* \[[`d6f9ca385c`](https://github.com/nodejs/node/commit/d6f9ca385c)] - **deps**: update zlib to 1.3.0.1-motley-24342f6 (Node.js GitHub Bot) [#52123](https://github.com/nodejs/node/pull/52123) +* \[[`f5512897b0`](https://github.com/nodejs/node/commit/f5512897b0)] - **deps**: update corepack to 0.26.0 (Node.js GitHub Bot) [#52027](https://github.com/nodejs/node/pull/52027) +* \[[`d891275178`](https://github.com/nodejs/node/commit/d891275178)] - **deps**: update ada to 2.7.7 (Node.js GitHub Bot) [#52028](https://github.com/nodejs/node/pull/52028) +* \[[`18838f2db3`](https://github.com/nodejs/node/commit/18838f2db3)] - **deps**: update simdutf to 4.0.9 (Node.js GitHub Bot) [#51655](https://github.com/nodejs/node/pull/51655) +* \[[`503c034abc`](https://github.com/nodejs/node/commit/503c034abc)] - **deps**: update undici to 6.6.2 (Node.js GitHub Bot) [#51667](https://github.com/nodejs/node/pull/51667) +* \[[`256bcba52e`](https://github.com/nodejs/node/commit/256bcba52e)] - **deps**: update undici to 6.6.0 (Node.js GitHub Bot) [#51630](https://github.com/nodejs/node/pull/51630) +* \[[`7a1e321d95`](https://github.com/nodejs/node/commit/7a1e321d95)] - **deps**: update undici to 6.4.0 (Node.js GitHub Bot) [#51527](https://github.com/nodejs/node/pull/51527) +* \[[`dde9e08224`](https://github.com/nodejs/node/commit/dde9e08224)] - **deps**: update ngtcp2 to 1.1.0 (Node.js GitHub Bot) [#51319](https://github.com/nodejs/node/pull/51319) +* \[[`0a08c4a7b3`](https://github.com/nodejs/node/commit/0a08c4a7b3)] - **(SEMVER-MINOR)** **deps**: update undici to 6.3.0 (Node.js GitHub Bot) [#51462](https://github.com/nodejs/node/pull/51462) +* \[[`f1b7bda4f5`](https://github.com/nodejs/node/commit/f1b7bda4f5)] - **(SEMVER-MINOR)** **deps**: update undici to 6.2.1 (Node.js GitHub Bot) [#51278](https://github.com/nodejs/node/pull/51278) +* \[[`ecadd638cd`](https://github.com/nodejs/node/commit/ecadd638cd)] - **deps**: V8: remove references to non-existent flags (Richard Lau) [#52256](https://github.com/nodejs/node/pull/52256) +* \[[`27d364491f`](https://github.com/nodejs/node/commit/27d364491f)] - **dgram**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`b94d11935a`](https://github.com/nodejs/node/commit/b94d11935a)] - **diagnostics\_channel**: early-exit tracing channel trace methods (Stephen Belanger) [#51915](https://github.com/nodejs/node/pull/51915) +* \[[`4acca8ed84`](https://github.com/nodejs/node/commit/4acca8ed84)] - **(SEMVER-MINOR)** **dns**: add order option and support ipv6first (Paolo Insogna) [#52492](https://github.com/nodejs/node/pull/52492) +* \[[`bcc06ac5a9`](https://github.com/nodejs/node/commit/bcc06ac5a9)] - **doc**: remove relative limitation to pm (Rafael Gonzaga) [#52648](https://github.com/nodejs/node/pull/52648) +* \[[`4d5ef4f7af`](https://github.com/nodejs/node/commit/4d5ef4f7af)] - **doc**: fix info string causing duplicated code blocks (Mathieu Leenhardt) [#52660](https://github.com/nodejs/node/pull/52660) +* \[[`d5a316f5ea`](https://github.com/nodejs/node/commit/d5a316f5ea)] - **doc**: run license-builder (github-actions\[bot]) [#52631](https://github.com/nodejs/node/pull/52631) +* \[[`d7434fe411`](https://github.com/nodejs/node/commit/d7434fe411)] - **doc**: deprecate --experimental-policy (RafaelGSS) [#52602](https://github.com/nodejs/node/pull/52602) +* \[[`02a83d89f7`](https://github.com/nodejs/node/commit/02a83d89f7)] - **doc**: add info on contributor spotlight program (Michael Dawson) [#52598](https://github.com/nodejs/node/pull/52598) +* \[[`a905eaace1`](https://github.com/nodejs/node/commit/a905eaace1)] - **doc**: correct unsafe URL example in http docs (Malte Legenhausen) [#52555](https://github.com/nodejs/node/pull/52555) +* \[[`69c21a6522`](https://github.com/nodejs/node/commit/69c21a6522)] - **doc**: replace U+00A0 with U+0020 (Luigi Pinca) [#52590](https://github.com/nodejs/node/pull/52590) +* \[[`5df34c7d0a`](https://github.com/nodejs/node/commit/5df34c7d0a)] - **doc**: sort options alphabetically (Luigi Pinca) [#52589](https://github.com/nodejs/node/pull/52589) +* \[[`b49464bc9d`](https://github.com/nodejs/node/commit/b49464bc9d)] - **doc**: correct stream.finished changes (KaKa) [#52551](https://github.com/nodejs/node/pull/52551) +* \[[`4d051ba89b`](https://github.com/nodejs/node/commit/4d051ba89b)] - **doc**: add RedYetiDev to triage team (Aviv Keller) [#52556](https://github.com/nodejs/node/pull/52556) +* \[[`4ed55bf16c`](https://github.com/nodejs/node/commit/4ed55bf16c)] - **doc**: fix issue detected in markdown lint update (Rich Trott) [#52566](https://github.com/nodejs/node/pull/52566) +* \[[`a8bc40fd07`](https://github.com/nodejs/node/commit/a8bc40fd07)] - **doc**: update test runner coverage limitations (Moshe Atlow) [#52515](https://github.com/nodejs/node/pull/52515) +* \[[`17d5ba9fed`](https://github.com/nodejs/node/commit/17d5ba9fed)] - **doc**: add lint-js-fix into BUILDING.md (jakecastelli) [#52290](https://github.com/nodejs/node/pull/52290) +* \[[`88adbd0991`](https://github.com/nodejs/node/commit/88adbd0991)] - **doc**: remove Internet Explorer mention in BUILDING.md (Rich Trott) [#52455](https://github.com/nodejs/node/pull/52455) +* \[[`e8cb29d66d`](https://github.com/nodejs/node/commit/e8cb29d66d)] - **doc**: accommodate upcoming stricter .md linting (Rich Trott) [#52454](https://github.com/nodejs/node/pull/52454) +* \[[`e2ea984c7b`](https://github.com/nodejs/node/commit/e2ea984c7b)] - **doc**: add Rafael to steward list (Rafael Gonzaga) [#52452](https://github.com/nodejs/node/pull/52452) +* \[[`93d684097a`](https://github.com/nodejs/node/commit/93d684097a)] - **doc**: correct naming convention in C++ style guide (Mohammed Keyvanzadeh) [#52424](https://github.com/nodejs/node/pull/52424) +* \[[`b9bdb947ac`](https://github.com/nodejs/node/commit/b9bdb947ac)] - **doc**: update `process.execArg` example to be more useful (Jacob Smith) [#52412](https://github.com/nodejs/node/pull/52412) +* \[[`f3f67ff84a`](https://github.com/nodejs/node/commit/f3f67ff84a)] - **doc**: call out http(s).globalAgent default (mathis-west-1) [#52392](https://github.com/nodejs/node/pull/52392) +* \[[`392a0d310e`](https://github.com/nodejs/node/commit/392a0d310e)] - **doc**: update the location of `build_with_cmake` (Emmanuel Ferdman) [#52356](https://github.com/nodejs/node/pull/52356) +* \[[`3ad62f1cc7`](https://github.com/nodejs/node/commit/3ad62f1cc7)] - **doc**: reserve 125 for Electron 31 (Shelley Vohr) [#52379](https://github.com/nodejs/node/pull/52379) +* \[[`bfd4c7844b`](https://github.com/nodejs/node/commit/bfd4c7844b)] - **doc**: use consistent plural form of "index" (Rich Trott) [#52373](https://github.com/nodejs/node/pull/52373) +* \[[`6f31cc8361`](https://github.com/nodejs/node/commit/6f31cc8361)] - **doc**: add Rafael to sec release stewards (Rafael Gonzaga) [#52354](https://github.com/nodejs/node/pull/52354) +* \[[`c55a3be789`](https://github.com/nodejs/node/commit/c55a3be789)] - **doc**: document missing options of events.on (Chemi Atlow) [#52080](https://github.com/nodejs/node/pull/52080) +* \[[`1a843f7c6d`](https://github.com/nodejs/node/commit/1a843f7c6d)] - **doc**: add missing space (Augustin Mauroy) [#52360](https://github.com/nodejs/node/pull/52360) +* \[[`8ee20d8693`](https://github.com/nodejs/node/commit/8ee20d8693)] - **doc**: add tips about vcpkg cause build faild on windows (Cong Zhang) [#52181](https://github.com/nodejs/node/pull/52181) +* \[[`a86705c113`](https://github.com/nodejs/node/commit/a86705c113)] - **doc**: replace "below" with "following" (Rich Trott) [#52315](https://github.com/nodejs/node/pull/52315) +* \[[`f3e8d1159a`](https://github.com/nodejs/node/commit/f3e8d1159a)] - **doc**: fix email pattern to be wrapped with `<<` instead of single `<` (Raz Luvaton) [#52284](https://github.com/nodejs/node/pull/52284) +* \[[`cc67720ff9`](https://github.com/nodejs/node/commit/cc67720ff9)] - **doc**: update release gpg keyserver (marco-ippolito) [#52257](https://github.com/nodejs/node/pull/52257) +* \[[`c2def7df96`](https://github.com/nodejs/node/commit/c2def7df96)] - **doc**: add release key for marco-ippolito (marco-ippolito) [#52257](https://github.com/nodejs/node/pull/52257) +* \[[`2509f3be18`](https://github.com/nodejs/node/commit/2509f3be18)] - **doc**: fix arrow vertical alignment in HTML version (Akash Yeole) [#52193](https://github.com/nodejs/node/pull/52193) +* \[[`2abaea3cdc`](https://github.com/nodejs/node/commit/2abaea3cdc)] - **doc**: move TSC members from regular to emeritus (Michael Dawson) [#52209](https://github.com/nodejs/node/pull/52209) +* \[[`65618a3d7b`](https://github.com/nodejs/node/commit/65618a3d7b)] - **doc**: add section explaining todo tests (Colin Ihrig) [#52204](https://github.com/nodejs/node/pull/52204) +* \[[`bf0ed95b04`](https://github.com/nodejs/node/commit/bf0ed95b04)] - **doc**: edit `ChildProcess` `'message'` event docs (theanarkh) [#52154](https://github.com/nodejs/node/pull/52154) +* \[[`3d67b6b5e8`](https://github.com/nodejs/node/commit/3d67b6b5e8)] - **doc**: add mold to speeding up section (Cong Zhang) [#52179](https://github.com/nodejs/node/pull/52179) +* \[[`8ba308a838`](https://github.com/nodejs/node/commit/8ba308a838)] - **doc**: http event order correction (wh0) [#51464](https://github.com/nodejs/node/pull/51464) +* \[[`9771f41069`](https://github.com/nodejs/node/commit/9771f41069)] - **doc**: move gabrielschulhof to TSC emeritus (Gabriel Schulhof) [#52192](https://github.com/nodejs/node/pull/52192) +* \[[`72bd2b0d62`](https://github.com/nodejs/node/commit/72bd2b0d62)] - **doc**: fix `--env-file` docs for valid quotes for defining values (Gabriel Bota) [#52157](https://github.com/nodejs/node/pull/52157) +* \[[`4f19203dfb`](https://github.com/nodejs/node/commit/4f19203dfb)] - **doc**: clarify what is supported in NODE\_OPTIONS (Michael Dawson) [#52076](https://github.com/nodejs/node/pull/52076) +* \[[`5bce596838`](https://github.com/nodejs/node/commit/5bce596838)] - **doc**: fix typos in maintaining-dependencies.md (RoboSchmied) [#52160](https://github.com/nodejs/node/pull/52160) +* \[[`f5241e20cc`](https://github.com/nodejs/node/commit/f5241e20cc)] - **doc**: add spec for contains module syntax (Geoffrey Booth) [#52059](https://github.com/nodejs/node/pull/52059) +* \[[`bda3cdea86`](https://github.com/nodejs/node/commit/bda3cdea86)] - **doc**: optimize the doc about Unix abstract socket (theanarkh) [#52043](https://github.com/nodejs/node/pull/52043) +* \[[`8d7d6eff81`](https://github.com/nodejs/node/commit/8d7d6eff81)] - **doc**: update pnpm link (Superchupu) [#52113](https://github.com/nodejs/node/pull/52113) +* \[[`af7c55f62d`](https://github.com/nodejs/node/commit/af7c55f62d)] - **doc**: remove ableist language from crypto (Jamie King) [#52063](https://github.com/nodejs/node/pull/52063) +* \[[`f8362b0a5a`](https://github.com/nodejs/node/commit/f8362b0a5a)] - **doc**: update collaborator email (Ruy Adorno) [#52088](https://github.com/nodejs/node/pull/52088) +* \[[`48cbd5f71e`](https://github.com/nodejs/node/commit/48cbd5f71e)] - **doc**: state that removing npm is a non-goal (Geoffrey Booth) [#51951](https://github.com/nodejs/node/pull/51951) +* \[[`0ef2708131`](https://github.com/nodejs/node/commit/0ef2708131)] - **doc**: mention NodeSource in RafaelGSS steward list (Rafael Gonzaga) [#52057](https://github.com/nodejs/node/pull/52057) +* \[[`a6473a89be`](https://github.com/nodejs/node/commit/a6473a89be)] - **doc**: remove ArrayBuffer from crypto.hash() data parameter type (fengmk2) [#52069](https://github.com/nodejs/node/pull/52069) +* \[[`ae7a11c787`](https://github.com/nodejs/node/commit/ae7a11c787)] - **doc**: add some commonly used lables up gront (Michael Dawson) [#52006](https://github.com/nodejs/node/pull/52006) +* \[[`01aaddde3c`](https://github.com/nodejs/node/commit/01aaddde3c)] - **doc**: document that `const c2 = vm.createContext(c1); c1 === c2` is true (Daniel Kaplan) [#51960](https://github.com/nodejs/node/pull/51960) +* \[[`912145fac4`](https://github.com/nodejs/node/commit/912145fac4)] - **doc**: clarify what moderation issues are for (Antoine du Hamel) [#51990](https://github.com/nodejs/node/pull/51990) +* \[[`807c89cb26`](https://github.com/nodejs/node/commit/807c89cb26)] - **doc**: add UlisesGascon as a collaborator (Ulises Gascón) [#51991](https://github.com/nodejs/node/pull/51991) +* \[[`53ff3e5682`](https://github.com/nodejs/node/commit/53ff3e5682)] - **doc**: deprecate hmac public constructor (Marco Ippolito) [#51881](https://github.com/nodejs/node/pull/51881) +* \[[`5e78a20ef9`](https://github.com/nodejs/node/commit/5e78a20ef9)] - **(SEMVER-MINOR)** **doc**: deprecate fs.Stats public constructor (Marco Ippolito) [#51879](https://github.com/nodejs/node/pull/51879) +* \[[`7bfb0b43e6`](https://github.com/nodejs/node/commit/7bfb0b43e6)] - **events**: rename high & low watermark for consistency (Chemi Atlow) [#52080](https://github.com/nodejs/node/pull/52080) +* \[[`5e6967359b`](https://github.com/nodejs/node/commit/5e6967359b)] - **events**: extract addAbortListener for safe internal use (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`6930205272`](https://github.com/nodejs/node/commit/6930205272)] - **events**: remove abort listener from signal in `on` (Neal Beeken) [#51091](https://github.com/nodejs/node/pull/51091) +* \[[`235ab4f99f`](https://github.com/nodejs/node/commit/235ab4f99f)] - **events,doc**: mark CustomEvent as stable (Daeyeon Jeong) [#52618](https://github.com/nodejs/node/pull/52618) +* \[[`ca5b827148`](https://github.com/nodejs/node/commit/ca5b827148)] - **fs**: fix read / readSync positional offset types (Ruy Adorno) [#52603](https://github.com/nodejs/node/pull/52603) +* \[[`e7d0d804b2`](https://github.com/nodejs/node/commit/e7d0d804b2)] - **fs**: fixes recursive fs.watch crash on Linux when deleting files (Matteo Collina) [#52349](https://github.com/nodejs/node/pull/52349) +* \[[`c5fd193d6b`](https://github.com/nodejs/node/commit/c5fd193d6b)] - **fs**: refactor maybeCallback function (Yagiz Nizipli) [#52129](https://github.com/nodejs/node/pull/52129) +* \[[`0a9910c2c1`](https://github.com/nodejs/node/commit/0a9910c2c1)] - **fs**: fix edge case in readFileSync utf8 fast path (Richard Lau) [#52101](https://github.com/nodejs/node/pull/52101) +* \[[`51d7cd5de8`](https://github.com/nodejs/node/commit/51d7cd5de8)] - **fs**: validate fd from cpp on `fchown` (Yagiz Nizipli) [#52051](https://github.com/nodejs/node/pull/52051) +* \[[`33ad86c2be`](https://github.com/nodejs/node/commit/33ad86c2be)] - **fs**: validate fd from cpp on `close` (Yagiz Nizipli) [#52051](https://github.com/nodejs/node/pull/52051) +* \[[`34667c0a7e`](https://github.com/nodejs/node/commit/34667c0a7e)] - **fs**: validate file mode from cpp (Yagiz Nizipli) [#52050](https://github.com/nodejs/node/pull/52050) +* \[[`c530520be3`](https://github.com/nodejs/node/commit/c530520be3)] - **fs**: add stacktrace to fs/promises (翠 / green) [#49849](https://github.com/nodejs/node/pull/49849) +* \[[`edecd464b9`](https://github.com/nodejs/node/commit/edecd464b9)] - **fs,permission**: make handling of buffers consistent (Tobias Nießen) [#52348](https://github.com/nodejs/node/pull/52348) +* \[[`3bcd68337e`](https://github.com/nodejs/node/commit/3bcd68337e)] - **http2**: fix excessive CPU usage when using `allowHTTP1=true` (Eugene) [#52713](https://github.com/nodejs/node/pull/52713) +* \[[`e01015996a`](https://github.com/nodejs/node/commit/e01015996a)] - **http2**: fix h2-over-h2 connection proxying (Tim Perry) [#52368](https://github.com/nodejs/node/pull/52368) +* \[[`9f88736860`](https://github.com/nodejs/node/commit/9f88736860)] - **http2**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`acd7758959`](https://github.com/nodejs/node/commit/acd7758959)] - **lib**: use predefined variable instead of bit operation (Deokjin Kim) [#52580](https://github.com/nodejs/node/pull/52580) +* \[[`18ae7a46f6`](https://github.com/nodejs/node/commit/18ae7a46f6)] - **lib**: refactor lazy loading of undici for fetch method (Victor Chen) [#52275](https://github.com/nodejs/node/pull/52275) +* \[[`64c2c2a7ac`](https://github.com/nodejs/node/commit/64c2c2a7ac)] - **lib**: replace string prototype usage with alternatives (Aviv Keller) [#52440](https://github.com/nodejs/node/pull/52440) +* \[[`ee11b5315c`](https://github.com/nodejs/node/commit/ee11b5315c)] - **lib**: .load .save add proper error message when no file passed (Thomas Mauran) [#52225](https://github.com/nodejs/node/pull/52225) +* \[[`e5521b537f`](https://github.com/nodejs/node/commit/e5521b537f)] - **lib**: fix type error for \_refreshLine (Jackson Tian) [#52133](https://github.com/nodejs/node/pull/52133) +* \[[`d5d6e041c8`](https://github.com/nodejs/node/commit/d5d6e041c8)] - **lib**: emit listening event once when call listen twice (theanarkh) [#52119](https://github.com/nodejs/node/pull/52119) +* \[[`d33fc36784`](https://github.com/nodejs/node/commit/d33fc36784)] - **lib**: make sure clear the old timer in http server (theanarkh) [#52118](https://github.com/nodejs/node/pull/52118) +* \[[`ea4905c0f5`](https://github.com/nodejs/node/commit/ea4905c0f5)] - **lib**: fix listen with handle in cluster worker (theanarkh) [#52056](https://github.com/nodejs/node/pull/52056) +* \[[`8fd8130507`](https://github.com/nodejs/node/commit/8fd8130507)] - **lib, doc**: rename readme.md to README.md (Aviv Keller) [#52471](https://github.com/nodejs/node/pull/52471) +* \[[`722fe64ff7`](https://github.com/nodejs/node/commit/722fe64ff7)] - **(SEMVER-MINOR)** **lib, url**: add a `windows` option to path parsing (Aviv Keller) [#52509](https://github.com/nodejs/node/pull/52509) +* \[[`26691e6032`](https://github.com/nodejs/node/commit/26691e6032)] - **meta**: move one or more collaborators to emeritus (Node.js GitHub Bot) [#52633](https://github.com/nodejs/node/pull/52633) +* \[[`befb90dc83`](https://github.com/nodejs/node/commit/befb90dc83)] - **meta**: move one or more collaborators to emeritus (Node.js GitHub Bot) [#52457](https://github.com/nodejs/node/pull/52457) +* \[[`22b7167e72`](https://github.com/nodejs/node/commit/22b7167e72)] - **meta**: bump actions/download-artifact from 4.1.3 to 4.1.4 (dependabot\[bot]) [#52314](https://github.com/nodejs/node/pull/52314) +* \[[`4dafd3ede2`](https://github.com/nodejs/node/commit/4dafd3ede2)] - **meta**: bump rtCamp/action-slack-notify from 2.2.1 to 2.3.0 (dependabot\[bot]) [#52313](https://github.com/nodejs/node/pull/52313) +* \[[`2760db7640`](https://github.com/nodejs/node/commit/2760db7640)] - **meta**: bump github/codeql-action from 3.24.6 to 3.24.9 (dependabot\[bot]) [#52312](https://github.com/nodejs/node/pull/52312) +* \[[`542aaf9ca9`](https://github.com/nodejs/node/commit/542aaf9ca9)] - **meta**: bump actions/cache from 4.0.1 to 4.0.2 (dependabot\[bot]) [#52311](https://github.com/nodejs/node/pull/52311) +* \[[`df330998d9`](https://github.com/nodejs/node/commit/df330998d9)] - **meta**: bump actions/setup-python from 5.0.0 to 5.1.0 (dependabot\[bot]) [#52310](https://github.com/nodejs/node/pull/52310) +* \[[`5f40fe0cc2`](https://github.com/nodejs/node/commit/5f40fe0cc2)] - **meta**: bump codecov/codecov-action from 4.1.0 to 4.1.1 (dependabot\[bot]) [#52308](https://github.com/nodejs/node/pull/52308) +* \[[`481420f25c`](https://github.com/nodejs/node/commit/481420f25c)] - **meta**: move one or more collaborators to emeritus (Node.js GitHub Bot) [#52300](https://github.com/nodejs/node/pull/52300) +* \[[`3121949f85`](https://github.com/nodejs/node/commit/3121949f85)] - **meta**: pass Codecov upload token to codecov action (Michaël Zasso) [#51982](https://github.com/nodejs/node/pull/51982) +* \[[`882a64e639`](https://github.com/nodejs/node/commit/882a64e639)] - **module**: fix detect-module not retrying as esm for cjs-only errors (Geoffrey Booth) [#52024](https://github.com/nodejs/node/pull/52024) +* \[[`5fcc1d32a8`](https://github.com/nodejs/node/commit/5fcc1d32a8)] - **module**: refactor ESM loader initialization and entry point handling (Joyee Cheung) [#51999](https://github.com/nodejs/node/pull/51999) +* \[[`d116fa1568`](https://github.com/nodejs/node/commit/d116fa1568)] - **(SEMVER-MINOR)** **net**: add CLI option for autoSelectFamilyAttemptTimeout (Paolo Insogna) [#52474](https://github.com/nodejs/node/pull/52474) +* \[[`37abad86ae`](https://github.com/nodejs/node/commit/37abad86ae)] - **net**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`a920489a1f`](https://github.com/nodejs/node/commit/a920489a1f)] - **node-api**: address coverity report (Michael Dawson) [#52584](https://github.com/nodejs/node/pull/52584) +* \[[`0a225a4b40`](https://github.com/nodejs/node/commit/0a225a4b40)] - **node-api**: copy external type tags when they are set (Niels Martignène) [#52426](https://github.com/nodejs/node/pull/52426) +* \[[`f9d95674be`](https://github.com/nodejs/node/commit/f9d95674be)] - **node-api**: make tsfn accept napi\_finalize once more (Gabriel Schulhof) [#51801](https://github.com/nodejs/node/pull/51801) +* \[[`72aabe1139`](https://github.com/nodejs/node/commit/72aabe1139)] - **perf\_hooks**: reduce overhead of createHistogram (Vinícius Lourenço) [#50074](https://github.com/nodejs/node/pull/50074) +* \[[`fb601c3a94`](https://github.com/nodejs/node/commit/fb601c3a94)] - **readline**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`29f09f05f7`](https://github.com/nodejs/node/commit/29f09f05f7)] - **(SEMVER-MINOR)** **report**: add `--report-exclude-network` option (Ethan Arrowood) [#51645](https://github.com/nodejs/node/pull/51645) +* \[[`7e6d923f5b`](https://github.com/nodejs/node/commit/7e6d923f5b)] - **src**: cast to v8::Value before using v8::EmbedderGraph::V8Node (Joyee Cheung) [#52638](https://github.com/nodejs/node/pull/52638) +* \[[`6af7b78b0d`](https://github.com/nodejs/node/commit/6af7b78b0d)] - **(SEMVER-MINOR)** **src**: add `string_view` overload to snapshot FromBlob (Anna Henningsen) [#52595](https://github.com/nodejs/node/pull/52595) +* \[[`27491e55c1`](https://github.com/nodejs/node/commit/27491e55c1)] - **src**: remove regex usage for env file parsing (IlyasShabi) [#52406](https://github.com/nodejs/node/pull/52406) +* \[[`b05e639e27`](https://github.com/nodejs/node/commit/b05e639e27)] - **src**: fix loadEnvFile ENOENT error (mathis-west-1) [#52438](https://github.com/nodejs/node/pull/52438) +* \[[`1b4d2814d1`](https://github.com/nodejs/node/commit/1b4d2814d1)] - **src**: update branch name in node\_revert.h (Tobias Nießen) [#52390](https://github.com/nodejs/node/pull/52390) +* \[[`7e35a169ea`](https://github.com/nodejs/node/commit/7e35a169ea)] - **src**: stop using `v8::BackingStore::Reallocate` (Michaël Zasso) [#52292](https://github.com/nodejs/node/pull/52292) +* \[[`2449d2606a`](https://github.com/nodejs/node/commit/2449d2606a)] - **src**: fix move after use reported by coverity (Michael Dawson) [#52141](https://github.com/nodejs/node/pull/52141) +* \[[`b33eff887d`](https://github.com/nodejs/node/commit/b33eff887d)] - **(SEMVER-MINOR)** **src**: add C++ ProcessEmitWarningSync() (Joyee Cheung) [#51977](https://github.com/nodejs/node/pull/51977) +* \[[`f2c7408927`](https://github.com/nodejs/node/commit/f2c7408927)] - **src**: return a number from process.constrainedMemory() constantly (Chengzhong Wu) [#52039](https://github.com/nodejs/node/pull/52039) +* \[[`7f575c886b`](https://github.com/nodejs/node/commit/7f575c886b)] - **(SEMVER-MINOR)** **src**: add uv\_get\_available\_memory to report and process (theanarkh) [#52023](https://github.com/nodejs/node/pull/52023) +* \[[`e161e62313`](https://github.com/nodejs/node/commit/e161e62313)] - **src**: use dedicated routine to compile function for builtin CJS loader (Joyee Cheung) [#52016](https://github.com/nodejs/node/pull/52016) +* \[[`07322b490f`](https://github.com/nodejs/node/commit/07322b490f)] - **src**: fix reading empty string views in Blob\[De]serializer (Joyee Cheung) [#52000](https://github.com/nodejs/node/pull/52000) +* \[[`e216192390`](https://github.com/nodejs/node/commit/e216192390)] - **src**: refactor out FormatErrorMessage for error formatting (Joyee Cheung) [#51999](https://github.com/nodejs/node/pull/51999) +* \[[`b3a11b574b`](https://github.com/nodejs/node/commit/b3a11b574b)] - **(SEMVER-MINOR)** **src**: preload function for Environment (Cheng Zhao) [#51539](https://github.com/nodejs/node/pull/51539) +* \[[`09bd367ef6`](https://github.com/nodejs/node/commit/09bd367ef6)] - **stream**: make Duplex inherit destroy from Writable (Luigi Pinca) [#52318](https://github.com/nodejs/node/pull/52318) +* \[[`0b853a7576`](https://github.com/nodejs/node/commit/0b853a7576)] - **(SEMVER-MINOR)** **stream**: support typed arrays (IlyasShabi) [#51866](https://github.com/nodejs/node/pull/51866) +* \[[`f8209ffe45`](https://github.com/nodejs/node/commit/f8209ffe45)] - **stream**: add `new` when constructing `ERR_MULTIPLE_CALLBACK` (haze) [#52110](https://github.com/nodejs/node/pull/52110) +* \[[`8442457117`](https://github.com/nodejs/node/commit/8442457117)] - **stream**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`8d20b641a2`](https://github.com/nodejs/node/commit/8d20b641a2)] - _**Revert**_ "**stream**: fix cloned webstreams not being unref'd" (Matteo Collina) [#51491](https://github.com/nodejs/node/pull/51491) +* \[[`a923adffab`](https://github.com/nodejs/node/commit/a923adffab)] - **test**: mark `test-error-serdes` as flaky (Antoine du Hamel) [#52739](https://github.com/nodejs/node/pull/52739) +* \[[`d4f1803f0b`](https://github.com/nodejs/node/commit/d4f1803f0b)] - **test**: mark test as flaky (Michael Dawson) [#52671](https://github.com/nodejs/node/pull/52671) +* \[[`1e88e042c2`](https://github.com/nodejs/node/commit/1e88e042c2)] - **test**: skip test-fs-watch-recursive-delete.js on IBM i (Abdirahim Musse) [#52645](https://github.com/nodejs/node/pull/52645) +* \[[`6da558af8b`](https://github.com/nodejs/node/commit/6da558af8b)] - **test**: ensure that all worker servers are ready (Luigi Pinca) [#52563](https://github.com/nodejs/node/pull/52563) +* \[[`c871fadb85`](https://github.com/nodejs/node/commit/c871fadb85)] - **test**: fix test-tls-ticket-cluster.js (Hüseyin Açacak) [#52431](https://github.com/nodejs/node/pull/52431) +* \[[`b6cb74d775`](https://github.com/nodejs/node/commit/b6cb74d775)] - **test**: split wasi poll test for windows (Hüseyin Açacak) [#52538](https://github.com/nodejs/node/pull/52538) +* \[[`4ad159bb75`](https://github.com/nodejs/node/commit/4ad159bb75)] - **test**: write tests for assertIsArray http2 util (Sinan Sonmez (Chaush)) [#52511](https://github.com/nodejs/node/pull/52511) +* \[[`1f7a28cbe7`](https://github.com/nodejs/node/commit/1f7a28cbe7)] - **test**: fix watch test with require not testing pid (Raz Luvaton) [#52353](https://github.com/nodejs/node/pull/52353) +* \[[`5b758b93d5`](https://github.com/nodejs/node/commit/5b758b93d5)] - **test**: simplify ASan build checks (Michaël Zasso) [#52430](https://github.com/nodejs/node/pull/52430) +* \[[`375c3db5ea`](https://github.com/nodejs/node/commit/375c3db5ea)] - **test**: fix Windows compiler warnings in overlapped-checker (Michaël Zasso) [#52405](https://github.com/nodejs/node/pull/52405) +* \[[`a1dd92cdee`](https://github.com/nodejs/node/commit/a1dd92cdee)] - **test**: add test for skip+todo combinations (Colin Ihrig) [#52204](https://github.com/nodejs/node/pull/52204) +* \[[`8a0b721930`](https://github.com/nodejs/node/commit/8a0b721930)] - **test**: fix incorrect test fixture (Colin Ihrig) [#52185](https://github.com/nodejs/node/pull/52185) +* \[[`dd1f761f3b`](https://github.com/nodejs/node/commit/dd1f761f3b)] - **test**: add missing cctest/test\_path.cc (Yagiz Nizipli) [#52148](https://github.com/nodejs/node/pull/52148) +* \[[`6da446d9e1`](https://github.com/nodejs/node/commit/6da446d9e1)] - **test**: add `spawnSyncAndAssert` util (Antoine du Hamel) [#52132](https://github.com/nodejs/node/pull/52132) +* \[[`d7bfb4e8d8`](https://github.com/nodejs/node/commit/d7bfb4e8d8)] - **test**: reduce flakiness of test-runner-output.mjs (Colin Ihrig) [#52146](https://github.com/nodejs/node/pull/52146) +* \[[`e4981b3d75`](https://github.com/nodejs/node/commit/e4981b3d75)] - **test**: add test for using `--print` with promises (Antoine du Hamel) [#52137](https://github.com/nodejs/node/pull/52137) +* \[[`5cc540078e`](https://github.com/nodejs/node/commit/5cc540078e)] - **test**: un-set test-emit-after-on-destroyed as flaky (Abdirahim Musse) [#51995](https://github.com/nodejs/node/pull/51995) +* \[[`b9eb0035dd`](https://github.com/nodejs/node/commit/b9eb0035dd)] - **test**: skip test for dynamically linked OpenSSL (Richard Lau) [#52542](https://github.com/nodejs/node/pull/52542) +* \[[`32014f5601`](https://github.com/nodejs/node/commit/32014f5601)] - **test**: avoid v8 deadcode on performance function (Vinícius Lourenço) [#50074](https://github.com/nodejs/node/pull/50074) +* \[[`29d2011f51`](https://github.com/nodejs/node/commit/29d2011f51)] - **test\_runner**: better error handing for test hook (Alex Yang) [#52401](https://github.com/nodejs/node/pull/52401) +* \[[`9497097fb3`](https://github.com/nodejs/node/commit/9497097fb3)] - **test\_runner**: fix clearing final timeout in own callback (Ben Richeson) [#52332](https://github.com/nodejs/node/pull/52332) +* \[[`0f690f0b9e`](https://github.com/nodejs/node/commit/0f690f0b9e)] - **test\_runner**: fix recursive run (Moshe Atlow) [#52322](https://github.com/nodejs/node/pull/52322) +* \[[`34ab1a36ee`](https://github.com/nodejs/node/commit/34ab1a36ee)] - **test\_runner**: hide new line when no error in spec reporter (Moshe Atlow) [#52297](https://github.com/nodejs/node/pull/52297) +* \[[`379535abe3`](https://github.com/nodejs/node/commit/379535abe3)] - **test\_runner**: disable highWatermark on TestsStream (Colin Ihrig) [#52287](https://github.com/nodejs/node/pull/52287) +* \[[`35588cff39`](https://github.com/nodejs/node/commit/35588cff39)] - **test\_runner**: run afterEach hooks in correct order (Colin Ihrig) [#52239](https://github.com/nodejs/node/pull/52239) +* \[[`5cd3df8fe1`](https://github.com/nodejs/node/commit/5cd3df8fe1)] - **test\_runner**: simplify test end time tracking (Colin Ihrig) [#52182](https://github.com/nodejs/node/pull/52182) +* \[[`07e4a42e4b`](https://github.com/nodejs/node/commit/07e4a42e4b)] - **test\_runner**: simplify test start time tracking (Colin Ihrig) [#52182](https://github.com/nodejs/node/pull/52182) +* \[[`caec996831`](https://github.com/nodejs/node/commit/caec996831)] - **test\_runner**: emit diagnostics when watch mode drains (Moshe Atlow) [#52130](https://github.com/nodejs/node/pull/52130) +* \[[`41646d9c9e`](https://github.com/nodejs/node/commit/41646d9c9e)] - **(SEMVER-MINOR)** **test\_runner**: add suite() (Colin Ihrig) [#52127](https://github.com/nodejs/node/pull/52127) +* \[[`fd1489a623`](https://github.com/nodejs/node/commit/fd1489a623)] - **test\_runner**: skip each hooks for skipped tests (Colin Ihrig) [#52115](https://github.com/nodejs/node/pull/52115) +* \[[`73b38bfa9e`](https://github.com/nodejs/node/commit/73b38bfa9e)] - **test\_runner**: remove redundant report call (Colin Ihrig) [#52089](https://github.com/nodejs/node/pull/52089) +* \[[`68187c4d9e`](https://github.com/nodejs/node/commit/68187c4d9e)] - **test\_runner**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`61e7ae05ef`](https://github.com/nodejs/node/commit/61e7ae05ef)] - **test\_runner**: use source maps when reporting coverage (Moshe Atlow) [#52060](https://github.com/nodejs/node/pull/52060) +* \[[`e64a25af61`](https://github.com/nodejs/node/commit/e64a25af61)] - **test\_runner**: handle undefined test locations (Colin Ihrig) [#52036](https://github.com/nodejs/node/pull/52036) +* \[[`590decf202`](https://github.com/nodejs/node/commit/590decf202)] - **test\_runner**: avoid overwriting root start time (Colin Ihrig) [#52020](https://github.com/nodejs/node/pull/52020) +* \[[`a4cbb61c65`](https://github.com/nodejs/node/commit/a4cbb61c65)] - **test\_runner**: abort unfinished tests on async error (Colin Ihrig) [#51996](https://github.com/nodejs/node/pull/51996) +* \[[`a223ca4868`](https://github.com/nodejs/node/commit/a223ca4868)] - **test\_runner**: run before hook immediately if test started (Moshe Atlow) [#52003](https://github.com/nodejs/node/pull/52003) +* \[[`956ee74c7e`](https://github.com/nodejs/node/commit/956ee74c7e)] - **test\_runner**: add support for null and date value output (Malthe Borch) [#51920](https://github.com/nodejs/node/pull/51920) +* \[[`fc9ba17f6c`](https://github.com/nodejs/node/commit/fc9ba17f6c)] - **(SEMVER-MINOR)** **test\_runner**: add `test:complete` event to reflect execution order (Moshe Atlow) [#51909](https://github.com/nodejs/node/pull/51909) +* \[[`d5ac979aeb`](https://github.com/nodejs/node/commit/d5ac979aeb)] - **test\_runner**: format coverage report for tap reporter (Pulkit Gupta) [#51119](https://github.com/nodejs/node/pull/51119) +* \[[`c925bc18dc`](https://github.com/nodejs/node/commit/c925bc18dc)] - **tools**: take co-authors into account in `find-inactive-collaborators` (Antoine du Hamel) [#52669](https://github.com/nodejs/node/pull/52669) +* \[[`1d37e772ec`](https://github.com/nodejs/node/commit/1d37e772ec)] - **tools**: fix invalid escape sequence in mkssldef (Michaël Zasso) [#52624](https://github.com/nodejs/node/pull/52624) +* \[[`5b22fc3a81`](https://github.com/nodejs/node/commit/5b22fc3a81)] - **tools**: update lint-md-dependencies to rollup\@4.15.0 (Node.js GitHub Bot) [#52617](https://github.com/nodejs/node/pull/52617) +* \[[`9cf47bb2f1`](https://github.com/nodejs/node/commit/9cf47bb2f1)] - **tools**: update lint-md-dependencies (Rich Trott) [#52581](https://github.com/nodejs/node/pull/52581) +* \[[`c0c60d13c0`](https://github.com/nodejs/node/commit/c0c60d13c0)] - **tools**: fix heading spaces for osx-entitlements.plist (Jackson Tian) [#52561](https://github.com/nodejs/node/pull/52561) +* \[[`7c349d7819`](https://github.com/nodejs/node/commit/7c349d7819)] - **tools**: update lint-md-dependencies to rollup\@4.14.2 vfile-reporter\@8.1.1 (Node.js GitHub Bot) [#52518](https://github.com/nodejs/node/pull/52518) +* \[[`b4d703297b`](https://github.com/nodejs/node/commit/b4d703297b)] - **tools**: use stylistic ESLint plugin for formatting (Michaël Zasso) [#50714](https://github.com/nodejs/node/pull/50714) +* \[[`c6813360c2`](https://github.com/nodejs/node/commit/c6813360c2)] - **tools**: update minimatch index path (Marco Ippolito) [#52523](https://github.com/nodejs/node/pull/52523) +* \[[`8464c0253c`](https://github.com/nodejs/node/commit/8464c0253c)] - **tools**: add a linter for README lists (Antoine du Hamel) [#52476](https://github.com/nodejs/node/pull/52476) +* \[[`55a3fbc842`](https://github.com/nodejs/node/commit/55a3fbc842)] - **tools**: change inactive limit to 12 months (Yagiz Nizipli) [#52425](https://github.com/nodejs/node/pull/52425) +* \[[`74a171f130`](https://github.com/nodejs/node/commit/74a171f130)] - **tools**: update stale bot messaging (Wes Todd) [#52423](https://github.com/nodejs/node/pull/52423) +* \[[`b2a3dcec2a`](https://github.com/nodejs/node/commit/b2a3dcec2a)] - **tools**: update lint-md-dependencies to rollup\@4.14.0 (Node.js GitHub Bot) [#52398](https://github.com/nodejs/node/pull/52398) +* \[[`f71a777e6e`](https://github.com/nodejs/node/commit/f71a777e6e)] - **tools**: update Ruff to v0.3.4 (Michaël Zasso) [#52302](https://github.com/nodejs/node/pull/52302) +* \[[`e3e0c68f8f`](https://github.com/nodejs/node/commit/e3e0c68f8f)] - **tools**: run test-ubsan on ubuntu-latest (Michaël Zasso) [#52375](https://github.com/nodejs/node/pull/52375) +* \[[`893b5aac31`](https://github.com/nodejs/node/commit/893b5aac31)] - **tools**: update lint-md-dependencies to rollup\@4.13.2 (Node.js GitHub Bot) [#52286](https://github.com/nodejs/node/pull/52286) +* \[[`049cca419e`](https://github.com/nodejs/node/commit/049cca419e)] - _**Revert**_ "**tools**: run `build-windows` workflow only on source changes" (Michaël Zasso) [#52320](https://github.com/nodejs/node/pull/52320) +* \[[`b3dfc62cee`](https://github.com/nodejs/node/commit/b3dfc62cee)] - **tools**: use Python 3.12 in GitHub Actions workflows (Michaël Zasso) [#52301](https://github.com/nodejs/node/pull/52301) +* \[[`c7238d0c04`](https://github.com/nodejs/node/commit/c7238d0c04)] - **tools**: allow local updates for llhttp (Paolo Insogna) [#52085](https://github.com/nodejs/node/pull/52085) +* \[[`c39f15cafd`](https://github.com/nodejs/node/commit/c39f15cafd)] - **tools**: install npm PowerShell scripts on Windows (Luke Karrys) [#52009](https://github.com/nodejs/node/pull/52009) +* \[[`b36fea064a`](https://github.com/nodejs/node/commit/b36fea064a)] - **tools**: update lint-md-dependencies to rollup\@4.13.0 (Node.js GitHub Bot) [#52122](https://github.com/nodejs/node/pull/52122) +* \[[`a5204eb915`](https://github.com/nodejs/node/commit/a5204eb915)] - **tools**: fix error reported by coverity in js2c.cc (Michael Dawson) [#52142](https://github.com/nodejs/node/pull/52142) +* \[[`cef4b7ef3d`](https://github.com/nodejs/node/commit/cef4b7ef3d)] - **tools**: sync ubsan workflow with asan (Michaël Zasso) [#52152](https://github.com/nodejs/node/pull/52152) +* \[[`d406976bbe`](https://github.com/nodejs/node/commit/d406976bbe)] - **tools**: update github\_reporter to 1.7.0 (Node.js GitHub Bot) [#52121](https://github.com/nodejs/node/pull/52121) +* \[[`fb100a2ac8`](https://github.com/nodejs/node/commit/fb100a2ac8)] - **tools**: remove gyp-next .github folder (Marco Ippolito) [#52064](https://github.com/nodejs/node/pull/52064) +* \[[`5f1e7a0de2`](https://github.com/nodejs/node/commit/5f1e7a0de2)] - **tools**: update gyp-next to 0.16.2 (Node.js GitHub Bot) [#52062](https://github.com/nodejs/node/pull/52062) +* \[[`1f1253446b`](https://github.com/nodejs/node/commit/1f1253446b)] - **tools**: install manpage to share/man for FreeBSD (Po-Chuan Hsieh) [#51791](https://github.com/nodejs/node/pull/51791) +* \[[`4b8b92fccc`](https://github.com/nodejs/node/commit/4b8b92fccc)] - **tools**: automate gyp-next update (Marco Ippolito) [#52014](https://github.com/nodejs/node/pull/52014) +* \[[`1ec9e58692`](https://github.com/nodejs/node/commit/1ec9e58692)] - **typings**: fix invalid JSDoc declarations (Yagiz Nizipli) [#52659](https://github.com/nodejs/node/pull/52659) +* \[[`2d6b19970b`](https://github.com/nodejs/node/commit/2d6b19970b)] - **(SEMVER-MINOR)** **util**: support array of formats in util.styleText (Marco Ippolito) [#52040](https://github.com/nodejs/node/pull/52040) +* \[[`d30cccdf8c`](https://github.com/nodejs/node/commit/d30cccdf8c)] - **(SEMVER-MINOR)** **v8**: implement v8.queryObjects() for memory leak regression testing (Joyee Cheung) [#51927](https://github.com/nodejs/node/pull/51927) +* \[[`7f3b7fdeff`](https://github.com/nodejs/node/commit/7f3b7fdeff)] - **watch**: fix some node argument not passed to watched process (Raz Luvaton) [#52358](https://github.com/nodejs/node/pull/52358) +* \[[`8ba6f9bc9a`](https://github.com/nodejs/node/commit/8ba6f9bc9a)] - **watch**: use internal addAbortListener (Chemi Atlow) [#52081](https://github.com/nodejs/node/pull/52081) +* \[[`5a922232da`](https://github.com/nodejs/node/commit/5a922232da)] - **watch**: mark as stable (Moshe Atlow) [#52074](https://github.com/nodejs/node/pull/52074) +* \[[`508e968a5f`](https://github.com/nodejs/node/commit/508e968a5f)] - **watch**: batch file restarts (Moshe Atlow) [#51992](https://github.com/nodejs/node/pull/51992) + ## 2024-04-10, Version 20.12.2 'Iron' (LTS), @RafaelGSS From 951af83033968328350adbcc173403d8f33fb791 Mon Sep 17 00:00:00 2001 From: Rafael Gonzaga Date: Tue, 7 May 2024 13:25:45 -0300 Subject: [PATCH 6/8] lib,src: remove --experimental-policy Signed-off-by: RafaelGSS PR-URL: https://github.com/nodejs/node/pull/52583 Refs: https://github.com/nodejs/node/issues/52575 Reviewed-By: Yagiz Nizipli Reviewed-By: Geoffrey Booth Reviewed-By: Benjamin Gruenbaum Reviewed-By: Ruben Bridgewater Reviewed-By: Marco Ippolito Reviewed-By: Moshe Atlow --- benchmark/policy/policy-startup.js | 51 -- doc/api/cli.md | 25 - doc/api/deprecations.md | 3 +- doc/api/errors.md | 157 ++-- doc/api/policy.md | 11 - doc/node.1 | 6 - lib/internal/errors.js | 35 - lib/internal/main/worker_thread.js | 5 - lib/internal/modules/cjs/loader.js | 62 +- lib/internal/modules/esm/load.js | 10 - lib/internal/modules/esm/resolve.js | 50 +- lib/internal/modules/helpers.js | 63 +- lib/internal/modules/package_json_reader.js | 38 +- lib/internal/modules/run_main.js | 14 +- lib/internal/policy/manifest.js | 751 ------------------ lib/internal/policy/sri.js | 73 -- lib/internal/process/policy.js | 71 -- lib/internal/process/pre_execution.js | 59 +- lib/internal/worker.js | 7 - src/env.cc | 6 - src/env_properties.h | 3 +- src/node_builtins.cc | 1 - src/node_modules.cc | 14 +- src/node_options.cc | 22 - src/node_options.h | 3 - test/benchmark/test-benchmark-policy.js | 9 - .../policy-manifest/createRequire-bypass.js | 2 - .../policy-manifest/invalid-module.js | 0 test/fixtures/policy-manifest/invalid.json | 9 - .../main-constructor-bypass.js | 2 - .../main-constructor-extensions-bypass.js | 2 - .../policy-manifest/main-module-bypass.js | 1 - .../main-module-proto-bypass.js | 1 - .../policy-manifest/manifest-impersonate.json | 13 - .../module-constructor-bypass.js | 1 - .../object-define-property-bypass.js | 19 - .../policy-manifest/onerror-exit.json | 9 - .../onerror-resource-exit.json | 17 - test/fixtures/policy-manifest/valid-module.js | 0 test/fixtures/policy/bad-main.mjs | 1 - test/fixtures/policy/canonicalize.mjs | 5 - .../crypto-default-encoding/.gitattributes | 1 - .../policy/crypto-default-encoding/dep.js | 3 - .../policy/crypto-default-encoding/parent.js | 4 - .../crypto-default-encoding/policy.json | 14 - .../crypto-hash-tampering/.gitattributes | 1 - .../policy/crypto-hash-tampering/main.js | 8 - .../policy/crypto-hash-tampering/policy.json | 15 - .../policy/crypto-hash-tampering/protected.js | 1 - test/fixtures/policy/dep-policy.json | 7 - test/fixtures/policy/dep.js | 2 - .../dependencies-empty-policy.json | 11 - .../dependencies-missing-export-policy.json | 11 - ...endencies-missing-policy-default-true.json | 13 - .../dependencies-missing-policy.json | 10 - .../dependencies-redirect-builtin-policy.json | 10 - .../dependencies-redirect-policy.json | 13 - ...ncies-redirect-unknown-builtin-policy.json | 10 - ...endencies-scopes-and-resources-policy.json | 14 - .../dependencies-scopes-policy.json | 8 - ...ependencies-scopes-relative-specifier.json | 12 - .../dependencies-wildcard-policy.json | 11 - test/fixtures/policy/main.mjs | 2 - test/fixtures/policy/multi-deps.js | 3 - test/fixtures/policy/parent.js | 3 - test/fixtures/policy/process-binding/app.js | 10 - .../policy/process-binding/policy.json | 10 - test/node-api/test_policy/binding.c | 17 - test/node-api/test_policy/binding.gyp | 8 - test/node-api/test_policy/test_policy.js | 59 -- test/parallel/test-compile-cache-policy.js | 38 - .../test-policy-crypto-default-encoding.js | 34 - .../test-policy-crypto-hash-tampering.js | 21 - test/parallel/test-policy-dependencies.js | 146 ---- .../test-policy-dependency-conditions.js | 123 --- test/parallel/test-policy-integrity-flag.js | 66 -- test/parallel/test-policy-manifest.js | 157 ---- test/parallel/test-policy-parse-integrity.js | 111 --- test/parallel/test-policy-process-binding.js | 28 - .../test-policy-scopes-dependencies.js | 342 -------- test/parallel/test-policy-scopes-integrity.js | 316 -------- test/parallel/test-policy-scopes.js | 40 - test/pummel/test-policy-integrity-dep.js | 365 --------- .../test-policy-integrity-parent-commonjs.js | 352 -------- .../test-policy-integrity-parent-module.js | 352 -------- ...policy-integrity-parent-no-package-json.js | 324 -------- .../test-policy-integrity-worker-commonjs.js | 375 --------- .../test-policy-integrity-worker-module.js | 373 --------- ...policy-integrity-worker-no-package-json.js | 345 -------- typings/internalBinding/modules.d.ts | 6 +- 90 files changed, 116 insertions(+), 5720 deletions(-) delete mode 100644 benchmark/policy/policy-startup.js delete mode 100644 doc/api/policy.md delete mode 100644 lib/internal/policy/manifest.js delete mode 100644 lib/internal/policy/sri.js delete mode 100644 lib/internal/process/policy.js delete mode 100644 test/benchmark/test-benchmark-policy.js delete mode 100644 test/fixtures/policy-manifest/createRequire-bypass.js delete mode 100644 test/fixtures/policy-manifest/invalid-module.js delete mode 100644 test/fixtures/policy-manifest/invalid.json delete mode 100644 test/fixtures/policy-manifest/main-constructor-bypass.js delete mode 100644 test/fixtures/policy-manifest/main-constructor-extensions-bypass.js delete mode 100644 test/fixtures/policy-manifest/main-module-bypass.js delete mode 100644 test/fixtures/policy-manifest/main-module-proto-bypass.js delete mode 100644 test/fixtures/policy-manifest/manifest-impersonate.json delete mode 100644 test/fixtures/policy-manifest/module-constructor-bypass.js delete mode 100644 test/fixtures/policy-manifest/object-define-property-bypass.js delete mode 100644 test/fixtures/policy-manifest/onerror-exit.json delete mode 100644 test/fixtures/policy-manifest/onerror-resource-exit.json delete mode 100644 test/fixtures/policy-manifest/valid-module.js delete mode 100644 test/fixtures/policy/bad-main.mjs delete mode 100644 test/fixtures/policy/canonicalize.mjs delete mode 100644 test/fixtures/policy/crypto-default-encoding/.gitattributes delete mode 100644 test/fixtures/policy/crypto-default-encoding/dep.js delete mode 100644 test/fixtures/policy/crypto-default-encoding/parent.js delete mode 100644 test/fixtures/policy/crypto-default-encoding/policy.json delete mode 100644 test/fixtures/policy/crypto-hash-tampering/.gitattributes delete mode 100644 test/fixtures/policy/crypto-hash-tampering/main.js delete mode 100644 test/fixtures/policy/crypto-hash-tampering/policy.json delete mode 100644 test/fixtures/policy/crypto-hash-tampering/protected.js delete mode 100644 test/fixtures/policy/dep-policy.json delete mode 100644 test/fixtures/policy/dep.js delete mode 100644 test/fixtures/policy/dependencies/dependencies-empty-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-missing-export-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-missing-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-redirect-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-scopes-policy.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json delete mode 100644 test/fixtures/policy/dependencies/dependencies-wildcard-policy.json delete mode 100644 test/fixtures/policy/main.mjs delete mode 100644 test/fixtures/policy/multi-deps.js delete mode 100644 test/fixtures/policy/parent.js delete mode 100644 test/fixtures/policy/process-binding/app.js delete mode 100644 test/fixtures/policy/process-binding/policy.json delete mode 100644 test/node-api/test_policy/binding.c delete mode 100644 test/node-api/test_policy/binding.gyp delete mode 100644 test/node-api/test_policy/test_policy.js delete mode 100644 test/parallel/test-compile-cache-policy.js delete mode 100644 test/parallel/test-policy-crypto-default-encoding.js delete mode 100644 test/parallel/test-policy-crypto-hash-tampering.js delete mode 100644 test/parallel/test-policy-dependencies.js delete mode 100644 test/parallel/test-policy-dependency-conditions.js delete mode 100644 test/parallel/test-policy-integrity-flag.js delete mode 100644 test/parallel/test-policy-manifest.js delete mode 100644 test/parallel/test-policy-parse-integrity.js delete mode 100644 test/parallel/test-policy-process-binding.js delete mode 100644 test/parallel/test-policy-scopes-dependencies.js delete mode 100644 test/parallel/test-policy-scopes-integrity.js delete mode 100644 test/parallel/test-policy-scopes.js delete mode 100644 test/pummel/test-policy-integrity-dep.js delete mode 100644 test/pummel/test-policy-integrity-parent-commonjs.js delete mode 100644 test/pummel/test-policy-integrity-parent-module.js delete mode 100644 test/pummel/test-policy-integrity-parent-no-package-json.js delete mode 100644 test/pummel/test-policy-integrity-worker-commonjs.js delete mode 100644 test/pummel/test-policy-integrity-worker-module.js delete mode 100644 test/pummel/test-policy-integrity-worker-no-package-json.js diff --git a/benchmark/policy/policy-startup.js b/benchmark/policy/policy-startup.js deleted file mode 100644 index 9ee84fff4d0452..00000000000000 --- a/benchmark/policy/policy-startup.js +++ /dev/null @@ -1,51 +0,0 @@ -// Tests the impact on eager operations required for policies affecting -// general startup, does not test lazy operations -'use strict'; -const common = require('../common.js'); - -const configs = { - n: [1024], -}; - -const options = { - flags: ['--expose-internals'], -}; - -const bench = common.createBenchmark(main, configs, options); - -function main(conf) { - const hash = (str, algo) => { - const hash = require('crypto').createHash(algo); - return hash.update(str).digest('base64'); - }; - const resources = Object.fromEntries( - // Simulate graph of 1k modules - Array.from({ length: 1024 }, (_, i) => { - return [`./_${i}`, { - integrity: `sha256-${hash(`// ./_${i}`, 'sha256')}`, - dependencies: Object.fromEntries(Array.from({ - // Average 3 deps per 4 modules - length: Math.floor((i % 4) / 2), - }, (_, ii) => { - return [`_${ii}`, `./_${i - ii}`]; - })), - }]; - }), - ); - const json = JSON.parse(JSON.stringify({ resources }), (_, o) => { - if (o && typeof o === 'object') { - Reflect.setPrototypeOf(o, null); - Object.freeze(o); - } - return o; - }); - const { Manifest } = require('internal/policy/manifest'); - - bench.start(); - - for (let i = 0; i < conf.n; i++) { - new Manifest(json, 'file://benchmark/policy-relative'); - } - - bench.end(conf.n); -} diff --git a/doc/api/cli.md b/doc/api/cli.md index 363c42f030085b..dbfa6045567a77 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -881,16 +881,6 @@ following permissions are restricted: * Child Process - manageable through [`--allow-child-process`][] flag * Worker Threads - manageable through [`--allow-worker`][] flag -### `--experimental-policy` - - - -> Stability: 0 - Deprecated: Will be removed shortly. - -Use the specified file as a security policy. - ### `--experimental-require-module` - -> Stability: 0 - Deprecated: Will be removed shortly. - -Instructs Node.js to error prior to running any code if the policy does not have -the specified integrity. It expects a [Subresource Integrity][] string as a -parameter. - ### `--preserve-symlinks` + +An attempt was made to load a resource, but the resource did not match the +integrity defined by the policy manifest. See the documentation for policy +manifests for more information. + + + +### `ERR_MANIFEST_DEPENDENCY_MISSING` + + + +An attempt was made to load a resource, but the resource was not listed as a +dependency from the location that attempted to load it. See the documentation +for policy manifests for more information. + + + +### `ERR_MANIFEST_INTEGRITY_MISMATCH` + + + +An attempt was made to load a policy manifest, but the manifest had multiple +entries for a resource which did not match each other. Update the manifest +entries to match in order to resolve this error. See the documentation for +policy manifests for more information. + + + +### `ERR_MANIFEST_INVALID_RESOURCE_FIELD` + + + +A policy manifest resource had an invalid value for one of its fields. Update +the manifest entry to match in order to resolve this error. See the +documentation for policy manifests for more information. + + + +### `ERR_MANIFEST_INVALID_SPECIFIER` + + + +A policy manifest resource had an invalid value for one of its dependency +mappings. Update the manifest entry to match to resolve this error. See the +documentation for policy manifests for more information. + + + +### `ERR_MANIFEST_PARSE_POLICY` + + + +An attempt was made to load a policy manifest, but the manifest was unable to +be parsed. See the documentation for policy manifests for more information. + + + +### `ERR_MANIFEST_TDZ` + + + +An attempt was made to read from a policy manifest, but the manifest +initialization has not yet taken place. This is likely a bug in Node.js. + + + +### `ERR_MANIFEST_UNKNOWN_ONERROR` + + + +A policy manifest was loaded, but had an unknown value for its "onerror" +behavior. See the documentation for policy manifests for more information. + ### `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST` @@ -4016,7 +4048,6 @@ An error occurred trying to allocate memory. This should never happen. [domains]: domain.md [event emitter-based]: events.md#class-eventemitter [file descriptors]: https://en.wikipedia.org/wiki/File_descriptor -[policy]: permissions.md#policies [relative URL]: https://url.spec.whatwg.org/#relative-url-string [self-reference a package using its name]: packages.md#self-referencing-a-package-using-its-name [special scheme]: https://url.spec.whatwg.org/#special-scheme diff --git a/doc/api/policy.md b/doc/api/policy.md deleted file mode 100644 index c3a974a2d81b2b..00000000000000 --- a/doc/api/policy.md +++ /dev/null @@ -1,11 +0,0 @@ -# Policies - - - - - -> Stability: 1 - Experimental - -The former Policies documentation is now at [Permissions documentation][]. - -[Permissions documentation]: permissions.md#policies diff --git a/doc/node.1 b/doc/node.1 index c058694ac8f7e6..fcaf971f240d72 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -174,9 +174,6 @@ Enable experimental support for loading modules using `import` over `https:`. .It Fl -experimental-permission Enable the experimental permission model. . -.It Fl -experimental-policy -Use the specified file as a security policy. -. .It Fl -experimental-shadow-realm Use this flag to enable ShadowRealm support. . @@ -334,9 +331,6 @@ Among other uses, this can be used to enable FIPS-compliant crypto if Node.js is .It Fl -pending-deprecation Emit pending deprecation warnings. . -.It Fl -policy-integrity Ns = Ns Ar sri -Instructs Node.js to error prior to running any code if the policy does not have the specified integrity. It expects a Subresource Integrity string as a parameter. -. .It Fl -preserve-symlinks Instructs the module loader to preserve symbolic links when resolving and caching modules other than the main module. . diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 530afc07f7bb8a..5b8009f802e534 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -12,7 +12,6 @@ const { AggregateError, - ArrayFrom, ArrayIsArray, ArrayPrototypeFilter, ArrayPrototypeIncludes, @@ -1555,40 +1554,6 @@ E( ' `shortCircuit: true` in the hook\'s return.', Error, ); -E('ERR_MANIFEST_ASSERT_INTEGRITY', - (moduleURL, realIntegrities) => { - let msg = `The content of "${ - moduleURL - }" does not match the expected integrity.`; - if (realIntegrities.size) { - const sri = ArrayPrototypeJoin( - ArrayFrom(realIntegrities.entries(), - ({ 0: alg, 1: dgs }) => `${alg}-${dgs}`), - ' ', - ); - msg += ` Integrities found are: ${sri}`; - } else { - msg += ' The resource was not found in the policy.'; - } - return msg; - }, Error); -E('ERR_MANIFEST_DEPENDENCY_MISSING', - 'Manifest resource %s does not list %s as a dependency specifier for ' + - 'conditions: %s', - Error); -E('ERR_MANIFEST_INTEGRITY_MISMATCH', - 'Manifest resource %s has multiple entries but integrity lists do not match', - SyntaxError); -E('ERR_MANIFEST_INVALID_RESOURCE_FIELD', - 'Manifest resource %s has invalid property value for %s', - TypeError); -E('ERR_MANIFEST_INVALID_SPECIFIER', - 'Manifest resource %s has invalid dependency mapping %s', - TypeError); -E('ERR_MANIFEST_TDZ', 'Manifest initialization has not yet run', Error); -E('ERR_MANIFEST_UNKNOWN_ONERROR', - 'Manifest specified unknown error behavior "%s".', - SyntaxError); E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented', Error); E('ERR_MISSING_ARGS', (...args) => { diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index c0b151a1eac9de..aa329b9fe04f15 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -94,8 +94,6 @@ port.on('message', (message) => { environmentData, filename, hasStdin, - manifestSrc, - manifestURL, publicPort, workerData, } = message; @@ -130,9 +128,6 @@ port.on('message', (message) => { workerIo.sharedCwdCounter = cwdCounter; } - if (manifestSrc) { - require('internal/process/policy').setup(manifestSrc, manifestURL); - } const isLoaderWorker = doEval === 'internal' && filename === require('internal/modules/esm/utils').loaderWorkerId; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index ab373dcb5032a2..69041d154c6fa2 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -140,11 +140,6 @@ const fs = require('fs'); const path = require('path'); const { internalModuleStat } = internalBinding('fs'); const { safeGetenv } = internalBinding('credentials'); -const { - privateSymbols: { - require_private_symbol, - }, -} = internalBinding('util'); const { getCjsConditions, initializeCjsConditions, @@ -156,9 +151,6 @@ const { } = require('internal/modules/helpers'); const packageJsonReader = require('internal/modules/package_json_reader'); const { getOptionValue, getEmbedderOptions } = require('internal/options'); -const policy = getLazy( - () => (getOptionValue('--experimental-policy') ? require('internal/process/policy') : null), -); const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES); const permission = require('internal/process/permission'); @@ -197,25 +189,6 @@ let requireDepth = 0; let isPreloading = false; let statCache = null; -/** - * Our internal implementation of `require`. - * @param {Module} module Parent module of what is being required - * @param {string} id Specifier of the child module being imported - */ -function internalRequire(module, id) { - validateString(id, 'id'); - if (id === '') { - throw new ERR_INVALID_ARG_VALUE('id', id, - 'must be a non-empty string'); - } - requireDepth++; - try { - return Module._load(id, module, /* isMain */ false); - } finally { - requireDepth--; - } -} - /** * Get a path's properties, using an in-memory cache to minimize lookups. * @param {string} filename Absolute path to the file @@ -294,17 +267,6 @@ function Module(id = '', parent) { this.filename = null; this.loaded = false; this.children = []; - let redirects; - const manifest = policy()?.manifest; - if (manifest) { - const moduleURL = pathToFileURL(id); - redirects = manifest.getDependencyMapper(moduleURL); - // TODO(rafaelgss): remove the necessity of this branch - setOwnProperty(this, 'require', makeRequireFunction(this, redirects)); - // eslint-disable-next-line no-proto - setOwnProperty(this.__proto__, 'require', makeRequireFunction(this, redirects)); - } - this[require_private_symbol] = internalRequire; } /** @type {Record} */ @@ -1295,7 +1257,6 @@ Module.prototype.load = function(filename) { /** * Loads a module at the given file path. Returns that module's `exports` property. - * Note: when using the experimental policy mechanism this function is overridden. * @param {string} id * @throws {ERR_INVALID_ARG_TYPE} When `id` is not a string */ @@ -1411,14 +1372,7 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache, format) { * @param {'module'|'commonjs'|undefined} format Intended format of the module. */ Module.prototype._compile = function(content, filename, format) { - let moduleURL; let redirects; - const manifest = policy()?.manifest; - if (manifest) { - moduleURL = pathToFileURL(filename); - redirects = manifest.getDependencyMapper(moduleURL); - manifest.assertIntegrity(moduleURL, content); - } let compiledWrapper; if (format !== 'module') { @@ -1572,12 +1526,6 @@ Module._extensions['.js'] = function(module, filename) { Module._extensions['.json'] = function(module, filename) { const content = fs.readFileSync(filename, 'utf8'); - const manifest = policy()?.manifest; - if (manifest) { - const moduleURL = pathToFileURL(filename); - manifest.assertIntegrity(moduleURL, content); - } - try { setOwnProperty(module, 'exports', JSONParse(stripBOM(content))); } catch (err) { @@ -1592,12 +1540,6 @@ Module._extensions['.json'] = function(module, filename) { * @param {string} filename The file path of the module */ Module._extensions['.node'] = function(module, filename) { - const manifest = policy()?.manifest; - if (manifest) { - const content = fs.readFileSync(filename); - const moduleURL = pathToFileURL(filename); - manifest.assertIntegrity(moduleURL, content); - } // Be aware this doesn't use `content` return process.dlopen(module, path.toNamespacedPath(filename)); }; @@ -1708,7 +1650,7 @@ Module._preloadModules = function(requests) { } } for (let n = 0; n < requests.length; n++) { - internalRequire(parent, requests[n]); + parent.require(requests[n]); } isPreloading = false; }; @@ -1728,7 +1670,7 @@ Module.syncBuiltinESMExports = function syncBuiltinESMExports() { ObjectDefineProperty(Module.prototype, 'constructor', { __proto__: null, get: function() { - return policy() ? undefined : Module; + return Module; }, configurable: false, enumerable: false, diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 7b77af35a1dfeb..1ca6495c84a029 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -12,10 +12,6 @@ const { validateAttributes, emitImportAssertionWarning } = require('internal/mod const { getOptionValue } = require('internal/options'); const { readFileSync } = require('fs'); -// Do not eagerly grab .manifest, it may be in TDZ -const policy = getOptionValue('--experimental-policy') ? - require('internal/process/policy') : - null; const experimentalNetworkImports = getOptionValue('--experimental-network-imports'); const defaultType = @@ -66,9 +62,6 @@ async function getSource(url, context) { } throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url, supportedSchemes); } - if (policy?.manifest) { - policy.manifest.assertIntegrity(href, source); - } return { __proto__: null, responseURL, source }; } @@ -94,9 +87,6 @@ function getSourceSync(url, context) { const supportedSchemes = ['file', 'data']; throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url, supportedSchemes); } - if (policy?.manifest) { - policy.manifest.assertIntegrity(url, source); - } return { __proto__: null, responseURL, source }; } diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index bbbaed87479289..99d91ed794ea4c 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -27,9 +27,6 @@ const { BuiltinModule } = require('internal/bootstrap/realm'); const { realpathSync } = require('fs'); const { getOptionValue } = require('internal/options'); // Do not eagerly grab .manifest, it may be in TDZ -const policy = getOptionValue('--experimental-policy') ? - require('internal/process/policy') : - null; const { sep, posix: { relative: relativePosixPath }, toNamespacedPath, resolve } = require('path'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); @@ -46,7 +43,6 @@ const { ERR_INVALID_MODULE_SPECIFIER, ERR_INVALID_PACKAGE_CONFIG, ERR_INVALID_PACKAGE_TARGET, - ERR_MANIFEST_DEPENDENCY_MISSING, ERR_MODULE_NOT_FOUND, ERR_PACKAGE_IMPORT_NOT_DEFINED, ERR_PACKAGE_PATH_NOT_EXPORTED, @@ -1045,8 +1041,7 @@ function throwIfInvalidParentURL(parentURL) { /** * Resolves the given specifier using the provided context, which includes the parent URL and conditions. - * Throws an error if the parent URL is invalid or if the resolution is disallowed by the policy manifest. - * Otherwise, attempts to resolve the specifier and returns the resulting URL and format. + * Attempts to resolve the specifier and returns the resulting URL and format. * @param {string} specifier - The specifier to resolve. * @param {object} [context={}] - The context object containing the parent URL and conditions. * @param {string} [context.parentURL] - The URL of the parent module. @@ -1055,30 +1050,6 @@ function throwIfInvalidParentURL(parentURL) { function defaultResolve(specifier, context = {}) { let { parentURL, conditions } = context; throwIfInvalidParentURL(parentURL); - if (parentURL && policy?.manifest) { - const redirects = policy.manifest.getDependencyMapper(parentURL); - if (redirects) { - const { resolve, reaction } = redirects; - const destination = resolve(specifier, new SafeSet(conditions)); - let missing = true; - if (destination === true) { - missing = false; - } else if (destination) { - const href = destination.href; - return { __proto__: null, url: href }; - } - if (missing) { - // Prevent network requests from firing if resolution would be banned. - // Network requests can extract data by doing things like putting - // secrets in query params - reaction(new ERR_MANIFEST_DEPENDENCY_MISSING( - parentURL, - specifier, - ArrayPrototypeJoin([...conditions], ', ')), - ); - } - } - } let parsedParentURL; if (parentURL) { @@ -1207,22 +1178,3 @@ module.exports = { const { defaultGetFormatWithoutErrors, } = require('internal/modules/esm/get_format'); - -if (policy) { - const $defaultResolve = defaultResolve; - module.exports.defaultResolve = function defaultResolve( - specifier, - context, - ) { - const ret = $defaultResolve(specifier, context); - // This is a preflight check to avoid data exfiltration by query params etc. - policy.manifest.mightAllow(ret.url, () => - new ERR_MANIFEST_DEPENDENCY_MISSING( - context.parentURL, - specifier, - context.conditions, - ), - ); - return ret; - }; -} diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index f7ad4cd1be6c8d..ba13aa64bfaa24 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -2,7 +2,6 @@ const { ArrayPrototypeForEach, - ArrayPrototypeJoin, ObjectDefineProperty, ObjectPrototypeHasOwnProperty, SafeMap, @@ -14,8 +13,6 @@ const { } = primordials; const { ERR_INVALID_ARG_TYPE, - ERR_MANIFEST_DEPENDENCY_MISSING, - ERR_UNKNOWN_BUILTIN_MODULE, } = require('internal/errors').codes; const { BuiltinModule } = require('internal/bootstrap/realm'); @@ -30,11 +27,6 @@ const { getOptionValue } = require('internal/options'); const { setOwnProperty } = require('internal/util'); const { inspect } = require('internal/util/inspect'); -const { - privateSymbols: { - require_private_symbol, - }, -} = internalBinding('util'); const { canParse: URLCanParse } = internalBinding('url'); let debug = require('internal/util/debuglog').debuglog('module', (fn) => { @@ -115,69 +107,20 @@ function lazyModule() { return $Module; } -/** - * Invoke with `makeRequireFunction(module)` where `module` is the `Module` object to use as the context for the - * `require()` function. - * Use redirects to set up a mapping from a policy and restrict dependencies. - */ -const urlToFileCache = new SafeMap(); /** * Create the module-scoped `require` function to pass into CommonJS modules. * @param {Module} mod - The module to create the `require` function for. - * @param {ReturnType} redirects * @typedef {(specifier: string) => unknown} RequireFunction */ -function makeRequireFunction(mod, redirects) { +function makeRequireFunction(mod) { // lazy due to cycle const Module = lazyModule(); if (mod instanceof Module !== true) { throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod); } - /** @type {RequireFunction} */ - let require; - if (redirects) { - const id = mod.filename || mod.id; - const conditions = getCjsConditions(); - const { resolve, reaction } = redirects; - require = function require(specifier) { - let missing = true; - const destination = resolve(specifier, conditions); - if (destination === true) { - missing = false; - } else if (destination) { - const { href, protocol } = destination; - if (protocol === 'node:') { - const specifier = destination.pathname; - - if (BuiltinModule.canBeRequiredByUsers(specifier)) { - const mod = loadBuiltinModule(specifier, href); - return mod.exports; - } - throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier); - } else if (protocol === 'file:') { - let filepath = urlToFileCache.get(href); - if (!filepath) { - filepath = fileURLToPath(destination); - urlToFileCache.set(href, filepath); - } - return mod[require_private_symbol](mod, filepath); - } - } - if (missing) { - reaction(new ERR_MANIFEST_DEPENDENCY_MISSING( - id, - specifier, - ArrayPrototypeJoin([...conditions], ', '), - )); - } - return mod[require_private_symbol](mod, specifier); - }; - } else { - require = function require(path) { - // When no policy manifest, the original prototype.require is sustained - return mod.require(path); - }; + function require(path) { + return mod.require(path); } /** diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 3df2b7bc6a05b3..9a9dcebb799c00 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -10,34 +10,13 @@ const { const modulesBinding = internalBinding('modules'); const { resolve, sep } = require('path'); const { kEmptyObject } = require('internal/util'); -const { pathToFileURL } = require('internal/url'); - -let manifest; - -/** - * @param {string} jsonPath - * @param {string} value The integrity value to check against. - */ -function checkPackageJSONIntegrity(jsonPath, value) { - if (manifest === undefined) { - const { getOptionValue } = require('internal/options'); - manifest = getOptionValue('--experimental-policy') ? - require('internal/process/policy').manifest : - null; - } - if (manifest !== null) { - const jsonURL = pathToFileURL(jsonPath); - manifest.assertIntegrity(jsonURL, value); - } -} /** * @param {string} path * @param {import('typings/internalBinding/modules').SerializedPackageConfig} contents - * @param {boolean} [checkIntegrity=false] Whether to check the integrity of the package.json file. * @returns {import('typings/internalBinding/modules').PackageConfig} */ -function deserializePackageJSON(path, contents, checkIntegrity = false) { +function deserializePackageJSON(path, contents) { if (contents === undefined) { return { __proto__: null, @@ -54,8 +33,7 @@ function deserializePackageJSON(path, contents, checkIntegrity = false) { 2: type, 3: plainImports, 4: plainExports, - 5: manifest, - 6: optionalFilePath, + 5: optionalFilePath, } = contents; // This is required to be used in getPackageScopeConfig. @@ -63,11 +41,6 @@ function deserializePackageJSON(path, contents, checkIntegrity = false) { pjsonPath = optionalFilePath; } - if (checkIntegrity) { - // parsed[5] is only available when experimental policy is enabled. - checkPackageJSONIntegrity(pjsonPath, manifest); - } - // The imports and exports fields can be either undefined or a string. // - If it's a string, it's either plain string or a stringified JSON string. // - If it's a stringified JSON string, it starts with either '[' or '{'. @@ -114,7 +87,7 @@ function read(jsonPath, { base, specifier, isESM } = kEmptyObject) { specifier == null ? undefined : `${specifier}`, ); - return deserializePackageJSON(jsonPath, parsed, true /* checkIntegrity */); + return deserializePackageJSON(jsonPath, parsed); } /** @@ -141,7 +114,7 @@ function getNearestParentPackageJSON(checkPath) { return undefined; } - const data = deserializePackageJSON(checkPath, result, true /* checkIntegrity */); + const data = deserializePackageJSON(checkPath, result); // Path should be the root folder of the matched package.json // For example for ~/path/package.json, it should be ~/path @@ -159,7 +132,7 @@ function getPackageScopeConfig(resolved) { const result = modulesBinding.getPackageScopeConfig(`${resolved}`); if (ArrayIsArray(result)) { - return deserializePackageJSON(`${resolved}`, result, false /* checkIntegrity */); + return deserializePackageJSON(`${resolved}`, result); } // This means that the response is a string @@ -182,7 +155,6 @@ function getPackageType(url) { } module.exports = { - checkPackageJSONIntegrity, read, readPackage, getNearestParentPackageJSON, diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index b68825950a8f75..7b26b67bfd4cd6 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -7,7 +7,6 @@ const { const { getNearestParentPackageJSONType } = internalBinding('modules'); const { getOptionValue } = require('internal/options'); -const { checkPackageJSONIntegrity } = require('internal/modules/package_json_reader'); const path = require('path'); const { pathToFileURL } = require('internal/url'); const { kEmptyObject, getCWDURL } = require('internal/util'); @@ -82,22 +81,13 @@ function shouldUseESMLoader(mainPath) { if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } if (!mainPath || StringPrototypeEndsWith(mainPath, '.cjs')) { return false; } - const response = getNearestParentPackageJSONType(mainPath); + const type = getNearestParentPackageJSONType(mainPath); // No package.json or no `type` field. - if (response === undefined || response[0] === 'none') { + if (type === undefined || type === 'none') { return false; } - // TODO(@anonrig): Do not return filePath and rawContent if experimental-policy is not used. - const { - 0: type, - 1: filePath, - 2: rawContent, - } = response; - - checkPackageJSONIntegrity(filePath, rawContent); - return type === 'module'; } diff --git a/lib/internal/policy/manifest.js b/lib/internal/policy/manifest.js deleted file mode 100644 index 61d32d1185d7e7..00000000000000 --- a/lib/internal/policy/manifest.js +++ /dev/null @@ -1,751 +0,0 @@ -'use strict'; - -// #region imports -const { - ArrayIsArray, - ArrayPrototypeSort, - ObjectEntries, - ObjectFreeze, - ObjectKeys, - ObjectSetPrototypeOf, - RegExpPrototypeExec, - RegExpPrototypeSymbolReplace, - SafeMap, - SafeSet, - StringPrototypeEndsWith, - StringPrototypeStartsWith, - Symbol, -} = primordials; -const { - ERR_MANIFEST_ASSERT_INTEGRITY, - ERR_MANIFEST_INVALID_RESOURCE_FIELD, - ERR_MANIFEST_INVALID_SPECIFIER, - ERR_MANIFEST_UNKNOWN_ONERROR, -} = require('internal/errors').codes; -let debug = require('internal/util/debuglog').debuglog('policy', (fn) => { - debug = fn; -}); -const SRI = require('internal/policy/sri'); -const { URL } = require('internal/url'); -const { internalVerifyIntegrity } = internalBinding('crypto'); -const kRelativeURLStringPattern = /^\.{0,2}\//; -const { getOptionValue } = require('internal/options'); -const shouldAbortOnUncaughtException = getOptionValue( - '--abort-on-uncaught-exception', -); -const { exitCodes: { kGenericUserError } } = internalBinding('errors'); - -const { abort, exit, _rawDebug } = process; -// #endregion - -// #region constants -// From https://url.spec.whatwg.org/#special-scheme -const kSpecialSchemes = new SafeSet([ - 'file:', - 'ftp:', - 'http:', - 'https:', - 'ws:', - 'wss:', -]); - -/** - * @type {symbol} - */ -const kCascade = Symbol('cascade'); -/** - * @type {symbol} - */ -const kFallThrough = Symbol('fall through'); - -function REACTION_THROW(error) { - throw error; -} - -function REACTION_EXIT(error) { - REACTION_LOG(error); - if (shouldAbortOnUncaughtException) { - abort(); - } - exit(kGenericUserError); -} - -function REACTION_LOG(error) { - _rawDebug(error.stack); -} - -// #endregion - -// #region DependencyMapperInstance -class DependencyMapperInstance { - /** - * @type {string} - */ - href; - /** - * @type {DependencyMap | undefined} - */ - #dependencies; - /** - * @type {PatternDependencyMap | undefined} - */ - #patternDependencies; - /** - * @type {DependencyMapperInstance | null | undefined} - */ - #parentDependencyMapper; - /** - * @type {boolean} - */ - #normalized = false; - /** - * @type {boolean} - */ - cascade; - /** - * @type {boolean} - */ - allowSameHREFScope; - /** - * @param {string} parentHREF - * @param {DependencyMap | undefined} dependencies - * @param {boolean} cascade - * @param {boolean} allowSameHREFScope - */ - constructor( - parentHREF, - dependencies, - cascade = false, - allowSameHREFScope = false) { - this.href = parentHREF; - if (dependencies === kFallThrough || - dependencies === undefined || - dependencies === null) { - this.#dependencies = dependencies; - this.#patternDependencies = undefined; - } else { - const patterns = []; - const keys = ObjectKeys(dependencies); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (StringPrototypeEndsWith(key, '*')) { - const target = RegExpPrototypeExec(/^([^*]*)\*([^*]*)$/); - if (!target) { - throw new ERR_MANIFEST_INVALID_SPECIFIER( - this.href, - `${target}, pattern needs to have a single trailing "*" in target`, - ); - } - const prefix = target[1]; - const suffix = target[2]; - patterns.push([ - target.slice(0, -1), - [prefix, suffix], - ]); - } - } - ArrayPrototypeSort(patterns, (a, b) => { - return a[0] < b[0] ? -1 : 1; - }); - this.#dependencies = dependencies; - this.#patternDependencies = patterns; - } - this.cascade = cascade; - this.allowSameHREFScope = allowSameHREFScope; - ObjectFreeze(this); - } - /** - * - * @param {string} normalizedSpecifier - * @param {Set} conditions - * @param {Manifest} manifest - * @returns {URL | typeof kFallThrough | null} - */ - _resolveAlreadyNormalized(normalizedSpecifier, conditions, manifest) { - let dependencies = this.#dependencies; - debug(this.href, 'resolving', normalizedSpecifier); - if (dependencies === kFallThrough) return true; - if (dependencies !== undefined && typeof dependencies === 'object') { - const normalized = this.#normalized; - if (normalized !== true) { - /** - * @type {Record} - */ - const normalizedDependencyMap = { __proto__: null }; - for (let specifier in dependencies) { - const target = dependencies[specifier]; - specifier = canonicalizeSpecifier(specifier, manifest.href); - normalizedDependencyMap[specifier] = target; - } - ObjectFreeze(normalizedDependencyMap); - dependencies = normalizedDependencyMap; - this.#dependencies = normalizedDependencyMap; - this.#normalized = true; - } - debug(dependencies); - if (normalizedSpecifier in dependencies === true) { - const to = searchDependencies( - this.href, - dependencies[normalizedSpecifier], - conditions, - ); - debug({ to }); - if (to === true) { - return true; - } - let ret; - if (parsedURLs && parsedURLs.has(to)) { - ret = parsedURLs.get(to); - } else if (RegExpPrototypeExec(kRelativeURLStringPattern, to) !== null) { - ret = resolve(to, manifest.href); - } else { - ret = resolve(to); - } - return ret; - } - } - const { cascade } = this; - if (cascade !== true) { - return null; - } - let parentDependencyMapper = this.#parentDependencyMapper; - if (parentDependencyMapper === undefined) { - parentDependencyMapper = manifest.getScopeDependencyMapper( - this.href, - this.allowSameHREFScope, - ); - this.#parentDependencyMapper = parentDependencyMapper; - } - if (parentDependencyMapper === null) { - return null; - } - return parentDependencyMapper._resolveAlreadyNormalized( - normalizedSpecifier, - conditions, - manifest, - ); - } -} - -const kArbitraryDependencies = new DependencyMapperInstance( - 'arbitrary dependencies', - kFallThrough, - false, - true, -); -const kNoDependencies = new DependencyMapperInstance( - 'no dependencies', - null, - false, - true, -); -/** - * @param {string} href - * @param {JSONDependencyMap} dependencies - * @param {boolean} cascade - * @param {boolean} allowSameHREFScope - * @param {Map} store - */ -const insertDependencyMap = ( - href, - dependencies, - cascade, - allowSameHREFScope, - store, -) => { - if (cascade !== undefined && typeof cascade !== 'boolean') { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'cascade'); - } - if (dependencies === true) { - store.set(href, kArbitraryDependencies); - return; - } - if (dependencies === null || dependencies === undefined) { - store.set( - href, - cascade ? - new DependencyMapperInstance(href, null, true, allowSameHREFScope) : - kNoDependencies, - ); - return; - } - if (objectButNotArray(dependencies)) { - store.set( - href, - new DependencyMapperInstance( - href, - dependencies, - cascade, - allowSameHREFScope, - ), - ); - return; - } - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'dependencies'); -}; -/** - * Finds the longest key within `this.#scopeDependencies` that covers a - * specific HREF - * @param {string} href - * @param {ScopeStore} scopeStore - * @returns {null | string} - */ -function findScopeHREF(href, scopeStore, allowSame) { - let protocol; - if (href !== '') { - // default URL parser does some stuff to special urls... skip if this is - // just the protocol - if (RegExpPrototypeExec(/^[^:]*[:]$/, href) !== null) { - protocol = href; - } else { - let currentURL = new URL(href); - const normalizedHREF = currentURL.href; - protocol = currentURL.protocol; - // Non-opaque blobs adopt origins - if (protocol === 'blob:' && currentURL.origin !== 'null') { - currentURL = new URL(currentURL.origin); - protocol = currentURL.protocol; - } - // Only a few schemes are hierarchical - if (kSpecialSchemes.has(currentURL.protocol)) { - // Make first '..' act like '.' - if (!StringPrototypeEndsWith(currentURL.pathname, '/')) { - currentURL.pathname += '/'; - } - let lastHREF; - let currentHREF = currentURL.href; - do { - if (scopeStore.has(currentHREF)) { - if (allowSame || currentHREF !== normalizedHREF) { - return currentHREF; - } - } - lastHREF = currentHREF; - currentURL = new URL('..', currentURL); - currentHREF = currentURL.href; - } while (lastHREF !== currentHREF); - } - } - } - if (scopeStore.has(protocol)) { - if (allowSame || protocol !== href) return protocol; - } - if (scopeStore.has('')) { - if (allowSame || '' !== href) return ''; - } - return null; -} -// #endregion - -/** - * @typedef {Record | typeof kFallThrough} DependencyMap - * @typedef {Array<[string, [string, string]]>} PatternDependencyMap - * @typedef {Record | null | true} JSONDependencyMap - */ -/** - * @typedef {Map} ScopeStore - * @typedef {(specifier: string) => true | URL} DependencyMapper - * @typedef {boolean | string | SRI[] | typeof kCascade} Integrity - */ - -class Manifest { - #defaultDependencies; - /** - * @type {string} - */ - href; - /** - * @type {(err: Error) => void} - * - * Performs default action for what happens when a manifest encounters - * a violation such as abort()ing or exiting the process, throwing the error, - * or logging the error. - */ - #reaction; - /** - * @type {Map} - * - * Used to find where a dependency is located. - * - * This stores functions to lazily calculate locations as needed. - * `true` is used to signify that the location is not specified - * by the manifest and default resolution should be allowed. - * - * The functions return `null` to signify that a dependency is - * not found - */ - #resourceDependencies = new SafeMap(); - /** - * @type {Map} - * - * Used to compare a resource to the content body at the resource. - * `true` is used to signify that all integrities are allowed, otherwise, - * SRI strings are parsed to compare with the body. - * - * This stores strings instead of eagerly parsing SRI strings - * and only converts them to SRI data structures when needed. - * This avoids needing to parse all SRI strings at startup even - * if some never end up being used. - */ - #resourceIntegrities = new SafeMap(); - /** - * @type {ScopeStore} - * - * Used to compare a resource to the content body at the resource. - * `true` is used to signify that all integrities are allowed, otherwise, - * SRI strings are parsed to compare with the body. - * - * Separate from #resourceDependencies due to conflicts with things like - * `blob:` being both a scope and a resource potentially as well as - * `file:` being parsed to `file:///` instead of remaining host neutral. - */ - #scopeDependencies = new SafeMap(); - /** - * @type {Map} - * - * Used to allow arbitrary loading within a scope - */ - #scopeIntegrities = new SafeMap(); - /** - * `obj` should match the policy file format described in the docs - * it is expected to not have prototype pollution issues either by reassigning - * the prototype to `null` for values or by running prior to any user code. - * - * `manifestURL` is a URL to resolve relative locations against. - * @param {object} obj - * @param {string} manifestHREF - */ - constructor(obj, manifestHREF) { - this.href = manifestHREF; - const scopes = this.#scopeDependencies; - const integrities = this.#resourceIntegrities; - const resourceDependencies = this.#resourceDependencies; - let reaction = REACTION_THROW; - - if (objectButNotArray(obj) && 'onerror' in obj) { - const behavior = obj.onerror; - if (behavior === 'exit') { - reaction = REACTION_EXIT; - } else if (behavior === 'log') { - reaction = REACTION_LOG; - } else if (behavior !== 'throw') { - throw new ERR_MANIFEST_UNKNOWN_ONERROR(behavior); - } - } - - this.#reaction = reaction; - const jsonResourcesEntries = ObjectEntries( - obj.resources ?? { __proto__: null }, - ); - const jsonScopesEntries = ObjectEntries(obj.scopes ?? { __proto__: null }); - const defaultDependencies = obj.dependencies ?? { __proto__: null }; - - this.#defaultDependencies = new DependencyMapperInstance( - 'default', - defaultDependencies === true ? kFallThrough : defaultDependencies, - false, - ); - - for (let i = 0; i < jsonResourcesEntries.length; i++) { - const { 0: originalHREF, 1: descriptor } = jsonResourcesEntries[i]; - const { cascade, dependencies, integrity } = descriptor; - const href = resolve(originalHREF, manifestHREF).href; - - if (typeof integrity !== 'undefined') { - debug('Manifest contains integrity for resource %s', originalHREF); - if (typeof integrity === 'string') { - integrities.set(href, integrity); - } else if (integrity === true) { - integrities.set(href, true); - } else { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'integrity'); - } - } else { - integrities.set(href, cascade === true ? kCascade : false); - } - insertDependencyMap( - href, - dependencies, - cascade, - true, - resourceDependencies, - ); - } - - const scopeIntegrities = this.#scopeIntegrities; - for (let i = 0; i < jsonScopesEntries.length; i++) { - const { 0: originalHREF, 1: descriptor } = jsonScopesEntries[i]; - const { cascade, dependencies, integrity } = descriptor; - const href = emptyOrProtocolOrResolve(originalHREF, manifestHREF); - if (typeof integrity !== 'undefined') { - debug('Manifest contains integrity for scope %s', originalHREF); - if (integrity === true) { - scopeIntegrities.set(href, true); - } else { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'integrity'); - } - } else { - scopeIntegrities.set(href, cascade === true ? kCascade : false); - } - insertDependencyMap(href, dependencies, cascade, false, scopes); - } - - ObjectFreeze(this); - } - - /** - * @param {string} requester - * @returns {{resolve: any, reaction: (err: any) => void}} - */ - getDependencyMapper(requester) { - const requesterHREF = `${requester}`; - const dependencies = this.#resourceDependencies; - /** - * @type {DependencyMapperInstance} - */ - const instance = ( - dependencies.has(requesterHREF) ? - dependencies.get(requesterHREF) ?? null : - this.getScopeDependencyMapper(requesterHREF, true) - ) ?? this.#defaultDependencies; - return { - resolve: (specifier, conditions) => { - const normalizedSpecifier = canonicalizeSpecifier( - specifier, - requesterHREF, - ); - const result = instance._resolveAlreadyNormalized( - normalizedSpecifier, - conditions, - this, - ); - if (result === kFallThrough) return true; - return result; - }, - reaction: this.#reaction, - }; - } - - mightAllow(url, onreact) { - const href = `${url}`; - debug('Checking for entry of %s', href); - if (StringPrototypeStartsWith(href, 'node:')) { - return true; - } - if (this.#resourceIntegrities.has(href)) { - return true; - } - let scope = findScopeHREF(href, this.#scopeIntegrities, true); - while (scope !== null) { - if (this.#scopeIntegrities.has(scope)) { - const entry = this.#scopeIntegrities.get(scope); - if (entry === true) { - return true; - } else if (entry !== kCascade) { - break; - } - } - const nextScope = findScopeHREF( - new URL('..', scope), - this.#scopeIntegrities, - false, - ); - if (!nextScope || nextScope === scope) { - break; - } - scope = nextScope; - } - if (onreact) { - this.#reaction(onreact()); - } - return false; - } - - assertIntegrity(url, content) { - const href = `${url}`; - debug('Checking integrity of %s', href); - const realIntegrities = new SafeMap(); - const integrities = this.#resourceIntegrities; - function processEntry(href) { - let integrityEntries = integrities.get(href); - if (integrityEntries === true) return true; - if (typeof integrityEntries === 'string') { - const sri = ObjectFreeze(SRI.parse(integrityEntries)); - integrities.set(href, sri); - integrityEntries = sri; - } - return integrityEntries; - } - if (integrities.has(href)) { - const integrityEntries = processEntry(href); - if (integrityEntries === true) return true; - if (ArrayIsArray(integrityEntries)) { - // Avoid clobbered Symbol.iterator - for (let i = 0; i < integrityEntries.length; i++) { - const { algorithm, value: expected } = integrityEntries[i]; - // TODO(tniessen): the content should not be passed as a string in the - // first place, see https://github.com/nodejs/node/issues/39707 - const mismatchedIntegrity = internalVerifyIntegrity(algorithm, content, expected); - if (mismatchedIntegrity === undefined) { - return true; - } - realIntegrities.set(algorithm, mismatchedIntegrity); - } - } - - if (integrityEntries !== kCascade) { - const error = new ERR_MANIFEST_ASSERT_INTEGRITY(url, realIntegrities); - this.#reaction(error); - } - } - let scope = findScopeHREF(href, this.#scopeIntegrities, true); - while (scope !== null) { - if (this.#scopeIntegrities.has(scope)) { - const entry = this.#scopeIntegrities.get(scope); - if (entry === true) { - return true; - } else if (entry !== kCascade) { - break; - } - } - const nextScope = findScopeHREF(scope, this.#scopeDependencies, false); - if (!nextScope) { - break; - } - scope = nextScope; - } - const error = new ERR_MANIFEST_ASSERT_INTEGRITY(url, realIntegrities); - this.#reaction(error); - } - /** - * @param {string} href - * @param {boolean} allowSameHREFScope - * @returns {DependencyMapperInstance | null} - */ - getScopeDependencyMapper(href, allowSameHREFScope) { - if (href === null) { - return this.#defaultDependencies; - } - /** @type {string | null} */ - const scopeHREF = findScopeHREF( - href, - this.#scopeDependencies, - allowSameHREFScope, - ); - if (scopeHREF === null) return this.#defaultDependencies; - return this.#scopeDependencies.get(scopeHREF); - } -} - -// Lock everything down to avoid problems even if reference is leaked somehow -ObjectSetPrototypeOf(Manifest, null); -ObjectSetPrototypeOf(Manifest.prototype, null); -ObjectFreeze(Manifest); -ObjectFreeze(Manifest.prototype); -module.exports = ObjectFreeze({ Manifest }); - -// #region URL utils - -/** - * Attempts to canonicalize relative URL strings against a base URL string - * Does not perform I/O - * If not able to canonicalize, returns the original specifier - * - * This effectively removes the possibility of the return value being a relative - * URL string - * @param {string} specifier - * @param {string} base - * @returns {string} - */ -function canonicalizeSpecifier(specifier, base) { - try { - if (RegExpPrototypeExec(kRelativeURLStringPattern, specifier) !== null) { - return resolve(specifier, base).href; - } - return resolve(specifier).href; - } catch { - // Continue regardless of error. - } - return specifier; -} - -/** - * Does a special allowance for scopes to be non-valid URLs - * that are only protocol strings or the empty string - * @param {string} resourceHREF - * @param {string} [base] - * @returns {string} - */ -const emptyOrProtocolOrResolve = (resourceHREF, base) => { - if (resourceHREF === '') return ''; - if (StringPrototypeEndsWith(resourceHREF, ':')) { - // URL parse will trim these anyway, save the compute - resourceHREF = RegExpPrototypeSymbolReplace( - // eslint-disable-next-line - /^[\x00-\x1F\x20]|\x09\x0A\x0D|[\x00-\x1F\x20]$/g, - resourceHREF, - '', - ); - if (RegExpPrototypeExec(/^[a-zA-Z][a-zA-Z+\-.]*:$/, resourceHREF) !== null) { - return resourceHREF; - } - } - return resolve(resourceHREF, base).href; -}; - -/** - * @type {Map} - */ -let parsedURLs; -/** - * Resolves a valid url string and uses the parsed cache to avoid double parsing - * costs. - * @param {string} originalHREF - * @param {string} [base] - * @returns {Readonly} - */ -const resolve = (originalHREF, base) => { - parsedURLs = parsedURLs ?? new SafeMap(); - if (parsedURLs.has(originalHREF)) { - return parsedURLs.get(originalHREF); - } else if (RegExpPrototypeExec(kRelativeURLStringPattern, originalHREF) !== null) { - const resourceURL = new URL(originalHREF, base); - parsedURLs.set(resourceURL.href, resourceURL); - return resourceURL; - } - const resourceURL = new URL(originalHREF); - parsedURLs.set(originalHREF, resourceURL); - return resourceURL; -}; - -// #endregion - -/** - * @param {any} o - * @returns {o is object} - */ -function objectButNotArray(o) { - return o && typeof o === 'object' && !ArrayIsArray(o); -} - -function searchDependencies(href, target, conditions) { - if (objectButNotArray(target)) { - const keys = ObjectKeys(target); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (conditions.has(key)) { - const ret = searchDependencies(href, target[key], conditions); - if (ret != null) { - return ret; - } - } - } - } else if (typeof target === 'string') { - return target; - } else if (target === true) { - return target; - } else { - throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD(href, 'dependencies'); - } - return null; -} diff --git a/lib/internal/policy/sri.js b/lib/internal/policy/sri.js deleted file mode 100644 index 675226a3a0723a..00000000000000 --- a/lib/internal/policy/sri.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; -// Utility to parse the value of -// https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute - -const { - ArrayPrototype, - ObjectDefineProperty, - ObjectFreeze, - ObjectSeal, - ObjectSetPrototypeOf, - RegExp, - RegExpPrototypeExec, - StringPrototypeSlice, -} = primordials; - -const { - ERR_SRI_PARSE, -} = require('internal/errors').codes; -const kWSP = '[\\x20\\x09]'; -const kVCHAR = '[\\x21-\\x7E]'; -const kHASH_ALGO = 'sha(?:256|384|512)'; -// Base64 -const kHASH_VALUE = '[A-Za-z0-9+/]+[=]{0,2}'; -const kHASH_EXPRESSION = `(${kHASH_ALGO})-(${kHASH_VALUE})`; -// Ungrouped since unused -const kOPTION_EXPRESSION = `(?:${kVCHAR}*)`; -const kHASH_WITH_OPTIONS = `${kHASH_EXPRESSION}(?:[?](${kOPTION_EXPRESSION}))?`; -const kSRIPattern = RegExp(`(${kWSP}*)(?:${kHASH_WITH_OPTIONS})`, 'g'); -ObjectSeal(kSRIPattern); -const kAllWSP = RegExp(`^${kWSP}*$`); -ObjectSeal(kAllWSP); - -const BufferFrom = require('buffer').Buffer.from; - -// Returns {algorithm, value (in base64 string), options,}[] -const parse = (str) => { - let prevIndex = 0; - let match; - const entries = []; - while ((match = RegExpPrototypeExec(kSRIPattern, str)) !== null) { - if (match.index !== prevIndex) { - throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); - } - if (entries.length > 0 && match[1] === '') { - throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); - } - - // Avoid setters being fired - ObjectDefineProperty(entries, entries.length, { - __proto__: null, - enumerable: true, - configurable: true, - value: ObjectFreeze({ - __proto__: null, - algorithm: match[2], - value: BufferFrom(match[3], 'base64'), - options: match[4] === undefined ? null : match[4], - }), - }); - prevIndex += match[0].length; - } - - if (prevIndex !== str.length) { - if (RegExpPrototypeExec(kAllWSP, StringPrototypeSlice(str, prevIndex)) === null) { - throw new ERR_SRI_PARSE(str, str[prevIndex], prevIndex); - } - } - return ObjectSetPrototypeOf(entries, ArrayPrototype); -}; - -module.exports = { - parse, -}; diff --git a/lib/internal/process/policy.js b/lib/internal/process/policy.js deleted file mode 100644 index 8e07cb92118c84..00000000000000 --- a/lib/internal/process/policy.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const { - JSONParse, - ObjectFreeze, - ReflectSetPrototypeOf, -} = primordials; - -const { - ERR_ACCESS_DENIED, - ERR_MANIFEST_TDZ, -} = require('internal/errors').codes; -const { Manifest } = require('internal/policy/manifest'); -let manifest; -let manifestSrc; -let manifestURL; - -module.exports = ObjectFreeze({ - __proto__: null, - setup(src, url) { - manifestSrc = src; - manifestURL = url; - if (src === null) { - manifest = null; - return; - } - - const json = JSONParse(src, (_, o) => { - if (o && typeof o === 'object') { - ReflectSetPrototypeOf(o, null); - ObjectFreeze(o); - } - return o; - }); - manifest = new Manifest(json, url); - - // process.binding() is deprecated (DEP0111) and trivially allows bypassing - // policies, so if policies are enabled, make this API unavailable. - process.binding = function binding(_module) { - throw new ERR_ACCESS_DENIED('process.binding'); - }; - process._linkedBinding = function _linkedBinding(_module) { - throw new ERR_ACCESS_DENIED('process._linkedBinding'); - }; - }, - - get manifest() { - if (typeof manifest === 'undefined') { - throw new ERR_MANIFEST_TDZ(); - } - return manifest; - }, - - get src() { - if (typeof manifestSrc === 'undefined') { - throw new ERR_MANIFEST_TDZ(); - } - return manifestSrc; - }, - - get url() { - if (typeof manifestURL === 'undefined') { - throw new ERR_MANIFEST_TDZ(); - } - return manifestURL; - }, - - assertIntegrity(moduleURL, content) { - this.manifest.assertIntegrity(moduleURL, content); - }, -}); diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 761d7ca5fdf8a1..133f2c07b53bc2 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -12,7 +12,6 @@ const { NumberParseInt, ObjectDefineProperty, ObjectFreeze, - SafeMap, String, StringPrototypeStartsWith, Symbol, @@ -34,7 +33,6 @@ const { } = require('internal/util'); const { - ERR_MANIFEST_ASSERT_INTEGRITY, ERR_MISSING_OPTION, ERR_ACCESS_DENIED, } = require('internal/errors').codes; @@ -57,7 +55,7 @@ function prepareMainThreadExecution(expandArgv1 = false, initializeModules = tru function prepareWorkerThreadExecution() { prepareExecution({ expandArgv1: false, - initializeModules: false, // Will need to initialize it after policy setup + initializeModules: false, isMainThread: false, }); } @@ -121,11 +119,6 @@ function prepareExecution(options) { if (isMainThread) { assert(internalBinding('worker').isMainThread); // Worker threads will get the manifest in the message handler. - const policy = readPolicyFromDisk(); - if (policy) { - require('internal/process/policy') - .setup(policy.manifestSrc, policy.manifestURL); - } // Print stack trace on `SIGINT` if option `--trace-sigint` presents. setupStacktracePrinterOnSigint(); @@ -580,56 +573,6 @@ function initializePermission() { } } -function readPolicyFromDisk() { - const experimentalPolicy = getOptionValue('--experimental-policy'); - if (experimentalPolicy) { - process.emitWarning('Policies are experimental.', - 'ExperimentalWarning'); - const { pathToFileURL, URL } = require('internal/url'); - // URL here as it is slightly different parsing - // no bare specifiers for now - let manifestURL; - if (require('path').isAbsolute(experimentalPolicy)) { - manifestURL = pathToFileURL(experimentalPolicy); - } else { - const cwdURL = pathToFileURL(process.cwd()); - cwdURL.pathname += '/'; - manifestURL = new URL(experimentalPolicy, cwdURL); - } - const fs = require('fs'); - const src = fs.readFileSync(manifestURL, 'utf8'); - const experimentalPolicyIntegrity = getOptionValue('--policy-integrity'); - if (experimentalPolicyIntegrity) { - const SRI = require('internal/policy/sri'); - const { createHash, timingSafeEqual } = require('crypto'); - const realIntegrities = new SafeMap(); - const integrityEntries = SRI.parse(experimentalPolicyIntegrity); - let foundMatch = false; - for (let i = 0; i < integrityEntries.length; i++) { - const { - algorithm, - value: expected, - } = integrityEntries[i]; - const hash = createHash(algorithm); - hash.update(src); - const digest = hash.digest(); - if (digest.length === expected.length && - timingSafeEqual(digest, expected)) { - foundMatch = true; - break; - } - realIntegrities.set(algorithm, digest.toString('base64')); - } - if (!foundMatch) { - throw new ERR_MANIFEST_ASSERT_INTEGRITY(manifestURL, realIntegrities); - } - } - return { - manifestSrc: src, manifestURL: manifestURL.href, - }; - } -} - function initializeCJSLoader() { const { initializeCJS } = require('internal/modules/cjs/loader'); initializeCJS(); diff --git a/lib/internal/worker.js b/lib/internal/worker.js index b58cbe56d01703..9eefaa97021756 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -41,7 +41,6 @@ const { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, } = errorCodes; -const { getOptionValue } = require('internal/options'); const workerIo = require('internal/worker/io'); const { @@ -273,12 +272,6 @@ class Worker extends EventEmitter { workerData: options.workerData, environmentData, publicPort: port2, - manifestURL: getOptionValue('--experimental-policy') ? - require('internal/process/policy').url : - null, - manifestSrc: getOptionValue('--experimental-policy') ? - require('internal/process/policy').src : - null, hasStdin: !!options.stdin, }, transferList); // Use this to cache the Worker's loopStart value once available. diff --git a/src/env.cc b/src/env.cc index 642a7d4a9d2559..b8574fcfb775b8 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1096,12 +1096,6 @@ void Environment::InitializeCompileCache() { dir_from_env.empty()) { return; } - if (!options()->experimental_policy.empty()) { - Debug(this, - DebugCategory::COMPILE_CACHE, - "[compile cache] skipping cache because policy is enabled"); - return; - } auto handler = std::make_unique(this); if (handler->InitializeDirectory(this, dir_from_env)) { compile_cache_handler_ = std::move(handler); diff --git a/src/env_properties.h b/src/env_properties.h index e10d472e730f34..8bf62103a394d9 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -35,8 +35,7 @@ V(napi_wrapper, "node:napi:wrapper") \ V(untransferable_object_private_symbol, "node:untransferableObject") \ V(exit_info_private_symbol, "node:exit_info_private_symbol") \ - V(promise_trace_id, "node:promise_trace_id") \ - V(require_private_symbol, "node:require_private_symbol") + V(promise_trace_id, "node:promise_trace_id") // Symbols are per-isolate primitives but Environment proxies them // for the sake of convenience. diff --git a/src/node_builtins.cc b/src/node_builtins.cc index bbb63df7899d4b..3a2320e33d0354 100644 --- a/src/node_builtins.cc +++ b/src/node_builtins.cc @@ -124,7 +124,6 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { "_tls_wrap", "internal/tls/secure-pair", "internal/tls/parse-cert-string", "internal/tls/secure-context", "internal/http2/core", "internal/http2/compat", - "internal/policy/manifest", "internal/process/policy", "internal/streams/lazy_transform", #endif // !HAVE_OPENSSL "sys", // Deprecated. diff --git a/src/node_modules.cc b/src/node_modules.cc index c2f7a3da3ba009..5ff7fae9aebe1c 100644 --- a/src/node_modules.cc +++ b/src/node_modules.cc @@ -76,23 +76,21 @@ void BindingData::Deserialize(v8::Local context, } Local BindingData::PackageConfig::Serialize(Realm* realm) const { - auto has_manifest = !realm->env()->options()->experimental_policy.empty(); auto isolate = realm->isolate(); const auto ToString = [isolate](std::string_view input) -> Local { return String::NewFromUtf8( isolate, input.data(), NewStringType::kNormal, input.size()) .ToLocalChecked(); }; - Local values[7] = { + Local values[6] = { name.has_value() ? ToString(*name) : Undefined(isolate), main.has_value() ? ToString(*main) : Undefined(isolate), ToString(type), imports.has_value() ? ToString(*imports) : Undefined(isolate), exports.has_value() ? ToString(*exports) : Undefined(isolate), - has_manifest ? ToString(raw_json) : Undefined(isolate), ToString(file_path), }; - return Array::New(isolate, values, 7); + return Array::New(isolate, values, 6); } const BindingData::PackageConfig* BindingData::GetPackageJSON( @@ -361,11 +359,9 @@ void BindingData::GetNearestParentPackageJSONType( return; } - Local values[3] = { - ToV8Value(realm->context(), package_json->type).ToLocalChecked(), - ToV8Value(realm->context(), package_json->file_path).ToLocalChecked(), - ToV8Value(realm->context(), package_json->raw_json).ToLocalChecked()}; - args.GetReturnValue().Set(Array::New(realm->isolate(), values, 3)); + Local value = + ToV8Value(realm->context(), package_json->type).ToLocalChecked(); + args.GetReturnValue().Set(value); } void BindingData::GetPackageScopeConfig( diff --git a/src/node_options.cc b/src/node_options.cc index 60971e156209c3..684e32836aa8c8 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -107,14 +107,6 @@ void PerIsolateOptions::CheckOptions(std::vector* errors, void EnvironmentOptions::CheckOptions(std::vector* errors, std::vector* argv) { - if (has_policy_integrity_string && experimental_policy.empty()) { - errors->push_back("--policy-integrity requires " - "--experimental-policy be enabled"); - } - if (has_policy_integrity_string && experimental_policy_integrity.empty()) { - errors->push_back("--policy-integrity cannot be empty"); - } - if (!input_type.empty()) { if (input_type != "commonjs" && input_type != "module") { errors->push_back("--input-type must be \"module\" or \"commonjs\""); @@ -435,20 +427,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { &EnvironmentOptions::experimental_permission, kAllowedInEnvvar, false); - AddOption("--experimental-policy", - "use the specified file as a " - "security policy", - &EnvironmentOptions::experimental_policy, - kAllowedInEnvvar); - AddOption("[has_policy_integrity_string]", - "", - &EnvironmentOptions::has_policy_integrity_string); - AddOption("--policy-integrity", - "ensure the security policy contents match " - "the specified integrity", - &EnvironmentOptions::experimental_policy_integrity, - kAllowedInEnvvar); - Implies("--policy-integrity", "[has_policy_integrity_string]"); AddOption("--allow-fs-read", "allow permissions to read the filesystem", &EnvironmentOptions::allow_fs_read, diff --git a/src/node_options.h b/src/node_options.h index 892399954aa9e1..a15303962ff461 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -118,9 +118,6 @@ class EnvironmentOptions : public Options { bool experimental_import_meta_resolve = false; std::string input_type; // Value of --input-type std::string type; // Value of --experimental-default-type - std::string experimental_policy; - std::string experimental_policy_integrity; - bool has_policy_integrity_string = false; bool experimental_permission = false; std::vector allow_fs_read; std::vector allow_fs_write; diff --git a/test/benchmark/test-benchmark-policy.js b/test/benchmark/test-benchmark-policy.js deleted file mode 100644 index 7eb0992b1f1eda..00000000000000 --- a/test/benchmark/test-benchmark-policy.js +++ /dev/null @@ -1,9 +0,0 @@ -'use strict'; - -require('../common'); - -const runBenchmark = require('../common/benchmark'); - -runBenchmark('policy', [ - 'n=1', -]); diff --git a/test/fixtures/policy-manifest/createRequire-bypass.js b/test/fixtures/policy-manifest/createRequire-bypass.js deleted file mode 100644 index 06827ea0251d13..00000000000000 --- a/test/fixtures/policy-manifest/createRequire-bypass.js +++ /dev/null @@ -1,2 +0,0 @@ -const os = module.constructor.createRequire('file:///os-access-module.js')('os') -os.cpus() \ No newline at end of file diff --git a/test/fixtures/policy-manifest/invalid-module.js b/test/fixtures/policy-manifest/invalid-module.js deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/test/fixtures/policy-manifest/invalid.json b/test/fixtures/policy-manifest/invalid.json deleted file mode 100644 index a8a0deb2cf7e07..00000000000000 --- a/test/fixtures/policy-manifest/invalid.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "resources": { - "./fhqwhgads.js": { - "dependencies": { - "**": true - } - } - } -} diff --git a/test/fixtures/policy-manifest/main-constructor-bypass.js b/test/fixtures/policy-manifest/main-constructor-bypass.js deleted file mode 100644 index 066af168b1432e..00000000000000 --- a/test/fixtures/policy-manifest/main-constructor-bypass.js +++ /dev/null @@ -1,2 +0,0 @@ -const m = new require.main.constructor(); -m.require('./invalid-module') diff --git a/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js b/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js deleted file mode 100644 index 7e002dc17a7741..00000000000000 --- a/test/fixtures/policy-manifest/main-constructor-extensions-bypass.js +++ /dev/null @@ -1,2 +0,0 @@ -const m = new require.main.constructor(); -require.extensions['.js'](m, './invalid-module') diff --git a/test/fixtures/policy-manifest/main-module-bypass.js b/test/fixtures/policy-manifest/main-module-bypass.js deleted file mode 100644 index a1cec2ddd3ffbb..00000000000000 --- a/test/fixtures/policy-manifest/main-module-bypass.js +++ /dev/null @@ -1 +0,0 @@ -process.mainModule.require('os').cpus(); diff --git a/test/fixtures/policy-manifest/main-module-proto-bypass.js b/test/fixtures/policy-manifest/main-module-proto-bypass.js deleted file mode 100644 index 6111aae140ed0a..00000000000000 --- a/test/fixtures/policy-manifest/main-module-proto-bypass.js +++ /dev/null @@ -1 +0,0 @@ -process.mainModule.__proto__.require("os") diff --git a/test/fixtures/policy-manifest/manifest-impersonate.json b/test/fixtures/policy-manifest/manifest-impersonate.json deleted file mode 100644 index 5e15b6e9cca906..00000000000000 --- a/test/fixtures/policy-manifest/manifest-impersonate.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "resources": { - "./createRequire-bypass.js": { - "integrity": true - }, - "/os-access-module.js": { - "integrity": true, - "dependencies": { - "os": true - } - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy-manifest/module-constructor-bypass.js b/test/fixtures/policy-manifest/module-constructor-bypass.js deleted file mode 100644 index 8ff9e532a81f71..00000000000000 --- a/test/fixtures/policy-manifest/module-constructor-bypass.js +++ /dev/null @@ -1 +0,0 @@ -module.constructor._load('node:child_process'); diff --git a/test/fixtures/policy-manifest/object-define-property-bypass.js b/test/fixtures/policy-manifest/object-define-property-bypass.js deleted file mode 100644 index 5543fd35b28b26..00000000000000 --- a/test/fixtures/policy-manifest/object-define-property-bypass.js +++ /dev/null @@ -1,19 +0,0 @@ -let requires = new WeakMap() -Object.defineProperty(Object.getPrototypeOf(module), 'require', { - get() { - return requires.get(this); - }, - set(v) { - requires.set(this, v); - process.nextTick(() => { - let fs = Reflect.apply(v, this, ['fs']) - if (typeof fs.readFileSync === 'function') { - process.exit(1); - } - }) - return requires.get(this); - }, - configurable: true -}) - -require('./valid-module') diff --git a/test/fixtures/policy-manifest/onerror-exit.json b/test/fixtures/policy-manifest/onerror-exit.json deleted file mode 100644 index 24bd92817d24b1..00000000000000 --- a/test/fixtures/policy-manifest/onerror-exit.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "onerror": "exit", - "scopes": { - "file:": { - "integrity": true, - "dependencies": {} - } - } -} diff --git a/test/fixtures/policy-manifest/onerror-resource-exit.json b/test/fixtures/policy-manifest/onerror-resource-exit.json deleted file mode 100644 index f08bc8d32c07e7..00000000000000 --- a/test/fixtures/policy-manifest/onerror-resource-exit.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "onerror": "exit", - "resources": { - "./object-define-property-bypass.js": { - "integrity": true, - "dependencies": { - "./valid-module": true - } - }, - "./valid-module.js": { - "integrity": true, - "dependencies": { - "fs": true - } - } - } -} diff --git a/test/fixtures/policy-manifest/valid-module.js b/test/fixtures/policy-manifest/valid-module.js deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/test/fixtures/policy/bad-main.mjs b/test/fixtures/policy/bad-main.mjs deleted file mode 100644 index db1938ad7c2e87..00000000000000 --- a/test/fixtures/policy/bad-main.mjs +++ /dev/null @@ -1 +0,0 @@ -import {doesNotExist} from './dep.js'; diff --git a/test/fixtures/policy/canonicalize.mjs b/test/fixtures/policy/canonicalize.mjs deleted file mode 100644 index 64e7cd117ad7c0..00000000000000 --- a/test/fixtures/policy/canonicalize.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import resolveAsFS from './dep.js'; -import fs from 'fs'; - -let correct = resolveAsFS === fs && typeof resolveAsFS === 'object'; -process.exit(correct ? 0 : 1); diff --git a/test/fixtures/policy/crypto-default-encoding/.gitattributes b/test/fixtures/policy/crypto-default-encoding/.gitattributes deleted file mode 100644 index cbdcbbc258e6e7..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.js text eol=lf diff --git a/test/fixtures/policy/crypto-default-encoding/dep.js b/test/fixtures/policy/crypto-default-encoding/dep.js deleted file mode 100644 index d741da76db0076..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/dep.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -// No code. diff --git a/test/fixtures/policy/crypto-default-encoding/parent.js b/test/fixtures/policy/crypto-default-encoding/parent.js deleted file mode 100644 index 90ebde7e6535c0..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/parent.js +++ /dev/null @@ -1,4 +0,0 @@ -'use strict'; - -require('crypto').DEFAULT_ENCODING = process.env.DEFAULT_ENCODING; -require('./dep.js'); diff --git a/test/fixtures/policy/crypto-default-encoding/policy.json b/test/fixtures/policy/crypto-default-encoding/policy.json deleted file mode 100644 index 4cb485e1d9e2e4..00000000000000 --- a/test/fixtures/policy/crypto-default-encoding/policy.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "resources": { - "./parent.js": { - "integrity": "sha384-j4pMdq83q5Bq9+idcHuGKzi89FrYm1PhZYrEw3irbNob6g4i3vKBjfYiRNYwmoGr", - "dependencies": { - "crypto": true, - "./dep.js": true - } - }, - "./dep.js": { - "integrity": "sha384-VU7GIrTix/HFLhUb4yqsV4n1xXqjPcWw6kLvjuKXtR1+9nmufJu5vZLajBs8brIW" - } - } -} diff --git a/test/fixtures/policy/crypto-hash-tampering/.gitattributes b/test/fixtures/policy/crypto-hash-tampering/.gitattributes deleted file mode 100644 index cbdcbbc258e6e7..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.js text eol=lf diff --git a/test/fixtures/policy/crypto-hash-tampering/main.js b/test/fixtures/policy/crypto-hash-tampering/main.js deleted file mode 100644 index 2ee233fe75461b..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/main.js +++ /dev/null @@ -1,8 +0,0 @@ -const h = require('crypto').createHash('sha384'); -const fakeDigest = h.digest(); - -const kHandle = Object.getOwnPropertySymbols(h) - .find((s) => s.description === 'kHandle'); -h[kHandle].constructor.prototype.digest = () => fakeDigest; - -require('./protected.js'); diff --git a/test/fixtures/policy/crypto-hash-tampering/policy.json b/test/fixtures/policy/crypto-hash-tampering/policy.json deleted file mode 100644 index 3d981911533f56..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/policy.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "resources": { - "./main.js": { - "integrity": true, - "dependencies": { - "./protected.js": true, - "crypto": true - } - }, - "./protected.js": { - "integrity": "sha384-OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb", - "dependencies": true - } - } -} diff --git a/test/fixtures/policy/crypto-hash-tampering/protected.js b/test/fixtures/policy/crypto-hash-tampering/protected.js deleted file mode 100644 index 2b57adba5b191a..00000000000000 --- a/test/fixtures/policy/crypto-hash-tampering/protected.js +++ /dev/null @@ -1 +0,0 @@ -console.log(require('fs').readFileSync('/etc/passwd').length); diff --git a/test/fixtures/policy/dep-policy.json b/test/fixtures/policy/dep-policy.json deleted file mode 100644 index 6cc483a578733c..00000000000000 --- a/test/fixtures/policy/dep-policy.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "resources": { - "./dep.js": { - "integrity": "sha512-7CMcc2oytFfMnGQaXbJk84gYWF2J7p/fmWPW7dsnJyniD+vgxtK9VAZ/22UxFOA4q5d27RoGLxSqNZ/nGCJkMw== sha512-scgN9Td0bGMlGH2lUHvEeHtz92Hx6AO+sYhU3WRI6bn3jEUCXbXJs68nOOsGzRWR7a2tbqGoETnOCpHHf1Njhw==" - } - } -} diff --git a/test/fixtures/policy/dep.js b/test/fixtures/policy/dep.js deleted file mode 100644 index 1c61a090d275a8..00000000000000 --- a/test/fixtures/policy/dep.js +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; -module.exports = 'The Secret Ingredient'; diff --git a/test/fixtures/policy/dependencies/dependencies-empty-policy.json b/test/fixtures/policy/dependencies/dependencies-empty-policy.json deleted file mode 100644 index 9c0389cd03928f..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-empty-policy.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": {} - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-missing-export-policy.json b/test/fixtures/policy/dependencies/dependencies-missing-export-policy.json deleted file mode 100644 index 5da0de13920936..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-missing-export-policy.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "resources": { - "../bad-main.mjs": { - "integrity": true, - "dependencies": true - }, - "../dep.js": { - "integrity": true - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json b/test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json deleted file mode 100644 index 10ab862d2b0cc0..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-missing-policy-default-true.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "dependencies": true, - "resources": { - "../parent.js": { - "cascade": true, - "integrity": true - }, - "../dep.js": { - "cascade": true, - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-missing-policy.json b/test/fixtures/policy/dependencies/dependencies-missing-policy.json deleted file mode 100644 index 40d2866ba5e06d..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-missing-policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json b/test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json deleted file mode 100644 index 7d2715f96a5092..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-redirect-builtin-policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": { - "../dep.js": "node:util" - } - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-redirect-policy.json b/test/fixtures/policy/dependencies/dependencies-redirect-policy.json deleted file mode 100644 index c5b73403d5f864..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-redirect-policy.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": { - "../dep.js": "../dep.js" - } - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json b/test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json deleted file mode 100644 index a2bcb2676223d3..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-redirect-unknown-builtin-policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": { - "../dep.js": "node:404" - } - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json b/test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json deleted file mode 100644 index 0cf33b16363352..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-scopes-and-resources-policy.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "resources": { - "../multi-deps.js": { - "integrity": true, - "cascade": true - } - }, - "scopes": { - "../": { - "integrity": true, - "dependencies": true - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-scopes-policy.json b/test/fixtures/policy/dependencies/dependencies-scopes-policy.json deleted file mode 100644 index 2af78b0fa945bf..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-scopes-policy.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "scopes": { - "../": { - "integrity": true, - "dependencies": true - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json b/test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json deleted file mode 100644 index 7ce0b0262e48a8..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-scopes-relative-specifier.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "scopes": { - "file:": { - "integrity": true, - "cascade": true, - "dependencies": { - "fs": "node:fs", - "../dep.js": "node:fs" - } - } - } -} diff --git a/test/fixtures/policy/dependencies/dependencies-wildcard-policy.json b/test/fixtures/policy/dependencies/dependencies-wildcard-policy.json deleted file mode 100644 index 04ae9a318f6dc0..00000000000000 --- a/test/fixtures/policy/dependencies/dependencies-wildcard-policy.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "resources": { - "../parent.js": { - "integrity": true, - "dependencies": true - }, - "../dep.js": { - "integrity": true - } - } -} \ No newline at end of file diff --git a/test/fixtures/policy/main.mjs b/test/fixtures/policy/main.mjs deleted file mode 100644 index feac7e3b8c946c..00000000000000 --- a/test/fixtures/policy/main.mjs +++ /dev/null @@ -1,2 +0,0 @@ -'use strict'; -export default 'main.mjs'; diff --git a/test/fixtures/policy/multi-deps.js b/test/fixtures/policy/multi-deps.js deleted file mode 100644 index 51cdf61783989a..00000000000000 --- a/test/fixtures/policy/multi-deps.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; -require('fs'); -require('process'); diff --git a/test/fixtures/policy/parent.js b/test/fixtures/policy/parent.js deleted file mode 100644 index b821bfee184a35..00000000000000 --- a/test/fixtures/policy/parent.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; -// Included in parent-policy.json -require('./dep.js'); diff --git a/test/fixtures/policy/process-binding/app.js b/test/fixtures/policy/process-binding/app.js deleted file mode 100644 index 16e26d12a16028..00000000000000 --- a/test/fixtures/policy/process-binding/app.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -const assert = require('assert'); - -assert.throws(() => { process.binding(); }, { - code: 'ERR_ACCESS_DENIED' -}); -assert.throws(() => { process._linkedBinding(); }, { - code: 'ERR_ACCESS_DENIED' -}); diff --git a/test/fixtures/policy/process-binding/policy.json b/test/fixtures/policy/process-binding/policy.json deleted file mode 100644 index 81f8b40c90a069..00000000000000 --- a/test/fixtures/policy/process-binding/policy.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resources": { - "./app.js": { - "integrity": true, - "dependencies": { - "assert": true - } - } - } -} diff --git a/test/node-api/test_policy/binding.c b/test/node-api/test_policy/binding.c deleted file mode 100644 index 3be31e18456bd9..00000000000000 --- a/test/node-api/test_policy/binding.c +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include "../../js-native-api/common.h" -#include - -static napi_value Method(napi_env env, napi_callback_info info) { - napi_value world; - const char* str = "world"; - size_t str_len = strlen(str); - NODE_API_CALL(env, napi_create_string_utf8(env, str, str_len, &world)); - return world; -} - -NAPI_MODULE_INIT() { - napi_property_descriptor desc = DECLARE_NODE_API_PROPERTY("hello", Method); - NODE_API_CALL(env, napi_define_properties(env, exports, 1, &desc)); - return exports; -} diff --git a/test/node-api/test_policy/binding.gyp b/test/node-api/test_policy/binding.gyp deleted file mode 100644 index 62381d5e54f22b..00000000000000 --- a/test/node-api/test_policy/binding.gyp +++ /dev/null @@ -1,8 +0,0 @@ -{ - "targets": [ - { - "target_name": "binding", - "sources": [ "binding.c" ] - } - ] -} diff --git a/test/node-api/test_policy/test_policy.js b/test/node-api/test_policy/test_policy.js deleted file mode 100644 index f14ceff3c4537b..00000000000000 --- a/test/node-api/test_policy/test_policy.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; -const common = require('../../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); - -const assert = require('assert'); -const tmpdir = require('../../common/tmpdir'); -const { spawnSync } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const { pathToFileURL } = require('url'); - -tmpdir.refresh(); - -function hash(algo, body) { - const h = crypto.createHash(algo); - h.update(body); - return h.digest('base64'); -} - -const policyFilepath = tmpdir.resolve('policy'); - -const depFilepath = require.resolve(`./build/${common.buildType}/binding.node`); -const depURL = pathToFileURL(depFilepath); - -const depBody = fs.readFileSync(depURL); -function writePolicy(...resources) { - const manifest = { resources: {} }; - for (const { url, integrity } of resources) { - manifest.resources[url] = { integrity }; - } - fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2)); -} - - -function test(shouldFail, resources) { - writePolicy(...resources); - const { status, stdout, stderr } = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - depFilepath, - ]); - - console.log(stdout.toString(), stderr.toString()); - if (shouldFail) { - assert.notStrictEqual(status, 0); - } else { - assert.strictEqual(status, 0); - } -} - -test(false, [{ - url: depURL, - integrity: `sha256-${hash('sha256', depBody)}`, -}]); -test(true, [{ - url: depURL, - integrity: `sha256akjsalkjdlaskjdk-${hash('sha256', depBody)}`, -}]); diff --git a/test/parallel/test-compile-cache-policy.js b/test/parallel/test-compile-cache-policy.js deleted file mode 100644 index fd6fe36bcc0cd9..00000000000000 --- a/test/parallel/test-compile-cache-policy.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -// This tests NODE_COMPILE_CACHE is disabled when policy is used. - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); - -const { spawnSyncAndAssert } = require('../common/child_process'); -const assert = require('assert'); -const fs = require('fs'); -const tmpdir = require('../common/tmpdir'); -const fixtures = require('../common/fixtures'); - -{ - tmpdir.refresh(); - const dir = tmpdir.resolve('.compile_cache_dir'); - const script = fixtures.path('policy', 'parent.js'); - const policy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-policy.json'); - spawnSyncAndAssert( - process.execPath, - ['--experimental-policy', policy, script], - { - env: { - ...process.env, - NODE_DEBUG_NATIVE: 'COMPILE_CACHE', - NODE_COMPILE_CACHE: dir - }, - cwd: tmpdir.path - }, - { - stderr: /skipping cache because policy is enabled/ - }); - assert(!fs.existsSync(dir)); -} diff --git a/test/parallel/test-policy-crypto-default-encoding.js b/test/parallel/test-policy-crypto-default-encoding.js deleted file mode 100644 index 1f62b4d85a3c4f..00000000000000 --- a/test/parallel/test-policy-crypto-default-encoding.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -const encodings = ['buffer', 'utf8', 'utf16le', 'latin1', 'base64', 'hex']; - -for (const encoding of encodings) { - const dep = fixtures.path('policy', 'crypto-default-encoding', 'parent.js'); - const depPolicy = fixtures.path( - 'policy', - 'crypto-default-encoding', - 'policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ], - { - env: { - ...process.env, - DEFAULT_ENCODING: encoding - } - } - ); - assert.strictEqual(status, 0); -} diff --git a/test/parallel/test-policy-crypto-hash-tampering.js b/test/parallel/test-policy-crypto-hash-tampering.js deleted file mode 100644 index 96066defb59a1b..00000000000000 --- a/test/parallel/test-policy-crypto-hash-tampering.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -const mainPath = fixtures.path('policy', 'crypto-hash-tampering', 'main.js'); -const policyPath = fixtures.path( - 'policy', - 'crypto-hash-tampering', - 'policy.json'); -const { status, stderr } = - spawnSync(process.execPath, ['--experimental-policy', policyPath, mainPath], { encoding: 'utf8' }); -assert.strictEqual(status, 1); -assert(stderr.includes('sha384-Bnp/T8gFNzT9mHj2G/AeuMH8LcAQ4mljw15nxBNl5yaGM7VgbMzDT7O4+dXZTJJn')); diff --git a/test/parallel/test-policy-dependencies.js b/test/parallel/test-policy-dependencies.js deleted file mode 100644 index decc164dadd253..00000000000000 --- a/test/parallel/test-policy-dependencies.js +++ /dev/null @@ -1,146 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -const dep = fixtures.path('policy', 'parent.js'); -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-policy.json'); - const { status, stderr, stdout } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - console.log('%s\n%s', stderr, stdout); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-builtin-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-redirect-unknown-builtin-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 1); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-wildcard-policy.json'); - const { status, stderr, stdout } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - console.log('%s\n%s', stderr, stdout); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-empty-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 1); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-missing-policy-default-true.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-missing-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 1); -} -{ - // Regression test for https://github.com/nodejs/node/issues/37812 - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-missing-export-policy.json'); - const { status, stderr } = spawnSync( - process.execPath, - [ - '--experimental-policy', - depPolicy, - fixtures.path('policy', 'bad-main.mjs'), - ] - ); - assert.strictEqual(status, 1); - assert.match( - `${stderr}`, - /SyntaxError: Named export 'doesNotExist' not found\./, - new Error('Should give the real SyntaxError and position')); -} -{ - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-scopes-relative-specifier.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', - depPolicy, - fixtures.path('policy', 'canonicalize.mjs'), - ] - ); - assert.strictEqual( - status, - 0, - new Error( - 'policies should canonicalize specifiers by default prior to matching') - ); -} diff --git a/test/parallel/test-policy-dependency-conditions.js b/test/parallel/test-policy-dependency-conditions.js deleted file mode 100644 index dec17aa22984b0..00000000000000 --- a/test/parallel/test-policy-dependency-conditions.js +++ /dev/null @@ -1,123 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -const common = require('../common'); - -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const Manifest = require('internal/policy/manifest').Manifest; - - -const assert = require('assert'); - -const { debuglog } = require('util'); -const debug = debuglog('test'); - -const conditionTreePermutations = [ - ['default'], - ['import'], - ['node'], - ['require'], - ['require', 'import'], - ['import', 'require'], - ['default', 'require'], - ['require', 'default'], - ['node', 'require'], - ['require', 'node'], -]; -for (const totalDepth of [1, 2, 3]) { - const conditionTrees = []; - function calc(depthLeft = 0, path = []) { - if (depthLeft) { - for (const conditions of conditionTreePermutations) { - calc(depthLeft - 1, [...path, conditions]); - } - } else { - conditionTrees.push(path); - } - } - calc(totalDepth, []); - let nextURLId = 1; - function getUniqueHREF() { - const id = nextURLId++; - return `test:${id}`; - } - for (const tree of conditionTrees) { - const root = {}; - const targets = [root]; - const offsets = [-1]; - const order = []; - while (offsets.length) { - const depth = offsets.length - 1; - offsets[depth]++; - const conditionOffset = offsets[depth]; - const conditionsForDepth = tree[depth]; - if (conditionOffset >= conditionsForDepth.length) { - offsets.pop(); - continue; - } - let target; - if (depth === tree.length - 1) { - target = getUniqueHREF(); - order.push({ - target, - conditions: new Set( - offsets.map( - (conditionOffset, depth) => tree[depth][conditionOffset] - ) - ) - }); - } else { - target = {}; - targets[depth + 1] = target; - offsets.push(-1); - } - const condition = tree[depth][conditionOffset]; - targets[depth][condition] = target; - } - const manifest = new Manifest({ - resources: { - 'test:_': { - dependencies: { - _: root - } - } - } - }); - const redirector = manifest.getDependencyMapper('test:_'); - for (const { target, conditions } of order) { - const result = redirector.resolve('_', conditions).href; - if (result !== target) { - // If we didn't hit the target, make sure a target prior to this one - // matched, including conditions - searchPriorTargets: - for (const { target: preTarget, conditions: preConditions } of order) { - if (result === preTarget) { - // Ensure that the current conditions are a super set of the - // prior target - for (const preCondition of preConditions) { - if (conditions.has(preCondition) !== true) { - continue searchPriorTargets; - } - } - break searchPriorTargets; - } - if (preTarget === target) { - debug( - 'dependencies %O expected ordering %O and trying for %j ' + - 'no prior targets matched', - root, - order, - target - ); - // THIS WILL ALWAYS FAIL, but we want that error message - assert.strictEqual( - result, target - ); - } - } - } - } - } -} diff --git a/test/parallel/test-policy-integrity-flag.js b/test/parallel/test-policy-integrity-flag.js deleted file mode 100644 index 1b4b013b900edd..00000000000000 --- a/test/parallel/test-policy-integrity-flag.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const fs = require('fs'); -const crypto = require('crypto'); - -const depPolicy = fixtures.path('policy', 'dep-policy.json'); -const dep = fixtures.path('policy', 'dep.js'); - -const emptyHash = crypto.createHash('sha512'); -emptyHash.update(''); -const emptySRI = `sha512-${emptyHash.digest('base64')}`; -const policyHash = crypto.createHash('sha512'); -policyHash.update(fs.readFileSync(depPolicy)); - -/* eslint-disable @stylistic/js/max-len */ -// When using \n only -const nixPolicySRI = 'sha512-u/nXI6UacK5fKDC2bopcgnuQY4JXJKlK3dESO3GIKKxwogVHjJqpF9rgk7Zw+TJXIc96xBUWKHuUgOzic8/4tQ=='; -// When \n is turned into \r\n -const windowsPolicySRI = 'sha512-OeyCPRo4OZMosHyquZXDHpuU1F4KzG9UHFnn12FMaHsvqFUt3TFZ+7wmZE7ThZ5rsQWkUjc9ZH0knGZ2e8BYPQ=='; -/* eslint-enable @stylistic/js/max-len */ - -const depPolicySRI = `${nixPolicySRI} ${windowsPolicySRI}`; -{ - const { status, stderr } = spawnSync( - process.execPath, - [ - '--policy-integrity', emptySRI, - '--experimental-policy', depPolicy, dep, - ] - ); - - assert.ok(stderr.includes('ERR_MANIFEST_ASSERT_INTEGRITY')); - assert.strictEqual(status, 1); -} -{ - const { status, stderr } = spawnSync( - process.execPath, - [ - '--policy-integrity', '', - '--experimental-policy', depPolicy, dep, - ] - ); - - assert.ok(stderr.includes('--policy-integrity')); - assert.strictEqual(status, 9); -} -{ - const { status, stderr } = spawnSync( - process.execPath, - [ - '--policy-integrity', depPolicySRI, - '--experimental-policy', depPolicy, dep, - ] - ); - - assert.strictEqual(status, 0, `status: ${status}\nstderr: ${stderr}`); -} diff --git a/test/parallel/test-policy-manifest.js b/test/parallel/test-policy-manifest.js deleted file mode 100644 index 3c4b1695d28835..00000000000000 --- a/test/parallel/test-policy-manifest.js +++ /dev/null @@ -1,157 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) - common.skip('missing crypto'); - -common.requireNoPackageJSONAbove(); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const { cpSync, rmSync } = require('fs'); -const fixtures = require('../common/fixtures.js'); -const tmpdir = require('../common/tmpdir.js'); - -{ - const policyFilepath = fixtures.path('policy-manifest', 'invalid.json'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - './fhqwhgads.js', - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /ERR_MANIFEST_INVALID_SPECIFIER/); - assert.match(stderr, /pattern needs to have a single trailing "\*"/); -} - -{ - tmpdir.refresh(); - const policyFilepath = tmpdir.resolve('file with % in its name.json'); - cpSync(fixtures.path('policy-manifest', 'invalid.json'), policyFilepath); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - './fhqwhgads.js', - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /ERR_MANIFEST_INVALID_SPECIFIER/); - assert.match(stderr, /pattern needs to have a single trailing "\*"/); - rmSync(policyFilepath); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - '-e', - 'require("os").cpus()', - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/); - assert.match(stderr, /does not list module as a dependency specifier for conditions: require, node, node-addons/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-module-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/); - assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-resource-exit.json'); - const objectDefinePropertyBypass = fixtures.path('policy-manifest', 'object-define-property-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - objectDefinePropertyBypass, - ]); - - assert.strictEqual(result.status, 0); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-module-proto-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /ERR_MANIFEST_DEPENDENCY_MISSING/); - assert.match(stderr, /does not list os as a dependency specifier for conditions: require, node, node-addons/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'module-constructor-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'manifest-impersonate.json'); - const createRequireBypass = fixtures.path('policy-manifest', 'createRequire-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - createRequireBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} - -{ - const policyFilepath = fixtures.path('policy-manifest', 'onerror-exit.json'); - const mainModuleBypass = fixtures.path('policy-manifest', 'main-constructor-extensions-bypass.js'); - const result = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - mainModuleBypass, - ]); - - assert.notStrictEqual(result.status, 0); - const stderr = result.stderr.toString(); - assert.match(stderr, /TypeError/); -} diff --git a/test/parallel/test-policy-parse-integrity.js b/test/parallel/test-policy-parse-integrity.js deleted file mode 100644 index 3295bed28fb5a8..00000000000000 --- a/test/parallel/test-policy-parse-integrity.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -tmpdir.refresh(); - -function hash(algo, body) { - const h = crypto.createHash(algo); - h.update(body); - return h.digest('base64'); -} - -const tmpdirPath = tmpdir.resolve('test-policy-parse-integrity'); -fs.rmSync(tmpdirPath, { maxRetries: 3, recursive: true, force: true }); -fs.mkdirSync(tmpdirPath, { recursive: true }); - -const policyFilepath = path.join(tmpdirPath, 'policy'); - -const parentFilepath = path.join(tmpdirPath, 'parent.js'); -const parentBody = "require('./dep.js')"; - -const depFilepath = path.join(tmpdirPath, 'dep.js'); -const depURL = pathToFileURL(depFilepath); -const depBody = ''; - -fs.writeFileSync(parentFilepath, parentBody); -fs.writeFileSync(depFilepath, depBody); - -const tmpdirURL = pathToFileURL(tmpdirPath); -if (!tmpdirURL.pathname.endsWith('/')) { - tmpdirURL.pathname += '/'; -} - -const packageFilepath = path.join(tmpdirPath, 'package.json'); -const packageURL = pathToFileURL(packageFilepath); -const packageBody = '{"main": "dep.js"}'; - -function test({ shouldFail, integrity, manifest = {} }) { - manifest.resources = {}; - const resources = { - [packageURL]: { - body: packageBody, - integrity: `sha256-${hash('sha256', packageBody)}` - }, - [depURL]: { - body: depBody, - integrity - } - }; - for (const [url, { body, integrity }] of Object.entries(resources)) { - manifest.resources[url] = { - integrity, - }; - fs.writeFileSync(new URL(url, tmpdirURL.href), body); - } - fs.writeFileSync(policyFilepath, JSON.stringify(manifest, null, 2)); - const { status } = spawnSync(process.execPath, [ - '--experimental-policy', - policyFilepath, - depFilepath, - ]); - if (shouldFail) { - assert.notStrictEqual(status, 0); - } else { - assert.strictEqual(status, 0); - } -} - -test({ - shouldFail: false, - integrity: `sha256-${hash('sha256', depBody)}`, -}); -test({ - shouldFail: true, - integrity: `1sha256-${hash('sha256', depBody)}`, -}); -test({ - shouldFail: true, - integrity: 'hoge', -}); -test({ - shouldFail: true, - integrity: `sha256-${hash('sha256', depBody)}sha256-${hash( - 'sha256', - depBody - )}`, -}); -test({ - shouldFail: true, - integrity: `sha256-${hash('sha256', 'file:///')}`, - manifest: { - onerror: 'exit' - } -}); -test({ - shouldFail: false, - integrity: `sha256-${hash('sha256', 'file:///')}`, - manifest: { - onerror: 'log' - } -}); diff --git a/test/parallel/test-policy-process-binding.js b/test/parallel/test-policy-process-binding.js deleted file mode 100644 index 34a6954e6c47c8..00000000000000 --- a/test/parallel/test-policy-process-binding.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const common = require('../common'); -common.requireNoPackageJSONAbove(); - -if (!common.hasCrypto) - common.skip('missing crypto'); - -const fixtures = require('../common/fixtures'); - -const assert = require('node:assert'); -const { spawnSync } = require('node:child_process'); - -const dep = fixtures.path('policy', 'process-binding', 'app.js'); -const depPolicy = fixtures.path( - 'policy', - 'process-binding', - 'policy.json'); -const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ], - { - stdio: 'inherit' - }, -); -assert.strictEqual(status, 0); diff --git a/test/parallel/test-policy-scopes-dependencies.js b/test/parallel/test-policy-scopes-dependencies.js deleted file mode 100644 index f4b93a08e58c47..00000000000000 --- a/test/parallel/test-policy-scopes-dependencies.js +++ /dev/null @@ -1,342 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -const common = require('../common'); - -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const Manifest = require('internal/policy/manifest').Manifest; -const assert = require('assert'); - -// #region files -{ - const baseURLs = [ - // Localhost is special cased in spec - 'file://localhost/root', - 'file:///root', - 'file:///', - 'file:///root/dir1', - 'file:///root/dir1/', - 'file:///root/dir1/dir2', - 'file:///root/dir1/dir2/', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'file:///': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - '': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - '': { - dependencies: true - }, - 'file:': { - cascade: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'file:': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest - .getDependencyMapper(href) - .resolve('fs'), - true); - } - - assert.strictEqual( - manifest - .getDependencyMapper('file://host/') - .resolve('fs'), - true); - } - { - const manifest = new Manifest({ - resources: { - 'file:///root/dir1': { - dependencies: { - fs: 'test:fs1' - } - }, - 'file:///root/dir1/isolated': {}, - 'file:///root/dir1/cascade': { - cascade: true - } - }, - scopes: { - 'file:///root/dir1/': { - dependencies: { - fs: 'test:fs2' - } - }, - 'file:///root/dir1/censor/': { - }, - } - }); - - for (const href of baseURLs) { - const redirector = manifest.getDependencyMapper(href); - if (href.startsWith('file:///root/dir1/')) { - assert.strictEqual( - redirector.resolve('fs').href, - 'test:fs2' - ); - } else if (href === 'file:///root/dir1') { - assert.strictEqual( - redirector.resolve('fs').href, - 'test:fs1' - ); - } else { - assert.strictEqual(redirector.resolve('fs'), null); - } - } - - assert.strictEqual( - manifest - .getDependencyMapper('file:///root/dir1/isolated') - .resolve('fs'), - null - ); - assert.strictEqual( - manifest - .getDependencyMapper('file:///root/dir1/cascade') - .resolve('fs').href, - 'test:fs2' - ); - assert.strictEqual( - manifest - .getDependencyMapper('file:///root/dir1/censor/foo') - .resolve('fs'), - null - ); - } -} -// #endregion -// #region data -{ - const baseURLs = [ - 'data:text/javascript,0', - 'data:text/javascript,0/1', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'data:text/': { - dependencies: { - fs: true - } - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - null); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:/': { - dependencies: { - fs: true - } - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - null); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:': { - dependencies: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:text/javascript,0/': { - dependencies: { - fs: 'test:fs1' - } - }, - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.getDependencyMapper(href).resolve('fs'), - null); - } - } -} -// #endregion -// #region blob -{ - { - const manifest = new Manifest({ - scopes: { - 'https://example.com/': { - dependencies: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - 'https://example.com': { - dependencies: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - null); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - null); - } - { - const manifest = new Manifest({ - scopes: { - // FIXME - 'https://example.com/': { - dependencies: true - }, - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:': { - dependencies: true - }, - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.strictEqual( - manifest - .getDependencyMapper('blob:https://example.com/has-origin') - .resolve('fs'), - null); - assert.strictEqual( - manifest - .getDependencyMapper('blob:foo').resolve('fs'), - true - ); - } -} -// #endregion diff --git a/test/parallel/test-policy-scopes-integrity.js b/test/parallel/test-policy-scopes-integrity.js deleted file mode 100644 index 16d9eb04eb7347..00000000000000 --- a/test/parallel/test-policy-scopes-integrity.js +++ /dev/null @@ -1,316 +0,0 @@ -'use strict'; -// Flags: --expose-internals - -const common = require('../common'); - -if (!common.hasCrypto) common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const Manifest = require('internal/policy/manifest').Manifest; -const assert = require('assert'); - -// #region files -{ - const baseURLs = [ - // Localhost is special cased in spec - 'file://localhost/root', - 'file:///root', - 'file:///', - 'file:///root/dir1', - 'file:///root/dir1/', - 'file:///root/dir1/dir2', - 'file:///root/dir1/dir2/', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'file:///': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.assertIntegrity(href), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, null), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, ''), - true - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'file:': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual( - manifest.assertIntegrity(href), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, null), - true - ); - assert.strictEqual( - manifest.assertIntegrity(href, ''), - true - ); - } - } - { - const manifest = new Manifest({ - resources: { - 'file:///root/dir1/isolated': {}, - 'file:///root/dir1/cascade': { - cascade: true - } - }, - scopes: { - 'file:///root/dir1/': { - integrity: true, - }, - 'file:///root/dir1/dir2/': { - cascade: true, - }, - 'file:///root/dir1/censor/': { - }, - } - }); - assert.throws( - () => { - manifest.assertIntegrity('file:///root/dir1/isolated'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - assert.strictEqual( - manifest.assertIntegrity('file:///root/dir1/cascade'), - true - ); - assert.strictEqual( - manifest.assertIntegrity('file:///root/dir1/enoent'), - true - ); - assert.strictEqual( - manifest.assertIntegrity('file:///root/dir1/dir2/enoent'), - true - ); - assert.throws( - () => { - manifest.assertIntegrity('file:///root/dir1/censor/enoent'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } -} -// #endregion -// #region data -{ - const baseURLs = [ - 'data:text/javascript,0', - 'data:text/javascript,0/1', - ]; - - { - const manifest = new Manifest({ - scopes: { - 'data:text/': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.throws( - () => { - manifest.assertIntegrity(href); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:/': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.throws( - () => { - manifest.assertIntegrity(href); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:': { - integrity: true - } - } - }); - - for (const href of baseURLs) { - assert.strictEqual(manifest.assertIntegrity(href), true); - } - } - { - const manifest = new Manifest({ - scopes: { - 'data:text/javascript,0/': { - integrity: true - }, - } - }); - - for (const href of baseURLs) { - assert.throws( - () => { - manifest.assertIntegrity(href); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - } -} -// #endregion -// #region blob -{ - { - const manifest = new Manifest({ - scopes: { - 'https://example.com/': { - integrity: true - } - } - }); - - assert.strictEqual( - manifest.assertIntegrity('blob:https://example.com/has-origin'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - } - }); - - assert.throws( - () => { - manifest.assertIntegrity('blob:https://example.com/has-origin'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:https://example.com/has-origin': { - cascade: true - } - } - }); - - assert.throws( - () => { - manifest.assertIntegrity('blob:https://example.com/has-origin'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - } - { - const manifest = new Manifest({ - resources: { - 'blob:https://example.com/has-origin': { - cascade: true - } - }, - scopes: { - 'https://example.com': { - integrity: true - } - } - }); - - assert.strictEqual( - manifest.assertIntegrity('blob:https://example.com/has-origin'), - true - ); - } - { - const manifest = new Manifest({ - scopes: { - 'blob:': { - integrity: true - }, - 'https://example.com': { - cascade: true - } - } - }); - - assert.throws( - () => { - manifest.assertIntegrity('blob:https://example.com/has-origin'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); - assert.strictEqual( - manifest.assertIntegrity('blob:foo'), - true - ); - } -} -// #endregion -// #startonerror -{ - const manifest = new Manifest({ - scopes: { - 'file:///': { - integrity: true - } - }, - onerror: 'throw' - }); - assert.throws( - () => { - manifest.assertIntegrity('http://example.com'); - }, - /ERR_MANIFEST_ASSERT_INTEGRITY/ - ); -} -{ - assert.throws( - () => { - new Manifest({ - scopes: { - 'file:///': { - integrity: true - } - }, - onerror: 'unknown' - }); - }, - /ERR_MANIFEST_UNKNOWN_ONERROR/ - ); -} -// #endonerror diff --git a/test/parallel/test-policy-scopes.js b/test/parallel/test-policy-scopes.js deleted file mode 100644 index 43789713cc979a..00000000000000 --- a/test/parallel/test-policy-scopes.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const common = require('../common'); -if (!common.hasCrypto) - common.skip('missing crypto'); -common.requireNoPackageJSONAbove(); - -const fixtures = require('../common/fixtures'); - -const assert = require('assert'); -const { spawnSync } = require('child_process'); - -{ - const dep = fixtures.path('policy', 'main.mjs'); - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-scopes-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} -{ - const dep = fixtures.path('policy', 'multi-deps.js'); - const depPolicy = fixtures.path( - 'policy', - 'dependencies', - 'dependencies-scopes-and-resources-policy.json'); - const { status } = spawnSync( - process.execPath, - [ - '--experimental-policy', depPolicy, dep, - ] - ); - assert.strictEqual(status, 0); -} diff --git a/test/pummel/test-policy-integrity-dep.js b/test/pummel/test-policy-integrity-dep.js deleted file mode 100644 index d5a23d96bc2593..00000000000000 --- a/test/pummel/test-policy-integrity-dep.js +++ /dev/null @@ -1,365 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (policyPath === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - policyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - process.kill(process.pid, 'SIGKILL'); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension, packageType) { - if (extension === '.js') { - return packageType === 'module' ? 'module' : 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageType: ['no-package-json', 'module', 'commonjs'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const effectivePackageType = - permutation.packageType === 'module' ? 'module' : 'commonjs'; - const parentFormat = fileExtensionFormat( - permutation.parentExtension, - effectivePackageType, - ); - const depFormat = fileExtensionFormat( - permutation.depExtension, - effectivePackageType, - ); - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - - const packageJSON = { - main: depPath, - type: permutation.packageType, - }; - if (permutation.packageType === 'no-field') { - delete packageJSON.type; - } - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - const hasParent = permutation.preloads.includes('parent'); - if (hasParent) { - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - } - - if (permutation.packageType !== 'no-package-json') { - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - } - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: depPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-parent-commonjs.js b/test/pummel/test-policy-integrity-parent-commonjs.js deleted file mode 100644 index 07eee598117ba1..00000000000000 --- a/test/pummel/test-policy-integrity-parent-commonjs.js +++ /dev/null @@ -1,352 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - willDeletePolicy, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath; - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (cliPolicy === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - cliPolicy, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const entryPath = parentPath; - const packageJSON = { - main: entryPath, - type: 'commonjs', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath, - willDeletePolicy: false, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-parent-module.js b/test/pummel/test-policy-integrity-parent-module.js deleted file mode 100644 index a09243ea10f529..00000000000000 --- a/test/pummel/test-policy-integrity-parent-module.js +++ /dev/null @@ -1,352 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - willDeletePolicy, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath; - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (cliPolicy === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - cliPolicy, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'module'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const entryPath = parentPath; - const packageJSON = { - main: entryPath, - type: 'module', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath, - willDeletePolicy: false, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-parent-no-package-json.js b/test/pummel/test-policy-integrity-parent-no-package-json.js deleted file mode 100644 index a6461a9a5835c3..00000000000000 --- a/test/pummel/test-policy-integrity-parent-no-package-json.js +++ /dev/null @@ -1,324 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - willDeletePolicy, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - const cliPolicy = willDeletePolicy ? tmpPolicyPath : policyPath; - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - if (cliPolicy === tmpPolicyPath) { - fs.writeFileSync(tmpPolicyPath, manifestBody); - } - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - cliPolicy, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const entryPath = parentPath; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath, - willDeletePolicy: false, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-worker-commonjs.js b/test/pummel/test-policy-integrity-worker-commonjs.js deleted file mode 100644 index acc4298eb7b23b..00000000000000 --- a/test/pummel/test-policy-integrity-worker-commonjs.js +++ /dev/null @@ -1,375 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; -const workerSpawningBody = ` - const path = require('path'); - const { Worker } = require('worker_threads'); - if (!process.env.PARENT_FILE) { - console.error( - 'missing required PARENT_FILE env to determine worker entry point' - ); - process.exit(33); - } - if (!process.env.DELETABLE_POLICY_FILE) { - console.error( - 'missing required DELETABLE_POLICY_FILE env to check reloading' - ); - process.exit(33); - } - const w = new Worker(path.resolve(process.env.PARENT_FILE)); - w.on('exit', (status) => process.exit(status === 0 ? 0 : 1)); -`; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - - fs.writeFileSync(tmpPolicyPath, manifestBody); - - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - tmpPolicyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const workerSpawnerPath = './worker-spawner.cjs'; - const packageJSON = { - main: workerSpawnerPath, - type: 'commonjs', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - resources[workerSpawnerPath] = { - body: workerSpawningBody, - integrities: hash('sha256', workerSpawningBody), - }; - - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: workerSpawnerPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-worker-module.js b/test/pummel/test-policy-integrity-worker-module.js deleted file mode 100644 index 65a04841415da9..00000000000000 --- a/test/pummel/test-policy-integrity-worker-module.js +++ /dev/null @@ -1,373 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; -const workerSpawningBody = ` - const path = require('path'); - const { Worker } = require('worker_threads'); - if (!process.env.PARENT_FILE) { - console.error( - 'missing required PARENT_FILE env to determine worker entry point' - ); - process.exit(33); - } - if (!process.env.DELETABLE_POLICY_FILE) { - console.error( - 'missing required DELETABLE_POLICY_FILE env to check reloading' - ); - process.exit(33); - } - const w = new Worker(path.resolve(process.env.PARENT_FILE)); - w.on('exit', (status) => process.exit(status === 0 ? 0 : 1)); -`; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - - fs.writeFileSync(tmpPolicyPath, manifestBody); - - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - tmpPolicyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'module'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const workerSpawnerPath = './worker-spawner.cjs'; - const packageJSON = { - main: workerSpawnerPath, - type: 'module', - }; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - resources[workerSpawnerPath] = { - body: workerSpawningBody, - integrities: hash('sha256', workerSpawningBody), - }; - - let packageBody = JSON.stringify(packageJSON, null, 2); - let packageIntegrities = hash('sha256', packageBody); - if ( - permutation.parentExtension !== '.js' || - permutation.depExtension !== '.js' - ) { - // NO PACKAGE LOOKUP - continue; - } - if (permutation.packageIntegrity === 'invalid') { - packageJSON['//'] = 'INVALID INTEGRITY'; - packageBody = JSON.stringify(packageJSON, null, 2); - shouldSucceed = false; - } else if (permutation.packageIntegrity === 'missing') { - packageIntegrities = []; - shouldSucceed = false; - } else if (permutation.packageIntegrity !== 'match') { - throw new Error('unreachable'); - } - resources['./package.json'] = { - body: packageBody, - integrities: packageIntegrities, - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: workerSpawnerPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/test/pummel/test-policy-integrity-worker-no-package-json.js b/test/pummel/test-policy-integrity-worker-no-package-json.js deleted file mode 100644 index fc90f73a03cf31..00000000000000 --- a/test/pummel/test-policy-integrity-worker-no-package-json.js +++ /dev/null @@ -1,345 +0,0 @@ -'use strict'; - -const common = require('../common'); - -if (!common.hasCrypto) { - common.skip('missing crypto'); -} - -if (common.isPi) { - common.skip('Too slow for Raspberry Pi devices'); -} - -common.requireNoPackageJSONAbove(); - -const { debuglog } = require('util'); -const debug = debuglog('test'); -const tmpdir = require('../common/tmpdir'); -const assert = require('assert'); -const { spawnSync, spawn } = require('child_process'); -const crypto = require('crypto'); -const fs = require('fs'); -const path = require('path'); -const { pathToFileURL } = require('url'); - -const cpus = require('os').availableParallelism(); - -function hash(algo, body) { - const values = []; - { - const h = crypto.createHash(algo); - h.update(body); - values.push(`${algo}-${h.digest('base64')}`); - } - { - const h = crypto.createHash(algo); - h.update(body.replace('\n', '\r\n')); - values.push(`${algo}-${h.digest('base64')}`); - } - return values; -} - -const policyPath = './policy.json'; -const parentBody = { - commonjs: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - require(process.env.DEP_FILE) - `, - module: ` - if (!process.env.DEP_FILE) { - console.error( - 'missing required DEP_FILE env to determine dependency' - ); - process.exit(33); - } - import(process.env.DEP_FILE) - `, -}; -const workerSpawningBody = ` - const path = require('path'); - const { Worker } = require('worker_threads'); - if (!process.env.PARENT_FILE) { - console.error( - 'missing required PARENT_FILE env to determine worker entry point' - ); - process.exit(33); - } - if (!process.env.DELETABLE_POLICY_FILE) { - console.error( - 'missing required DELETABLE_POLICY_FILE env to check reloading' - ); - process.exit(33); - } - const w = new Worker(path.resolve(process.env.PARENT_FILE)); - w.on('exit', (status) => process.exit(status === 0 ? 0 : 1)); -`; - -let nextTestId = 1; -function newTestId() { - return nextTestId++; -} -tmpdir.refresh(); -common.requireNoPackageJSONAbove(tmpdir.path); - -let spawned = 0; -const toSpawn = []; -function queueSpawn(opts) { - toSpawn.push(opts); - drainQueue(); -} - -function drainQueue() { - if (spawned > cpus) { - return; - } - if (toSpawn.length) { - const config = toSpawn.shift(); - const { - shouldSucceed, - preloads, - entryPath, - onError, - resources, - parentPath, - depPath, - } = config; - const testId = newTestId(); - const configDirPath = path.join( - tmpdir.path, - `test-policy-integrity-permutation-${testId}`, - ); - const tmpPolicyPath = path.join( - tmpdir.path, - `deletable-policy-${testId}.json`, - ); - - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - fs.mkdirSync(configDirPath, { recursive: true }); - const manifest = { - onerror: onError, - resources: {}, - }; - const manifestPath = path.join(configDirPath, policyPath); - for (const [resourcePath, { body, integrities }] of Object.entries( - resources, - )) { - const filePath = path.join(configDirPath, resourcePath); - if (integrities !== null) { - manifest.resources[pathToFileURL(filePath).href] = { - integrity: integrities.join(' '), - dependencies: true, - }; - } - fs.writeFileSync(filePath, body, 'utf8'); - } - const manifestBody = JSON.stringify(manifest); - fs.writeFileSync(manifestPath, manifestBody); - - fs.writeFileSync(tmpPolicyPath, manifestBody); - - const spawnArgs = [ - process.execPath, - [ - '--unhandled-rejections=strict', - '--experimental-policy', - tmpPolicyPath, - ...preloads.flatMap((m) => ['-r', m]), - entryPath, - '--', - testId, - configDirPath, - ], - { - env: { - ...process.env, - DELETABLE_POLICY_FILE: tmpPolicyPath, - PARENT_FILE: parentPath, - DEP_FILE: depPath, - }, - cwd: configDirPath, - stdio: 'pipe', - }, - ]; - spawned++; - const stdout = []; - const stderr = []; - const child = spawn(...spawnArgs); - child.stdout.on('data', (d) => stdout.push(d)); - child.stderr.on('data', (d) => stderr.push(d)); - child.on('exit', (status, signal) => { - spawned--; - try { - if (shouldSucceed) { - assert.strictEqual(status, 0); - } else { - assert.notStrictEqual(status, 0); - } - } catch (e) { - console.log( - 'permutation', - testId, - 'failed', - ); - console.dir( - { config, manifest }, - { depth: null }, - ); - console.log('exit code:', status, 'signal:', signal); - console.log(`stdout: ${Buffer.concat(stdout)}`); - console.log(`stderr: ${Buffer.concat(stderr)}`); - throw e; - } - fs.rmSync(configDirPath, { maxRetries: 3, recursive: true, force: true }); - drainQueue(); - }); - } -} - -{ - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', policyPath, '--experimental-policy', policyPath], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow multiple policies'); -} -{ - const enoentFilepath = tmpdir.resolve('enoent'); - try { - fs.unlinkSync(enoentFilepath); - } catch { - // Continue regardless of error. - } - const { status } = spawnSync( - process.execPath, - ['--experimental-policy', enoentFilepath, '-e', ''], - { - stdio: 'pipe', - }, - ); - assert.notStrictEqual(status, 0, 'Should not allow missing policies'); -} - -/** - * @template {Record>} T - * @param {T} configurations - * @param {object} path - * @returns {Array<{[key: keyof T]: T[keyof configurations]}>} - */ -function permutations(configurations, path = {}) { - const keys = Object.keys(configurations); - if (keys.length === 0) { - return path; - } - const config = keys[0]; - const { [config]: values, ...otherConfigs } = configurations; - return values.flatMap((value) => { - return permutations(otherConfigs, { ...path, [config]: value }); - }); -} -const tests = new Set(); -function fileExtensionFormat(extension) { - if (extension === '.js') { - return 'commonjs'; - } else if (extension === '.mjs') { - return 'module'; - } else if (extension === '.cjs') { - return 'commonjs'; - } - throw new Error('unknown format ' + extension); -} -for (const permutation of permutations({ - preloads: [[], ['parent'], ['dep']], - onError: ['log', 'exit'], - parentExtension: ['.js', '.mjs', '.cjs'], - parentIntegrity: ['match', 'invalid', 'missing'], - depExtension: ['.js', '.mjs', '.cjs'], - depIntegrity: ['match', 'invalid', 'missing'], - packageIntegrity: ['match', 'invalid', 'missing'], -})) { - let shouldSucceed = true; - const parentPath = `./parent${permutation.parentExtension}`; - const parentFormat = fileExtensionFormat(permutation.parentExtension); - const depFormat = fileExtensionFormat(permutation.depExtension); - - // non-sensical attempt to require ESM - if (depFormat === 'module' && parentFormat === 'commonjs') { - continue; - } - const depPath = `./dep${permutation.depExtension}`; - const workerSpawnerPath = './worker-spawner.cjs'; - - const resources = { - [depPath]: { - body: '', - integrities: hash('sha256', ''), - }, - }; - if (permutation.depIntegrity === 'invalid') { - resources[depPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.depIntegrity === 'missing') { - resources[depPath].integrities = null; - shouldSucceed = false; - } else if (permutation.depIntegrity !== 'match') { - throw new Error('unreachable'); - } - if (parentFormat !== 'commonjs') { - permutation.preloads = permutation.preloads.filter((_) => _ !== 'parent'); - } - - resources[parentPath] = { - body: parentBody[parentFormat], - integrities: hash('sha256', parentBody[parentFormat]), - }; - if (permutation.parentIntegrity === 'invalid') { - resources[parentPath].body += '\n// INVALID INTEGRITY'; - shouldSucceed = false; - } else if (permutation.parentIntegrity === 'missing') { - resources[parentPath].integrities = null; - shouldSucceed = false; - } else if (permutation.parentIntegrity !== 'match') { - throw new Error('unreachable'); - } - - resources[workerSpawnerPath] = { - body: workerSpawningBody, - integrities: hash('sha256', workerSpawningBody), - }; - - if (permutation.onError === 'log') { - shouldSucceed = true; - } - tests.add( - JSON.stringify({ - onError: permutation.onError, - shouldSucceed, - entryPath: workerSpawnerPath, - preloads: permutation.preloads - .map((_) => { - return { - '': '', - 'parent': parentFormat === 'commonjs' ? parentPath : '', - 'dep': depFormat === 'commonjs' ? depPath : '', - }[_]; - }) - .filter(Boolean), - parentPath, - depPath, - resources, - }), - ); -} -debug(`spawning ${tests.size} policy integrity permutations`); - -for (const config of tests) { - const parsed = JSON.parse(config); - queueSpawn(parsed); -} diff --git a/typings/internalBinding/modules.d.ts b/typings/internalBinding/modules.d.ts index 86efd6c971ae04..b9aa518abde337 100644 --- a/typings/internalBinding/modules.d.ts +++ b/typings/internalBinding/modules.d.ts @@ -20,11 +20,7 @@ export type SerializedPackageConfig = [ export interface ModulesBinding { readPackageJSON(path: string): SerializedPackageConfig | undefined; getNearestParentPackageJSON(path: string): PackageConfig | undefined - getNearestParentPackageJSONType(path: string): [ - PackageConfig['type'], - string, // package.json path - string, // raw content - ] + getNearestParentPackageJSONType(path: string): PackageConfig['type'] getPackageScopeConfig(path: string): SerializedPackageConfig | undefined getPackageJSONScripts(): string | undefined } From 95c16b2baffc8cf84b2b84ac9b0b6f61a43174cb Mon Sep 17 00:00:00 2001 From: Cheng Date: Wed, 8 May 2024 10:43:25 +0900 Subject: [PATCH 7/8] build: drop base64 dep in GN build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/52856 Reviewed-By: Michaël Zasso Reviewed-By: Luigi Pinca Reviewed-By: Yagiz Nizipli Reviewed-By: Mohammed Keyvanzadeh --- unofficial.gni | 1 - 1 file changed, 1 deletion(-) diff --git a/unofficial.gni b/unofficial.gni index 4ae6d80e1245d9..32df8b820f7e7c 100644 --- a/unofficial.gni +++ b/unofficial.gni @@ -139,7 +139,6 @@ template("node_gn_build") { public_deps = [ "deps/ada", "deps/uv", - "deps/base64", "deps/simdjson", "$node_v8_path", ] From 22cb99d073b03414e33843550cca3bac2833a361 Mon Sep 17 00:00:00 2001 From: Gabriel Bota <94833492+dygabo@users.noreply.github.com> Date: Wed, 8 May 2024 04:20:48 +0200 Subject: [PATCH 8/8] module: have a single hooks thread for all workers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/52706 Reviewed-By: Geoffrey Booth Reviewed-By: Jacob Smith Reviewed-By: Antoine du Hamel Reviewed-By: Gerhard Stöbich --- lib/internal/main/worker_thread.js | 2 + lib/internal/modules/esm/hooks.js | 127 ++++++++++++------ lib/internal/modules/esm/loader.js | 19 ++- lib/internal/modules/esm/worker.js | 74 ++++++++-- lib/internal/worker.js | 32 ++++- src/handle_wrap.cc | 8 +- test/common/index.mjs | 2 + test/es-module/test-esm-loader-mock.mjs | 7 +- test/es-module/test-esm-loader-threads.mjs | 74 ++++++++++ test/es-module/test-esm-named-exports.js | 4 +- test/es-module/test-esm-named-exports.mjs | 9 +- test/es-module/test-esm-virtual-json.mjs | 3 +- .../builtin-named-exports.mjs | 13 +- .../es-module-loaders/hooks-exit-worker.mjs | 21 +++ test/fixtures/es-module-loaders/hooks-log.mjs | 19 +++ .../not-found-assert-loader.mjs | 17 +-- .../es-module-loaders/worker-fail-on-load.mjs | 1 + .../worker-fail-on-resolve.mjs | 1 + .../es-module-loaders/worker-log-again.mjs | 3 + .../worker-log-fail-worker-load.mjs | 12 ++ .../worker-log-fail-worker-resolve.mjs | 12 ++ .../fixtures/es-module-loaders/worker-log.mjs | 9 ++ .../es-module-loaders/workers-spawned.mjs | 7 + 23 files changed, 395 insertions(+), 81 deletions(-) create mode 100644 test/es-module/test-esm-loader-threads.mjs create mode 100644 test/fixtures/es-module-loaders/hooks-exit-worker.mjs create mode 100644 test/fixtures/es-module-loaders/hooks-log.mjs create mode 100644 test/fixtures/es-module-loaders/worker-fail-on-load.mjs create mode 100644 test/fixtures/es-module-loaders/worker-fail-on-resolve.mjs create mode 100644 test/fixtures/es-module-loaders/worker-log-again.mjs create mode 100644 test/fixtures/es-module-loaders/worker-log-fail-worker-load.mjs create mode 100644 test/fixtures/es-module-loaders/worker-log-fail-worker-resolve.mjs create mode 100644 test/fixtures/es-module-loaders/worker-log.mjs create mode 100644 test/fixtures/es-module-loaders/workers-spawned.mjs diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index aa329b9fe04f15..32bccca9b53a72 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -95,6 +95,7 @@ port.on('message', (message) => { filename, hasStdin, publicPort, + hooksPort, workerData, } = message; @@ -109,6 +110,7 @@ port.on('message', (message) => { } require('internal/worker').assignEnvironmentData(environmentData); + require('internal/worker').hooksPort = hooksPort; if (SharedArrayBuffer !== undefined) { // The counter is only passed to the workers created by the main thread, diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index ba655116a0bb57..f5833ad61cdb75 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -35,7 +35,7 @@ const { const { exitCodes: { kUnsettledTopLevelAwait } } = internalBinding('errors'); const { URL } = require('internal/url'); const { canParse: URLCanParse } = internalBinding('url'); -const { receiveMessageOnPort } = require('worker_threads'); +const { receiveMessageOnPort, isMainThread } = require('worker_threads'); const { isAnyArrayBuffer, isArrayBufferView, @@ -482,6 +482,8 @@ class HooksProxy { */ #worker; + #portToHooksThread; + /** * The last notification ID received from the worker. This is used to detect * if the worker has already sent a notification before putting the main @@ -499,26 +501,38 @@ class HooksProxy { #isReady = false; constructor() { - const { InternalWorker } = require('internal/worker'); - MessageChannel ??= require('internal/worker/io').MessageChannel; - + const { InternalWorker, hooksPort } = require('internal/worker'); const lock = new SharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH); this.#lock = new Int32Array(lock); - this.#worker = new InternalWorker(loaderWorkerId, { - stderr: false, - stdin: false, - stdout: false, - trackUnmanagedFds: false, - workerData: { - lock, - }, - }); - this.#worker.unref(); // ! Allows the process to eventually exit. - this.#worker.on('exit', process.exit); + if (isMainThread) { + // Main thread is the only one that creates the internal single hooks worker + this.#worker = new InternalWorker(loaderWorkerId, { + stderr: false, + stdin: false, + stdout: false, + trackUnmanagedFds: false, + workerData: { + lock, + }, + }); + this.#worker.unref(); // ! Allows the process to eventually exit. + this.#worker.on('exit', process.exit); + this.#portToHooksThread = this.#worker; + } else { + this.#portToHooksThread = hooksPort; + } } waitForWorker() { + // There is one Hooks instance for each worker thread. But only one of these Hooks instances + // has an InternalWorker. That was the Hooks instance created for the main thread. + // It means for all Hooks instances that are not on the main thread => they are ready because they + // delegate to the single InternalWorker anyway. + if (!isMainThread) { + return; + } + if (!this.#isReady) { const { kIsOnline } = require('internal/worker'); if (!this.#worker[kIsOnline]) { @@ -535,6 +549,37 @@ class HooksProxy { } } + #postMessageToWorker(method, type, transferList, args) { + this.waitForWorker(); + + MessageChannel ??= require('internal/worker/io').MessageChannel; + + const { + port1: fromHooksThread, + port2: toHooksThread, + } = new MessageChannel(); + + // Pass work to the worker. + debug(`post ${type} message to worker`, { method, args, transferList }); + const usedTransferList = [toHooksThread]; + if (transferList) { + ArrayPrototypePushApply(usedTransferList, transferList); + } + + this.#portToHooksThread.postMessage( + { + __proto__: null, + args, + lock: this.#lock, + method, + port: toHooksThread, + }, + usedTransferList, + ); + + return fromHooksThread; + } + /** * Invoke a remote method asynchronously. * @param {string} method Method to invoke @@ -543,22 +588,7 @@ class HooksProxy { * @returns {Promise} */ async makeAsyncRequest(method, transferList, ...args) { - this.waitForWorker(); - - MessageChannel ??= require('internal/worker/io').MessageChannel; - const asyncCommChannel = new MessageChannel(); - - // Pass work to the worker. - debug('post async message to worker', { method, args, transferList }); - const finalTransferList = [asyncCommChannel.port2]; - if (transferList) { - ArrayPrototypePushApply(finalTransferList, transferList); - } - this.#worker.postMessage({ - __proto__: null, - method, args, - port: asyncCommChannel.port2, - }, finalTransferList); + const fromHooksThread = this.#postMessageToWorker(method, 'Async', transferList, args); if (this.#numberOfPendingAsyncResponses++ === 0) { // On the next lines, the main thread will await a response from the worker thread that might @@ -567,7 +597,11 @@ class HooksProxy { // However we want to keep the process alive until the worker thread responds (or until the // event loop of the worker thread is also empty), so we ref the worker until we get all the // responses back. - this.#worker.ref(); + if (this.#worker) { + this.#worker.ref(); + } else { + this.#portToHooksThread.ref(); + } } let response; @@ -576,18 +610,26 @@ class HooksProxy { await AtomicsWaitAsync(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, this.#workerNotificationLastId).value; this.#workerNotificationLastId = AtomicsLoad(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - response = receiveMessageOnPort(asyncCommChannel.port1); + response = receiveMessageOnPort(fromHooksThread); } while (response == null); debug('got async response from worker', { method, args }, this.#lock); if (--this.#numberOfPendingAsyncResponses === 0) { // We got all the responses from the worker, its job is done (until next time). - this.#worker.unref(); + if (this.#worker) { + this.#worker.unref(); + } else { + this.#portToHooksThread.unref(); + } + } + + if (response.message.status === 'exit') { + process.exit(response.message.body); } - const body = this.#unwrapMessage(response); - asyncCommChannel.port1.close(); - return body; + fromHooksThread.close(); + + return this.#unwrapMessage(response); } /** @@ -598,11 +640,7 @@ class HooksProxy { * @returns {any} */ makeSyncRequest(method, transferList, ...args) { - this.waitForWorker(); - - // Pass work to the worker. - debug('post sync message to worker', { method, args, transferList }); - this.#worker.postMessage({ __proto__: null, method, args }, transferList); + const fromHooksThread = this.#postMessageToWorker(method, 'Sync', transferList, args); let response; do { @@ -611,7 +649,7 @@ class HooksProxy { AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, this.#workerNotificationLastId); this.#workerNotificationLastId = AtomicsLoad(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - response = this.#worker.receiveMessageSync(); + response = receiveMessageOnPort(fromHooksThread); } while (response == null); debug('got sync response from worker', { method, args }); if (response.message.status === 'never-settle') { @@ -619,6 +657,9 @@ class HooksProxy { } else if (response.message.status === 'exit') { process.exit(response.message.body); } + + fromHooksThread.close(); + return this.#unwrapMessage(response); } diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 8fbcb56aa65287..5eb7cd17d02fad 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -41,6 +41,7 @@ const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding('module_wrap'); const { urlToFilename, } = require('internal/modules/helpers'); +const { isMainThread } = require('worker_threads'); let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer; /** @@ -607,10 +608,11 @@ class CustomizedModuleLoader { */ constructor() { getHooksProxy(); + _hasCustomizations = true; } /** - * Register some loader specifier. + * Register a loader specifier. * @param {string} originalSpecifier The specified URL path of the loader to * be registered. * @param {string} parentURL The parent URL from where the loader will be @@ -618,10 +620,14 @@ class CustomizedModuleLoader { * @param {any} [data] Arbitrary data to be passed from the custom loader * (user-land) to the worker. * @param {any[]} [transferList] Objects in `data` that are changing ownership - * @returns {{ format: string, url: URL['href'] }} + * @returns {{ format: string, url: URL['href'] } | undefined} */ register(originalSpecifier, parentURL, data, transferList) { - return hooksProxy.makeSyncRequest('register', transferList, originalSpecifier, parentURL, data); + if (isMainThread) { + // Only the main thread has a Hooks instance with worker thread. All other Worker threads + // delegate their hooks to the HooksThread of the main thread. + return hooksProxy.makeSyncRequest('register', transferList, originalSpecifier, parentURL, data); + } } /** @@ -719,6 +725,12 @@ function getHooksProxy() { return hooksProxy; } +let _hasCustomizations = false; +function hasCustomizations() { + return _hasCustomizations; +} + + let cascadedLoader; /** @@ -780,6 +792,7 @@ function register(specifier, parentURL = undefined, options) { module.exports = { createModuleLoader, + hasCustomizations, getHooksProxy, getOrInitializeCascadedLoader, register, diff --git a/lib/internal/modules/esm/worker.js b/lib/internal/modules/esm/worker.js index 311d77fb099384..088667f3c0d5d7 100644 --- a/lib/internal/modules/esm/worker.js +++ b/lib/internal/modules/esm/worker.js @@ -1,6 +1,8 @@ 'use strict'; const { + ArrayPrototypeFilter, + ArrayPrototypePush, AtomicsAdd, AtomicsNotify, DataViewPrototypeGetBuffer, @@ -97,7 +99,21 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { // so it can detect the exit event. const { exit } = process; process.exit = function(code) { - syncCommPort.postMessage(wrapMessage('exit', code ?? process.exitCode)); + const exitMsg = wrapMessage('exit', code ?? process.exitCode); + if (hooks) { + for (let i = 0; i < allThreadRegisteredHandlerPorts.length; i++) { + const { port: registeredPort } = allThreadRegisteredHandlerPorts[i]; + registeredPort.postMessage(exitMsg); + } + + for (const { port, lock: operationLock } of unsettledResponsePorts) { + port.postMessage(exitMsg); + // Wake all threads that have pending operations. + AtomicsAdd(operationLock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); + AtomicsNotify(operationLock, WORKER_TO_MAIN_THREAD_NOTIFICATION); + } + } + syncCommPort.postMessage(exitMsg); AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); return ReflectApply(exit, this, arguments); @@ -145,8 +161,11 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { const unsettledResponsePorts = new SafeSet(); process.on('beforeExit', () => { - for (const port of unsettledResponsePorts) { + for (const { port, lock: operationLock } of unsettledResponsePorts) { port.postMessage(wrapMessage('never-settle')); + // Wake all threads that have pending operations. + AtomicsAdd(operationLock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); + AtomicsNotify(operationLock, WORKER_TO_MAIN_THREAD_NOTIFICATION); } unsettledResponsePorts.clear(); @@ -164,24 +183,59 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { setImmediate(() => {}); }); + let allThreadRegisteredHandlerPorts = []; + /** + * @callback registerHandler + * @param {MessagePort} toWorkerThread - Upon Worker creation a message channel between the new Worker + * and the Hooks thread is being initialized. This is the MessagePort that the Hooks thread will use post + * messages to the worker. The other MessagePort is passed to the new Worker itself via LOAD_SCRIPT message + */ + function registerHandler(toWorkerThread, registeredThreadId) { + toWorkerThread.on('message', handleMessage); + ArrayPrototypePush(allThreadRegisteredHandlerPorts, { port: toWorkerThread, registeredThreadId }); + } + + /** + * @callback registerHandler + * @param {number} unregisteredThreadId - the thread id of the worker thread that is being unregistered + * from the Hooks Thread + */ + function unregisterHandler(unregisteredThreadId) { + allThreadRegisteredHandlerPorts = ArrayPrototypeFilter( + allThreadRegisteredHandlerPorts, (el) => el.registeredThreadId !== unregisteredThreadId); + } + + function getMessageHandler(method) { + if (method === '#registerWorkerClient') { + return registerHandler; + } + if (method === '#unregisterWorkerClient') { + return unregisterHandler; + } + return hooks[method]; + } + /** * Handles incoming messages from the main thread or other workers. * @param {object} options - The options object. * @param {string} options.method - The name of the hook. * @param {Array} options.args - The arguments to pass to the method. * @param {MessagePort} options.port - The message port to use for communication. + * @param {Int32Array} options.lock - The shared memory where the caller expects to get awaken. */ - async function handleMessage({ method, args, port }) { + async function handleMessage({ method, args, port, lock: msgLock }) { // Each potential exception needs to be caught individually so that the correct error is sent to // the main thread. let hasError = false; let shouldRemoveGlobalErrorHandler = false; - assert(typeof hooks[method] === 'function'); + const messageHandler = getMessageHandler(method); + assert(typeof messageHandler === 'function'); if (port == null && !hasUncaughtExceptionCaptureCallback()) { // When receiving sync messages, we want to unlock the main thread when there's an exception. process.on('uncaughtException', errorHandler); shouldRemoveGlobalErrorHandler = true; } + const usedLock = msgLock ?? lock; // We are about to yield the execution with `await ReflectApply` below. In case the code // following the `await` never runs, we remove the message handler so the `beforeExit` event @@ -192,17 +246,19 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { clearImmediate(immediate); immediate = setImmediate(checkForMessages).unref(); - unsettledResponsePorts.add(port ?? syncCommPort); + const unsettledActionData = { port: port ?? syncCommPort, lock: usedLock }; + + unsettledResponsePorts.add(unsettledActionData); let response; try { - response = await ReflectApply(hooks[method], hooks, args); + response = await ReflectApply(messageHandler, hooks, args); } catch (exception) { hasError = true; response = exception; } - unsettledResponsePorts.delete(port ?? syncCommPort); + unsettledResponsePorts.delete(unsettledActionData); // Send the method response (or exception) to the main thread. try { @@ -215,8 +271,8 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { (port ?? syncCommPort).postMessage(wrapMessage('error', exception)); } - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); + AtomicsAdd(usedLock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); + AtomicsNotify(usedLock, WORKER_TO_MAIN_THREAD_NOTIFICATION); if (shouldRemoveGlobalErrorHandler) { process.off('uncaughtException', errorHandler); } diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 9eefaa97021756..766659d4464379 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -132,7 +132,7 @@ class Worker extends EventEmitter { constructor(filename, options = kEmptyObject) { throwIfBuildingSnapshot('Creating workers'); super(); - const isInternal = arguments[2] === kIsInternal; + const isInternal = this.#isInternal = arguments[2] === kIsInternal; debug( `[${threadId}] create new worker`, filename, @@ -258,6 +258,15 @@ class Worker extends EventEmitter { ...new SafeArrayIterator(options.transferList)); this[kPublicPort] = port1; + const { + port1: toWorkerThread, + port2: toHooksThread, + } = new MessageChannel(); + if (!isInternal) { + // This is not an internal hooks thread => it needs a channel to the hooks thread: + // - send it one side of a channel here + ArrayPrototypePush(transferList, toHooksThread); + } ArrayPrototypeForEach(['message', 'messageerror'], (event) => { this[kPublicPort].on(event, (message) => this.emit(event, message)); }); @@ -272,8 +281,20 @@ class Worker extends EventEmitter { workerData: options.workerData, environmentData, publicPort: port2, + hooksPort: !isInternal ? toHooksThread : undefined, hasStdin: !!options.stdin, }, transferList); + + const loaderModule = require('internal/modules/esm/loader'); + const hasCustomizations = loaderModule.hasCustomizations(); + + if (!isInternal && hasCustomizations) { + // - send the second side of the channel to the hooks thread, + // also announce the threadId of the Worker that will use that port. + // This is needed for the cleanup stage + loaderModule.getHooksProxy().makeSyncRequest( + '#registerWorkerClient', [toWorkerThread], toWorkerThread, this.threadId); + } // Use this to cache the Worker's loopStart value once available. this[kLoopStartTime] = -1; this[kIsOnline] = false; @@ -293,6 +314,12 @@ class Worker extends EventEmitter { [kOnExit](code, customErr, customErrReason) { debug(`[${threadId}] hears end event for Worker ${this.threadId}`); + const loaderModule = require('internal/modules/esm/loader'); + const hasCustomizations = loaderModule.hasCustomizations(); + + if (!this.#isInternal && hasCustomizations) { + loaderModule.getHooksProxy()?.makeAsyncRequest('#unregisterWorkerClient', undefined, this.threadId); + } drainMessagePort(this[kPublicPort]); drainMessagePort(this[kPort]); this.removeAllListeners('message'); @@ -435,6 +462,8 @@ class Worker extends EventEmitter { return makeResourceLimits(this[kHandle].getResourceLimits()); } + #isInternal = false; + getHeapSnapshot(options) { const { HeapSnapshotStream, @@ -532,6 +561,7 @@ module.exports = { kIsOnline, isMainThread, SHARE_ENV, + hooksPort: undefined, resourceLimits: !isMainThread ? makeResourceLimits(resourceLimitsRaw) : {}, setEnvironmentData, diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index 69e2a389f9e148..1fbe0178c7a10d 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -148,9 +148,11 @@ void HandleWrap::OnClose(uv_handle_t* handle) { wrap->OnClose(); wrap->handle_wrap_queue_.Remove(); - if (!wrap->persistent().IsEmpty() && - wrap->object()->Has(env->context(), env->handle_onclose_symbol()) - .FromMaybe(false)) { + if (!env->isolate()->IsExecutionTerminating() && + !wrap->persistent().IsEmpty() && + wrap->object() + ->Has(env->context(), env->handle_onclose_symbol()) + .FromMaybe(false)) { wrap->MakeCallback(env->handle_onclose_symbol(), 0, nullptr); } } diff --git a/test/common/index.mjs b/test/common/index.mjs index ca2994f6e1360f..ec9d6ce8b2432c 100644 --- a/test/common/index.mjs +++ b/test/common/index.mjs @@ -50,6 +50,7 @@ const { skipIfDumbTerminal, skipIfEslintMissing, skipIfInspectorDisabled, + skipIfWorker, spawnPromisified, } = common; @@ -104,5 +105,6 @@ export { skipIfDumbTerminal, skipIfEslintMissing, skipIfInspectorDisabled, + skipIfWorker, spawnPromisified, }; diff --git a/test/es-module/test-esm-loader-mock.mjs b/test/es-module/test-esm-loader-mock.mjs index 164d0ac3775039..0d39f549581a54 100644 --- a/test/es-module/test-esm-loader-mock.mjs +++ b/test/es-module/test-esm-loader-mock.mjs @@ -1,6 +1,11 @@ -import '../common/index.mjs'; +import { skipIfWorker } from '../common/index.mjs'; import assert from 'node:assert/strict'; import { mock } from '../fixtures/es-module-loaders/mock.mjs'; +// Importing mock.mjs above will call `register` to modify the loaders chain. +// Modifying the loader chain is not supported currently when running from a worker thread. +// Relevant PR: https://github.com/nodejs/node/pull/52706 +// See comment: https://github.com/nodejs/node/pull/52706/files#r1585144580 +skipIfWorker(); mock('node:events', { EventEmitter: 'This is mocked!' diff --git a/test/es-module/test-esm-loader-threads.mjs b/test/es-module/test-esm-loader-threads.mjs new file mode 100644 index 00000000000000..7310a9ac5b54ac --- /dev/null +++ b/test/es-module/test-esm-loader-threads.mjs @@ -0,0 +1,74 @@ +import { spawnPromisified } from '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { strictEqual } from 'node:assert'; +import { execPath } from 'node:process'; +import { describe, it } from 'node:test'; + +describe('off-thread hooks', { concurrency: true }, () => { + it('uses only one hooks thread to support multiple application threads', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ + '--no-warnings', + '--import', + `data:text/javascript,${encodeURIComponent(` + import { register } from 'node:module'; + register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-log.mjs'))}); + `)}`, + fixtures.path('es-module-loaders/workers-spawned.mjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout.split('\n').filter((line) => line.startsWith('initialize')).length, 1); + strictEqual(stdout.split('\n').filter((line) => line === 'foo').length, 2); + strictEqual(stdout.split('\n').filter((line) => line === 'bar').length, 4); + // Calls to resolve/load: + // 1x main script: test/fixtures/es-module-loaders/workers-spawned.mjs + // 3x worker_threads + // => 1x test/fixtures/es-module-loaders/worker-log.mjs + // 2x test/fixtures/es-module-loaders/worker-log-again.mjs => once per worker-log.mjs Worker instance + // 2x test/fixtures/es-module-loaders/worker-log.mjs => once per worker-log.mjs Worker instance + // 4x test/fixtures/es-module-loaders/worker-log-again.mjs => 2x for each worker-log + // 6x module-named-exports.mjs => 2x worker-log.mjs + 4x worker-log-again.mjs + // =========================== + // 16 calls to resolve + 16 calls to load hook for the registered custom loader + strictEqual(stdout.split('\n').filter((line) => line.startsWith('hooked resolve')).length, 16); + strictEqual(stdout.split('\n').filter((line) => line.startsWith('hooked load')).length, 16); + strictEqual(code, 0); + strictEqual(signal, null); + }); + + it('propagates the exit code from worker thread import exiting from resolve hook', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ + '--no-warnings', + '--import', + `data:text/javascript,${encodeURIComponent(` + import { register } from 'node:module'; + register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-exit-worker.mjs'))}); + `)}`, + fixtures.path('es-module-loaders/worker-log-fail-worker-resolve.mjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout.split('\n').filter((line) => line.startsWith('resolve process-exit-module-resolve')).length, 1); + strictEqual(code, 42); + strictEqual(signal, null); + }); + + it('propagates the exit code from worker thread import exiting from load hook', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ + '--no-warnings', + '--import', + `data:text/javascript,${encodeURIComponent(` + import { register } from 'node:module'; + register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-exit-worker.mjs'))}); + `)}`, + fixtures.path('es-module-loaders/worker-log-fail-worker-load.mjs'), + ]); + + strictEqual(stderr, ''); + strictEqual(stdout.split('\n').filter((line) => line.startsWith('resolve process-exit-module-load')).length, 1); + strictEqual(stdout.split('\n').filter((line) => line.startsWith('load process-exit-on-load:///')).length, 1); + strictEqual(code, 43); + strictEqual(signal, null); + }); + +}); diff --git a/test/es-module/test-esm-named-exports.js b/test/es-module/test-esm-named-exports.js index 2c6f67288aa57c..00b7aebbfd1f46 100644 --- a/test/es-module/test-esm-named-exports.js +++ b/test/es-module/test-esm-named-exports.js @@ -1,7 +1,9 @@ // Flags: --import ./test/fixtures/es-module-loaders/builtin-named-exports.mjs 'use strict'; -require('../common'); +const common = require('../common'); +common.skipIfWorker(); + const { readFile, __fromLoader } = require('fs'); const assert = require('assert'); diff --git a/test/es-module/test-esm-named-exports.mjs b/test/es-module/test-esm-named-exports.mjs index bbe9c96b92d9b8..6e584b05aa204f 100644 --- a/test/es-module/test-esm-named-exports.mjs +++ b/test/es-module/test-esm-named-exports.mjs @@ -1,9 +1,10 @@ // Flags: --import ./test/fixtures/es-module-loaders/builtin-named-exports.mjs -import '../common/index.mjs'; -import { readFile, __fromLoader } from 'fs'; +import { skipIfWorker } from '../common/index.mjs'; +import * as fs from 'fs'; import assert from 'assert'; import ok from '../fixtures/es-modules/test-esm-ok.mjs'; +skipIfWorker(); assert(ok); -assert(readFile); -assert(__fromLoader); +assert(fs.readFile); +assert(fs.__fromLoader); diff --git a/test/es-module/test-esm-virtual-json.mjs b/test/es-module/test-esm-virtual-json.mjs index a42b037fc1f200..1064a6af5026cf 100644 --- a/test/es-module/test-esm-virtual-json.mjs +++ b/test/es-module/test-esm-virtual-json.mjs @@ -1,7 +1,8 @@ -import '../common/index.mjs'; +import { skipIfWorker } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { register } from 'node:module'; import assert from 'node:assert'; +skipIfWorker(); async function resolve(referrer, context, next) { const result = await next(referrer, context); diff --git a/test/fixtures/es-module-loaders/builtin-named-exports.mjs b/test/fixtures/es-module-loaders/builtin-named-exports.mjs index 123b12c26bf0c9..4e22f631eba416 100644 --- a/test/fixtures/es-module-loaders/builtin-named-exports.mjs +++ b/test/fixtures/es-module-loaders/builtin-named-exports.mjs @@ -1,3 +1,4 @@ +import { isMainThread } from '../../common/index.mjs'; import * as fixtures from '../../common/fixtures.mjs'; import { createRequire, register } from 'node:module'; @@ -10,8 +11,10 @@ Object.defineProperty(globalThis, GET_BUILTIN, { configurable: false, }); -register(fixtures.fileURL('es-module-loaders/builtin-named-exports-loader.mjs'), { - data: { - GET_BUILTIN, - }, -}); +if (isMainThread) { + register(fixtures.fileURL('es-module-loaders/builtin-named-exports-loader.mjs'), { + data: { + GET_BUILTIN, + }, + }); +} diff --git a/test/fixtures/es-module-loaders/hooks-exit-worker.mjs b/test/fixtures/es-module-loaders/hooks-exit-worker.mjs new file mode 100644 index 00000000000000..d499a835e6456c --- /dev/null +++ b/test/fixtures/es-module-loaders/hooks-exit-worker.mjs @@ -0,0 +1,21 @@ +import { writeFileSync } from 'node:fs'; + +export function resolve(specifier, context, next) { + writeFileSync(1, `resolve ${specifier}\n`); + if (specifier === 'process-exit-module-resolve') { + process.exit(42); + } + + if (specifier === 'process-exit-module-load') { + return { __proto__: null, shortCircuit: true, url: 'process-exit-on-load:///' } + } + return next(specifier, context); +} + +export function load(url, context, next) { + writeFileSync(1, `load ${url}\n`); + if (url === 'process-exit-on-load:///') { + process.exit(43); + } + return next(url, context); +} diff --git a/test/fixtures/es-module-loaders/hooks-log.mjs b/test/fixtures/es-module-loaders/hooks-log.mjs new file mode 100644 index 00000000000000..2d2512281e8bd5 --- /dev/null +++ b/test/fixtures/es-module-loaders/hooks-log.mjs @@ -0,0 +1,19 @@ +import { writeFileSync } from 'node:fs'; + +let initializeCount = 0; +let resolveCount = 0; +let loadCount = 0; + +export function initialize() { + writeFileSync(1, `initialize ${++initializeCount}\n`); +} + +export function resolve(specifier, context, next) { + writeFileSync(1, `hooked resolve ${++resolveCount} ${specifier}\n`); + return next(specifier, context); +} + +export function load(url, context, next) { + writeFileSync(1, `hooked load ${++loadCount} ${url}\n`); + return next(url, context); +} diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs index bf66efbd0810e5..7d53e31df918a7 100644 --- a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs +++ b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs @@ -1,16 +1,13 @@ import assert from 'node:assert'; // A loader that asserts that the defaultResolve will throw "not found" -// (skipping the top-level main of course, and the built-in ones needed for run-worker). -let mainLoad = true; export async function resolve(specifier, { importAttributes }, next) { - if (mainLoad || specifier === 'path' || specifier === 'worker_threads') { - mainLoad = false; - return next(specifier); + if (specifier.startsWith('./not-found')) { + await assert.rejects(next(specifier), { code: 'ERR_MODULE_NOT_FOUND' }); + return { + url: 'node:fs', + importAttributes, + }; } - await assert.rejects(next(specifier), { code: 'ERR_MODULE_NOT_FOUND' }); - return { - url: 'node:fs', - importAttributes, - }; + return next(specifier); } diff --git a/test/fixtures/es-module-loaders/worker-fail-on-load.mjs b/test/fixtures/es-module-loaders/worker-fail-on-load.mjs new file mode 100644 index 00000000000000..46e88664a03c5c --- /dev/null +++ b/test/fixtures/es-module-loaders/worker-fail-on-load.mjs @@ -0,0 +1 @@ +import 'process-exit-module-load'; diff --git a/test/fixtures/es-module-loaders/worker-fail-on-resolve.mjs b/test/fixtures/es-module-loaders/worker-fail-on-resolve.mjs new file mode 100644 index 00000000000000..e8e7adde42585f --- /dev/null +++ b/test/fixtures/es-module-loaders/worker-fail-on-resolve.mjs @@ -0,0 +1 @@ +import 'process-exit-module-resolve'; diff --git a/test/fixtures/es-module-loaders/worker-log-again.mjs b/test/fixtures/es-module-loaders/worker-log-again.mjs new file mode 100644 index 00000000000000..2969edc8dac382 --- /dev/null +++ b/test/fixtures/es-module-loaders/worker-log-again.mjs @@ -0,0 +1,3 @@ +import { bar } from './module-named-exports.mjs'; + +console.log(bar); diff --git a/test/fixtures/es-module-loaders/worker-log-fail-worker-load.mjs b/test/fixtures/es-module-loaders/worker-log-fail-worker-load.mjs new file mode 100644 index 00000000000000..81797da392cb7a --- /dev/null +++ b/test/fixtures/es-module-loaders/worker-log-fail-worker-load.mjs @@ -0,0 +1,12 @@ +import { Worker } from 'worker_threads'; +import { foo } from './module-named-exports.mjs'; + +const workerURLFailOnLoad = new URL('./worker-fail-on-load.mjs', import.meta.url); +console.log(foo); + +// Spawn a worker that will fail to import a dependant module +new Worker(workerURLFailOnLoad); + +process.on('exit', (code) => { + console.log(`process exit code: ${code}`) +}); diff --git a/test/fixtures/es-module-loaders/worker-log-fail-worker-resolve.mjs b/test/fixtures/es-module-loaders/worker-log-fail-worker-resolve.mjs new file mode 100644 index 00000000000000..b5ff238967f4ef --- /dev/null +++ b/test/fixtures/es-module-loaders/worker-log-fail-worker-resolve.mjs @@ -0,0 +1,12 @@ +import { Worker } from 'worker_threads'; +import { foo } from './module-named-exports.mjs'; + +const workerURLFailOnResolve = new URL('./worker-fail-on-resolve.mjs', import.meta.url); +console.log(foo); + +// Spawn a worker that will fail to import a dependant module +new Worker(workerURLFailOnResolve); + +process.on('exit', (code) => { + console.log(`process exit code: ${code}`) +}); diff --git a/test/fixtures/es-module-loaders/worker-log.mjs b/test/fixtures/es-module-loaders/worker-log.mjs new file mode 100644 index 00000000000000..13290c37d07104 --- /dev/null +++ b/test/fixtures/es-module-loaders/worker-log.mjs @@ -0,0 +1,9 @@ +import { Worker } from 'worker_threads'; +import { foo } from './module-named-exports.mjs'; + +const workerURL = new URL('./worker-log-again.mjs', import.meta.url); +console.log(foo); + +// Spawn two workers +new Worker(workerURL); +new Worker(workerURL); diff --git a/test/fixtures/es-module-loaders/workers-spawned.mjs b/test/fixtures/es-module-loaders/workers-spawned.mjs new file mode 100644 index 00000000000000..439847656fe13e --- /dev/null +++ b/test/fixtures/es-module-loaders/workers-spawned.mjs @@ -0,0 +1,7 @@ +import { Worker } from 'worker_threads'; + +const workerURL = new URL('./worker-log.mjs', import.meta.url); + +// Spawn two workers +new Worker(workerURL); +new Worker(workerURL);