ESM conversion of node-config#889
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRemoved deprecated async/raw helpers and CommonJS wrappers; migrated core modules and most tests to ES modules; added a new ES-module Config implementation at Changes
Sequence DiagramsequenceDiagram
participant App as Application
participant Load as Load.fromEnvironment
participant Config as ConfigClass
participant Env as Environment
App->>Load: Load.fromEnvironment()
Load->>Config: new ConfigClass(load)
Config->>Env: read NODE_CONFIG / CLI overrides
Config->>Config: _init(load) — apply overrides & attach proto
Config->>Config: runStrictnessChecks() / makeImmutable*
Config-->>App: export singleton instance
App->>Config: get(property) / has(property)
Config-->>App: return value
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
lib/util.js (2)
1391-1398:⚠️ Potential issue | 🔴 CriticalBug:
requireis undefined in ESM — hostname resolution silently fails.
require('os')on line 1393 will throw aReferenceErrorbecause this file is now an ES module and has norequirein scope. Thetry/catchsilently swallows the error and falls through tohostName = '', so the actual OS hostname is never used whenHOST/HOSTNAMEenv vars are absent.
OSis already imported at the top of the file (line 10:import OS from 'node:os'). Remove the redundantrequireand use the existing import directly.Proposed fix
try { if (!hostName) { - const OS = require('os'); hostName = OS.hostname(); } } catch (e) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/util.js` around lines 1391 - 1398, The hostname resolution try/catch currently calls require('os') (which fails under ESM) and gets swallowed; replace the dynamic require with the already-imported OS module and call OS.hostname() to set hostName when hostName is falsy, keeping the existing catch behavior (i.e., set hostName = '' on error). Update the block that references require('os') so it uses the imported OS symbol and OS.hostname() instead of require.
456-491:⚠️ Potential issue | 🔴 Critical
resolveAsyncConfigsdoes not write resolved values back to the config object.The method collects Promise instances and
awaits them viaPromise.all(), but the resolved values are never assigned back. UnlikeresolveDeferredConfigswhich uses a prepare/resolve pattern to track locations and update values in place,resolveAsyncConfigshas no mechanism to write values back to their original config paths. Tests confirm this is a bug—assertions expect resolved values to be present in the config object after this method completes. Since this method replaces the deprecated./async.js, callers expect the same behavior as before, including access to resolved values viaconfig.get().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/util.js` around lines 456 - 491, resolveAsyncConfigs currently gathers Promise instances in promises[] but never writes their resolved values back into the original config structure; update it to track each promise's target location and assign the resolved value back into the config (for object properties and array entries) after awaiting Promise.all. Modify the traversal in _iterate to, when encountering a Promise, push an object containing the promise and a setter reference (e.g., { promise, parent, keyOrIndex }) or push the promise alongside its target index in a separate array; after awaiting Promise.all, iterate results and use the stored parent/keyOrIndex to set the resolved values into the original config so config.get() returns resolved values. Ensure the logic handles promises inside arrays and nested objects and preserves the existing sort/traversal behavior in resolveAsyncConfigs.test/0-util.js (2)
1637-1645:⚠️ Potential issue | 🟠 MajorTest
'loads the values in left-right order'accepts adonecallback but never calls it — will time out innode:test.In
node:test, a test function that declares adoneparameter is treated as callback-style and the framework waits fordone()to be called. Sincedoneis never invoked here (the body is synchronous), this test will hang until it times out.🐛 Proposed fix
- it('loads the values in left-right order', function(done) { + it('loads the values in left-right order', function() { const config = util.loadFileConfigs({🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1637 - 1645, The test "loads the values in left-right order" declares a callback parameter `done` but runs synchronously and never calls it, causing node:test to hang; fix by removing the `done` parameter from the test function signature (so it runs as a synchronous test) or alternatively call `done()` after the assertions; update the test that invokes util.loadFileConfigs and the surrounding it(...) callback (the function containing the assertion for config.db.name) to use the synchronous form (no `done`) or to call `done()` explicitly.
761-820:⚠️ Potential issue | 🟠 MajorUpdate
enginesfield inpackage.jsonto>= 20.11.0—import.meta.dirnameis unavailable in Node.js < 20.11.0.The
import.meta.dirnameproperty is used extensively throughout the test files and requires Node.js v20.11.0 or later. The current engine constraint>= 20.0.0allows Node.js versions 20.0.0–20.10.x where this property is undefined, causing silent path resolution failures. Update theenginesfield to enforce the minimum version requirement.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 761 - 820, Tests use import.meta.dirname (seen in calls to load.loadFile with Path.join(import.meta.dirname, ...)), which is only available in Node >=20.11.0; update the package.json "engines" field from ">= 20.0.0" to ">= 20.11.0" so CI and consumers run a compatible runtime, and rerun tests to confirm path resolution issues are resolved.test/1-protected-test.js (1)
27-45:⚠️ Potential issue | 🟠 Major
process.argv.push()inbeforeEachaccumulates entries across tests — use assignment instead.
process.argv.push(...)runs before each test, so after 11 tests in this suite the argv array contains 11 copies of--NODE_CONFIG=.... Each call torequireUncached('./lib/config.mjs')will see a progressively longer argv, potentially causing undefined or inconsistent config behavior for later tests.Compare with the correct pattern already used in
test/2-config-test.jsline 30:process.argv = [undefined, undefined, '--NODE_CONFIG={"EnvOverride":...}'];🐛 Proposed fix
- process.argv.push('--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}'); + process.argv = [ + process.argv[0], + process.argv[1], + '--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}' + ];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/1-protected-test.js` around lines 27 - 45, The test's beforeEach uses process.argv.push(...) which accumulates the NODE_CONFIG flag across tests; replace the push with an assignment that sets process.argv to a clean array containing the NODE_CONFIG entry (matching the pattern used in 2-config-test.js) so each call to requireUncached('./lib/config.mjs') sees a consistent argv; update the beforeEach block (the process.argv handling) to assign a fresh array (e.g., with two leading entries and the '--NODE_CONFIG=...' string) instead of pushing.test/6-strict-mode.js (1)
74-97:⚠️ Potential issue | 🟡 Minor
erroris never reset at the start ofbeforeEach, leaving stale state risk across the twoitblocks.Each closure from
_expectExceptioncontains twoitblocks, meaningbeforeEachruns twice. If the first run setserrorand the second run completes without throwing (e.g., transient behavior in dynamic import caching), the seconditwill evaluate stale state and produce a misleading result.🛠️ Proposed fix – reset `error` at the top of `beforeEach`
return () => { beforeEach(async function() { + error = undefined; // Change the configuration directory for testing process.env.NODE_CONFIG_DIR = import.meta.dirname + '/6-config';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/6-strict-mode.js` around lines 74 - 97, The shared variable error is not reset at the start of the beforeEach closure, which can leave stale state across the two it blocks; inside the beforeEach function (the closure returned by _expectException), set error = undefined (or null) as the first statement before modifying env and calling requireUncached('./lib/config.mjs') so each test run starts with a clean error state.
🧹 Nitpick comments (14)
test/_utils/requireUncached.mjs (1)
9-9:process.cwd()-anchored resolution is fragile if tests are not run from the project root.
Path.resolve(process.cwd(), moduleName)works only whencwdequals the project root. Consider documenting this assumption or usingimport.metaof the calling test file instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/_utils/requireUncached.mjs` at line 9, The current resolution uses process.cwd() which breaks if tests run outside the repo root; change the resolver in requireUncached.mjs to resolve moduleName relative to the caller's module URL instead of process.cwd(). Specifically, replace Path.resolve(process.cwd(), moduleName) with a resolution based on import.meta.url (convert to a file path with URL.fileURLToPath and use Path.dirname(...)) so that requireUncached resolves modules relative to the test file that imported it; alternatively, document the cwd assumption if you prefer not to change behavior.parser.js (2)
90-103: Defensive TS lazy-init looks correct, butrequire?.optional chaining is unnecessary.
requireis defined on line 7 viacreateRequire(process.cwd())and is always truthy. The?.on line 91 can never short-circuit. Not harmful, but potentially confusing to readers about whenrequirecould be undefined.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 90 - 103, In Parser.tsParser replace the unnecessary optional chaining on require by changing require?.extensions['.ts'] to require.extensions['.ts'] so the defensive TS lazy-init logic is clearer; locate the check inside the Parser.tsParser function and update that conditional expression only (leave the rest of the TS lazy-init and TS.register call intact).
6-7:moduleRequireuses an inefficient module search path — clarify or refactor.
createRequire(Path.join(process.cwd(), 'node_modules'))causes module resolution to search/node_modules/node_modulesfirst, then fall back to/node_modules. This is less efficient thancreateRequire(process.cwd())which searches/node_modulesdirectly. While both functions eventually find modules through parent directory fallback,moduleRequirewastes a search iteration on a non-existent path.The intent appears to be distinguishing module loading (bare names like
'yaml') from file-based loading (by filename), but the implementation creates unnecessary inefficiency. Either pass a file path within node_modules (e.g.,Path.join(process.cwd(), 'node_modules', 'package.json')) or reconsider whether this distinction is needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 6 - 7, The createRequire call creating moduleRequire is using Path.join(process.cwd(), 'node_modules') which forces an extra, unnecessary lookup; change the moduleRequire createRequire argument to either createRequire(process.cwd()) to search node_modules directly or provide a concrete file inside node_modules (e.g., Path.join(process.cwd(), 'node_modules', 'package.json')) so resolution does not probe a non-existent nested node_modules—update the moduleRequire declaration accordingly (symbols: moduleRequire, require, createRequire, Path, process.cwd()).test/16-custom-parser.js (1)
78-80:afterhook doesn't need to be async.The body only performs a synchronous
delete— theasyncqualifier is unnecessary here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/16-custom-parser.js` around lines 78 - 80, The after hook in test/16-custom-parser.js is declared async but only performs a synchronous delete of process.env.NODE_CONFIG_PARSER; remove the unnecessary async qualifier from the anonymous function passed to after(...) so the hook is a plain synchronous function.package.json (1)
5-5: Consider using"exports"field with conditions for modern Node.js ESM support.The
"module"field is a de facto bundler convention (not an official Node.js field) and will be ignored by Node.js runtime. For official ESM resolution in Node.js, use the"exports"field with conditions like"import"and"require"to provide separate entry points. Currently, without an"exports"field, Node.js will resolve to the"main"entry (./lib/config.js) for all consumers; only explicit imports of the.mjsfile will bypass it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 5, Add an "exports" field to package.json to provide conditional entry points for ESM and CJS consumers instead of relying on the non-standard "module" field; map the "import" condition to "./lib/config.mjs" (ESM) and the "require" condition to "./lib/config.js" (CJS), and keep "main" as fallback if desired, so Node's resolver uses the correct file for each module system while preserving the existing "./lib/config.mjs" and "./lib/config.js" artifacts referenced by "module" and "main".lib/config.mjs (1)
598-615:filename.match(env)interpretsenvas a regex pattern.Lines 604–605 use
String.match()with the rawenvvalue, which is treated as a regex. IfNODE_ENVcontains regex metacharacters (e.g.,c++,test.v2), this can produce false matches or throw. Consider usingfilename.includes(env)or escaping the value. This is pre-existing behavior, so flagging as optional.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 598 - 615, The code uses filename.match(env) inside the load.options.nodeEnv loop which treats env as a regex and can misbehave for values with regex metacharacters; change the check to perform a literal substring or exact comparison instead — e.g., use filename.includes(env) or escape env before using RegExp — so replace the filename.match(env) call in the sourceFilenames.some callback (referencing sourceFilenames, load.options.nodeEnv, filename.match and load.getEnv) with a safe literal comparison and keep the existing _warnOrThrow calls unchanged.test/9-raw-configs.js (1)
8-13: Minor formatting: closing brace on same line as assignment.Line 13 has
});appended directly after the assignment statement. Likely an accidental collapse.Proposed fix
- CONFIG = await requireUncached('./lib/config.mjs'); }); + CONFIG = await requireUncached('./lib/config.mjs'); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/9-raw-configs.js` around lines 8 - 13, The closing brace and parenthesis for the before(async() => { ... }) hook are placed on the same line as the CONFIG assignment, making the formatting look collapsed; update the before hook around CONFIG = await requireUncached('./lib/config.mjs') so the final `});` is on its own line (i.e., close the async function block and the before call on a separate line) to restore proper readability and consistent formatting in test/9-raw-configs.js.test/11-regexp.js (1)
8-14:NODE_APP_INSTANCE='regexp'is set inbeforeEachbut never deleted — leaks into subsequent test files.After this suite runs,
NODE_APP_INSTANCEremains'regexp', which could affect any subsequent test that callsrequireUncached('./lib/config.mjs')without explicitly overriding it.🔧 Add cleanup
-import { describe, it, beforeEach } from 'node:test'; +import { describe, it, beforeEach, afterEach } from 'node:test'; ... + afterEach(function() { + delete process.env.NODE_APP_INSTANCE; + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/11-regexp.js` around lines 8 - 14, The test sets NODE_APP_INSTANCE='regexp' in beforeEach which is not cleaned up; add an afterEach (or restore logic) to remove or restore process.env.NODE_APP_INSTANCE after each test that calls requireUncached('./lib/config.mjs') so the environment doesn't leak into other suites; implement this cleanup in the same test file (pair with the existing beforeEach) to either delete process.env.NODE_APP_INSTANCE or reset it to its prior value.test/8-config-extending.js (1)
27-36:NODE_CONFIG_DIRis mutated inside theit()body (line 28) after already being set inbefore()(line 13) — anti-pattern and redundant.Setting
process.envinside a test body bypasses the setup/teardown lifecycle and makes the test's environment invisible to readers scanningbefore/beforeEachhooks. Since line 13 already sets the same value, line 28 is also redundant.♻️ Suggested fix — remove the redundant assignment
it('Extending a configuration with another configuration should work without error', function () { - process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config'; let filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` around lines 27 - 36, The test mutates process.env.NODE_CONFIG_DIR inside the it() block which is redundant because the same value is set in the before() hook; remove the assignment to process.env.NODE_CONFIG_DIR from the it('Extending a configuration...') test body (the line that sets process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config') so the test relies on the before() setup, leaving the rest of the test (reading base-config.json, calling CONFIG.util.attachProtoDeep, and invoking Util.extendDeep) unchanged.test/0-util.js (1)
1534-1544: Commented-out block referencesrequire.extensions, which is unavailable in ESM — remove it.
require.extensionsis a CJS-only API.require.extensionsis not used byimport. The entire commented-out test can be removed; there is no ESM equivalent for checking a custom.tsextension handler.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1534 - 1544, Remove the entire commented-out test that references the CJS-only API require.extensions (the block that checks existingHandler and asserts require.extensions['.ts'] after calling util.loadFileConfigs with import.meta.dirname and '.ts'); simply delete those commented lines since require.extensions is unavailable in ESM and there is no ESM equivalent, leaving no replacement code.test/21-date.js (1)
1-13: LGTM —beforeis appropriate here; considerpath.joinfor cross-platform safety.The one-time
beforehook is correct since all inner tests are read-only and do not mutateCONFIG. The only nit is the path string concatenation:🔧 Optional: use `path.join` for cross-platform path construction
+import path from 'node:path'; import { describe, it, before } from 'node:test'; import assert from 'assert'; import { requireUncached } from './_utils/requireUncached.mjs'; describe('Tests for date', function() { let CONFIG; before(async function() { - process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config'; + process.env.NODE_CONFIG_DIR = path.join(import.meta.dirname, 'config'); process.env.NODE_ENV = 'test';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/21-date.js` around lines 1 - 13, Replace the string concatenation for NODE_CONFIG_DIR with a cross-platform path join: instead of using import.meta.dirname + '/config' compute the directory from import.meta.url (via url.fileURLToPath and path.dirname) and then use path.join(dir, 'config'); update the top of the test to import/require the Node 'path' and 'url' helpers and keep the existing before hook, CONFIG variable, and requireUncached('./lib/config.mjs') unchanged.test/2-config-test.js (1)
62-147: Innerdescribeblocks load config viarequireUncachedwithout re-setting env vars — implicit dependency onLibrary initializationrunning first.The
Immutability,Configurations from $NODE_CONFIG,Configurations from --NODE_CONFIG, and other innerdescribeblocks each have abeforeEachthat calls onlyrequireUncached('./lib/config.mjs')with no env setup. They silently depend onNODE_CONFIG_DIR,NODE_ENV,NODE_APP_INSTANCE,NODE_CONFIG, andprocess.argvremaining set from theLibrary initializationbeforeEach above — which only runs for tests within that specific innerdescribe.This works today because
node:testruns tests sequentially, but any of these inner suites run in isolation (or if test order changes) would load config from an indeterminate environment.The shared setup belongs at the outer
describe('Test suite for node-config')scope:♻️ Suggested refactor (sketch)
describe('Test suite for node-config', function() { + beforeEach(async function () { + process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config'; + process.env.NODE_ENV = 'test'; + process.env.NODE_APP_INSTANCE = '3'; + process.env.NODE_CONFIG = '{"EnvOverride":{"parm3":"overridden from $NODE_CONFIG","parm4":100}}'; + process.argv = [undefined, undefined, '--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}']; + process.env.CUSTOM_JSON_ENVIRONMENT_VAR = 'CUSTOM VALUE FROM JSON ENV MAPPING'; + // ...remaining env setup... + }); + describe('Library initialization', function() { let config; - beforeEach(async function () { - process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config'; - // ...etc... - config = await requireUncached('./lib/config.mjs'); - }); + beforeEach(async function () { config = await requireUncached('./lib/config.mjs'); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/2-config-test.js` around lines 62 - 147, The inner suites (Immutability, Configurations from $NODE_CONFIG, Configurations from --NODE_CONFIG) call requireUncached('./lib/config.mjs') without re-establishing environment, implicitly depending on the outer "Test suite for node-config" setup; fix by moving the shared env/argv initialization (NODE_CONFIG_DIR, NODE_ENV, NODE_APP_INSTANCE, NODE_CONFIG, process.argv) into the outer describe's setup (use before or beforeEach at the outer describe level) so every inner requireUncached loads a deterministically configured environment, and add matching cleanup (after or afterEach) to restore original env/argv; keep inner beforeEach only calling requireUncached('./lib/config.mjs') so tests remain isolated.test/5-getConfigSources.js (1)
10-26: LGTM — butprocess.argvis left in a modified state after all tests in this file.
process.argvis replaced but never restored, leaving it as["node","path/to/some.js","--NODE_CONFIG={}"]after the first describe's last test. Subsequent test files that rely on the originalprocess.argvmay be affected. Consider restoring with anafterhook:🔧 Suggested cleanup
+import { describe, it, beforeEach, after } from 'node:test'; ... describe('tests with NODE_CONFIG env set...', function() { + let savedArgv; + after(() => { process.argv = savedArgv; }); beforeEach(async function () { + savedArgv = savedArgv ?? process.argv; process.argv = ["node","path/to/some.js","--NODE_CONFIG={}"];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/5-getConfigSources.js` around lines 10 - 26, Save the original process.argv at the start of the test setup and restore it in a teardown hook so other tests aren't affected: in the beforeEach (where process.argv is set to ["node","path/to/some.js","--NODE_CONFIG={}"]) capture the original value (e.g. const originalArgv = process.argv.slice()) and add an after or afterEach hook that resets process.argv = originalArgv; ensure this lives alongside the existing requireUncached('./lib/config.mjs') / sources = config.util.getConfigSources() setup so tests clean up after themselves.test/6-strict-mode.js (1)
77-97: NoafterEachcleanup for process environment variables.
NODE_CONFIG_DIR,NODE_CONFIG_STRICT_MODE,NODE_APP_INSTANCE,NODE_ENV, andNODE_CONFIG_ENVare mutated inbeforeEachbut never restored after each test. If any other test suite runs in the same process after this one, it may inherit contaminated environment state.♻️ Suggested `afterEach` teardown
return () => { + let origEnv; beforeEach(async function() { + error = undefined; + origEnv = { + NODE_CONFIG_DIR: process.env.NODE_CONFIG_DIR, + NODE_CONFIG_STRICT_MODE: process.env.NODE_CONFIG_STRICT_MODE, + NODE_APP_INSTANCE: process.env.NODE_APP_INSTANCE, + NODE_ENV: process.env.NODE_ENV, + NODE_CONFIG_ENV: process.env.NODE_CONFIG_ENV, + NODE_CONFIG: process.env.NODE_CONFIG, + }; process.env.NODE_CONFIG_DIR = import.meta.dirname + '/6-config'; // ... }); + + afterEach(function() { + for (const [key, val] of Object.entries(origEnv)) { + if (val === undefined) delete process.env[key]; + else process.env[key] = val; + } + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/6-strict-mode.js` around lines 77 - 97, Tests set environment variables in beforeEach (process.env.NODE_CONFIG_DIR, NODE_CONFIG_STRICT_MODE, NODE_APP_INSTANCE, NODE_ENV, NODE_CONFIG_ENV and delete process.env.NODE_CONFIG) and never restore them; update the test to capture the original values before mutation and add an afterEach that restores those originals (reassign saved values or delete if undefined) and also clear any side-effects from requireUncached/require (so the next test runs with a clean environment); locate the setup in beforeEach and implement the teardown symmetrically in an afterEach in the same test file surrounding the requireUncached call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/util.js`:
- Around line 1451-1467: The catch block in _loadParser currently logs errors
but falls through returning undefined, which then overwrites
DEFAULT_OPTIONS.parser and breaks callers like options.parser.getFilesOrder;
update the catch block inside function _loadParser to return the fallback
defaultParser (i.e., return defaultParser) after logging the failure and error,
leaving the existing Path.resolve(dir, name) and createRequire usage intact so
file-path usage remains supported.
In `@package.json`:
- Around line 48-50: Remove the self-referencing "config" entry from the
"dependencies" block in package.json and instead add an "exports" mapping so
local imports like "config" and "config/parser" resolve to local files; update
package.json to remove the dependency "config": "^4.3.0", add an "exports" field
that maps "." to the built entry (e.g. "./lib/config.js") and "./parser" to
"./parser.js", and keep the existing typesVersions mapping intact so
test/type-files/util.ts imports resolve to local sources during development and
type-checking.
In `@test/23-serialize.js`:
- Line 9: The test uses import.meta.dirname to set process.env.NODE_CONFIG_DIR,
but import.meta.dirname is only defined in Node ≥20.11.0 while package.json
currently declares ">=20.0.0"; update the minimum Node.js engine in package.json
to ">=20.11.0" (change the engines field) so import.meta.dirname will be
defined, and ensure any CI/test matrix or docs that advertise the Node
requirement are updated accordingly.
In `@test/6-strict-mode.js`:
- Line 79: The engines constraint in package.json allows Node versions where
import.meta.dirname is undefined, breaking tests that rely on
import.meta.dirname; update the "engines" field in package.json to require Node
>= 20.11.0 by changing the current "node": ">= 20.0.0" to "node": ">= 20.11.0"
so import.meta.dirname is guaranteed to exist for tests that use it (e.g., test
files referencing import.meta.dirname).
In `@test/8-config-extending.js`:
- Line 21: The test sets process.env.NODE_CONFIG_STRICT_MODE to a boolean which
becomes the string "false" and is inconsistent with other tests; change the
assignment so NODE_CONFIG_STRICT_MODE is set to the string '0' (i.e., replace
the boolean assignment to process.env.NODE_CONFIG_STRICT_MODE with the literal
string '0') to match convention used in tests like test/5-getConfigSources.js
and ensure consistent interpretation by the library.
In `@test/type-files/defer.ts`:
- Line 1: The import path in test/type-files/defer.ts is wrong: replace the
non-existent '../../types/lib/defer' with the actual module location
'../../lib/defer' so that the named imports deferConfig and DeferredConfig
resolve correctly; update the import statement to import { deferConfig,
DeferredConfig } from '../../lib/defer'.
In `@test/util.js`:
- Around line 43-53: The test calls to util.loadFileConfigs are passing a string
path which gets spread into the Load constructor options and produces numeric
keys instead of overriding configDir; update the two calls to loadFileConfigs in
test/util.js to pass an options object with the configDir property (e.g. {
configDir: path.join(import.meta.dirname, '5-config') }) so that Load's
this.options correctly overrides configDir and the test loads the intended
directory; reference symbols: util.loadFileConfigs, Load constructor,
this.options, configDir.
In `@test/x-deferred-configs-ts.js`:
- Line 20: The closing brace for the before callback is incorrectly placed on
the same line as the await call (CONFIG = await
requireUncached('./lib/config.mjs'); });); fix the formatting in the test by
moving the `});` that closes the `before` callback onto its own line so that the
`await requireUncached('./lib/config.mjs')` line ends with a semicolon and the
`});` appears on the next line; locate the statement referencing CONFIG and
requireUncached in the before block to apply this change.
---
Outside diff comments:
In `@lib/util.js`:
- Around line 1391-1398: The hostname resolution try/catch currently calls
require('os') (which fails under ESM) and gets swallowed; replace the dynamic
require with the already-imported OS module and call OS.hostname() to set
hostName when hostName is falsy, keeping the existing catch behavior (i.e., set
hostName = '' on error). Update the block that references require('os') so it
uses the imported OS symbol and OS.hostname() instead of require.
- Around line 456-491: resolveAsyncConfigs currently gathers Promise instances
in promises[] but never writes their resolved values back into the original
config structure; update it to track each promise's target location and assign
the resolved value back into the config (for object properties and array
entries) after awaiting Promise.all. Modify the traversal in _iterate to, when
encountering a Promise, push an object containing the promise and a setter
reference (e.g., { promise, parent, keyOrIndex }) or push the promise alongside
its target index in a separate array; after awaiting Promise.all, iterate
results and use the stored parent/keyOrIndex to set the resolved values into the
original config so config.get() returns resolved values. Ensure the logic
handles promises inside arrays and nested objects and preserves the existing
sort/traversal behavior in resolveAsyncConfigs.
In `@test/0-util.js`:
- Around line 1637-1645: The test "loads the values in left-right order"
declares a callback parameter `done` but runs synchronously and never calls it,
causing node:test to hang; fix by removing the `done` parameter from the test
function signature (so it runs as a synchronous test) or alternatively call
`done()` after the assertions; update the test that invokes util.loadFileConfigs
and the surrounding it(...) callback (the function containing the assertion for
config.db.name) to use the synchronous form (no `done`) or to call `done()`
explicitly.
- Around line 761-820: Tests use import.meta.dirname (seen in calls to
load.loadFile with Path.join(import.meta.dirname, ...)), which is only available
in Node >=20.11.0; update the package.json "engines" field from ">= 20.0.0" to
">= 20.11.0" so CI and consumers run a compatible runtime, and rerun tests to
confirm path resolution issues are resolved.
In `@test/1-protected-test.js`:
- Around line 27-45: The test's beforeEach uses process.argv.push(...) which
accumulates the NODE_CONFIG flag across tests; replace the push with an
assignment that sets process.argv to a clean array containing the NODE_CONFIG
entry (matching the pattern used in 2-config-test.js) so each call to
requireUncached('./lib/config.mjs') sees a consistent argv; update the
beforeEach block (the process.argv handling) to assign a fresh array (e.g., with
two leading entries and the '--NODE_CONFIG=...' string) instead of pushing.
In `@test/6-strict-mode.js`:
- Around line 74-97: The shared variable error is not reset at the start of the
beforeEach closure, which can leave stale state across the two it blocks; inside
the beforeEach function (the closure returned by _expectException), set error =
undefined (or null) as the first statement before modifying env and calling
requireUncached('./lib/config.mjs') so each test run starts with a clean error
state.
---
Nitpick comments:
In `@lib/config.mjs`:
- Around line 598-615: The code uses filename.match(env) inside the
load.options.nodeEnv loop which treats env as a regex and can misbehave for
values with regex metacharacters; change the check to perform a literal
substring or exact comparison instead — e.g., use filename.includes(env) or
escape env before using RegExp — so replace the filename.match(env) call in the
sourceFilenames.some callback (referencing sourceFilenames,
load.options.nodeEnv, filename.match and load.getEnv) with a safe literal
comparison and keep the existing _warnOrThrow calls unchanged.
In `@package.json`:
- Line 5: Add an "exports" field to package.json to provide conditional entry
points for ESM and CJS consumers instead of relying on the non-standard "module"
field; map the "import" condition to "./lib/config.mjs" (ESM) and the "require"
condition to "./lib/config.js" (CJS), and keep "main" as fallback if desired, so
Node's resolver uses the correct file for each module system while preserving
the existing "./lib/config.mjs" and "./lib/config.js" artifacts referenced by
"module" and "main".
In `@parser.js`:
- Around line 90-103: In Parser.tsParser replace the unnecessary optional
chaining on require by changing require?.extensions['.ts'] to
require.extensions['.ts'] so the defensive TS lazy-init logic is clearer; locate
the check inside the Parser.tsParser function and update that conditional
expression only (leave the rest of the TS lazy-init and TS.register call
intact).
- Around line 6-7: The createRequire call creating moduleRequire is using
Path.join(process.cwd(), 'node_modules') which forces an extra, unnecessary
lookup; change the moduleRequire createRequire argument to either
createRequire(process.cwd()) to search node_modules directly or provide a
concrete file inside node_modules (e.g., Path.join(process.cwd(),
'node_modules', 'package.json')) so resolution does not probe a non-existent
nested node_modules—update the moduleRequire declaration accordingly (symbols:
moduleRequire, require, createRequire, Path, process.cwd()).
In `@test/_utils/requireUncached.mjs`:
- Line 9: The current resolution uses process.cwd() which breaks if tests run
outside the repo root; change the resolver in requireUncached.mjs to resolve
moduleName relative to the caller's module URL instead of process.cwd().
Specifically, replace Path.resolve(process.cwd(), moduleName) with a resolution
based on import.meta.url (convert to a file path with URL.fileURLToPath and use
Path.dirname(...)) so that requireUncached resolves modules relative to the test
file that imported it; alternatively, document the cwd assumption if you prefer
not to change behavior.
In `@test/0-util.js`:
- Around line 1534-1544: Remove the entire commented-out test that references
the CJS-only API require.extensions (the block that checks existingHandler and
asserts require.extensions['.ts'] after calling util.loadFileConfigs with
import.meta.dirname and '.ts'); simply delete those commented lines since
require.extensions is unavailable in ESM and there is no ESM equivalent, leaving
no replacement code.
In `@test/11-regexp.js`:
- Around line 8-14: The test sets NODE_APP_INSTANCE='regexp' in beforeEach which
is not cleaned up; add an afterEach (or restore logic) to remove or restore
process.env.NODE_APP_INSTANCE after each test that calls
requireUncached('./lib/config.mjs') so the environment doesn't leak into other
suites; implement this cleanup in the same test file (pair with the existing
beforeEach) to either delete process.env.NODE_APP_INSTANCE or reset it to its
prior value.
In `@test/16-custom-parser.js`:
- Around line 78-80: The after hook in test/16-custom-parser.js is declared
async but only performs a synchronous delete of process.env.NODE_CONFIG_PARSER;
remove the unnecessary async qualifier from the anonymous function passed to
after(...) so the hook is a plain synchronous function.
In `@test/2-config-test.js`:
- Around line 62-147: The inner suites (Immutability, Configurations from
$NODE_CONFIG, Configurations from --NODE_CONFIG) call
requireUncached('./lib/config.mjs') without re-establishing environment,
implicitly depending on the outer "Test suite for node-config" setup; fix by
moving the shared env/argv initialization (NODE_CONFIG_DIR, NODE_ENV,
NODE_APP_INSTANCE, NODE_CONFIG, process.argv) into the outer describe's setup
(use before or beforeEach at the outer describe level) so every inner
requireUncached loads a deterministically configured environment, and add
matching cleanup (after or afterEach) to restore original env/argv; keep inner
beforeEach only calling requireUncached('./lib/config.mjs') so tests remain
isolated.
In `@test/21-date.js`:
- Around line 1-13: Replace the string concatenation for NODE_CONFIG_DIR with a
cross-platform path join: instead of using import.meta.dirname + '/config'
compute the directory from import.meta.url (via url.fileURLToPath and
path.dirname) and then use path.join(dir, 'config'); update the top of the test
to import/require the Node 'path' and 'url' helpers and keep the existing before
hook, CONFIG variable, and requireUncached('./lib/config.mjs') unchanged.
In `@test/5-getConfigSources.js`:
- Around line 10-26: Save the original process.argv at the start of the test
setup and restore it in a teardown hook so other tests aren't affected: in the
beforeEach (where process.argv is set to
["node","path/to/some.js","--NODE_CONFIG={}"]) capture the original value (e.g.
const originalArgv = process.argv.slice()) and add an after or afterEach hook
that resets process.argv = originalArgv; ensure this lives alongside the
existing requireUncached('./lib/config.mjs') / sources =
config.util.getConfigSources() setup so tests clean up after themselves.
In `@test/6-strict-mode.js`:
- Around line 77-97: Tests set environment variables in beforeEach
(process.env.NODE_CONFIG_DIR, NODE_CONFIG_STRICT_MODE, NODE_APP_INSTANCE,
NODE_ENV, NODE_CONFIG_ENV and delete process.env.NODE_CONFIG) and never restore
them; update the test to capture the original values before mutation and add an
afterEach that restores those originals (reassign saved values or delete if
undefined) and also clear any side-effects from requireUncached/require (so the
next test runs with a clean environment); locate the setup in beforeEach and
implement the teardown symmetrically in an afterEach in the same test file
surrounding the requireUncached call.
In `@test/8-config-extending.js`:
- Around line 27-36: The test mutates process.env.NODE_CONFIG_DIR inside the
it() block which is redundant because the same value is set in the before()
hook; remove the assignment to process.env.NODE_CONFIG_DIR from the
it('Extending a configuration...') test body (the line that sets
process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config') so the test
relies on the before() setup, leaving the rest of the test (reading
base-config.json, calling CONFIG.util.attachProtoDeep, and invoking
Util.extendDeep) unchanged.
In `@test/9-raw-configs.js`:
- Around line 8-13: The closing brace and parenthesis for the before(async() =>
{ ... }) hook are placed on the same line as the CONFIG assignment, making the
formatting look collapsed; update the before hook around CONFIG = await
requireUncached('./lib/config.mjs') so the final `});` is on its own line (i.e.,
close the async function block and the before call on a separate line) to
restore proper readability and consistent formatting in test/9-raw-configs.js.
4c86b63 to
1e40f7c
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
lib/util.js (1)
1391-1398:⚠️ Potential issue | 🔴 Critical
require('os')is not defined in ESM scope — hostname detection silently fails.
lib/util.jsis now an ES module (top-levelimportat line 8,exportat line 1469). In ESM,__filename,__dirname, andrequireare not available;require()can be created viamodule.createRequire(). The barerequire('os')on line 1393 will throwReferenceError: require is not definedat runtime. Because the surroundingtry/catchcatches all errors and setshostName = '', this failure is completely silent — every process launched withoutHOST/HOSTNAMEenv vars will get an empty string hostname instead of the OS hostname.
OSis already imported at line 10 (import OS from 'node:os'), so the fix is trivial:🐛 Proposed fix
- try { - if (!hostName) { - const OS = require('os'); - hostName = OS.hostname(); - } - } catch (e) { - hostName = ''; - } + if (!hostName) { + hostName = OS.hostname(); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/util.js` around lines 1391 - 1398, The try/catch around hostname detection uses a legacy require('os') call which fails in ESM; update the code that sets hostName (the try block referencing require('os')) to use the already-imported OS symbol (import OS from 'node:os') instead of calling require, e.g. call OS.hostname() when hostName is falsy, and keep the minimal error handling (or narrow the catch) so failures are not silently swallowed; change only the require('os') usage inside the hostName assignment logic (the try block that sets hostName = OS.hostname()).package.json (2)
1-93:⚠️ Potential issue | 🟠 MajorMissing
"type": "module"— all ESM.jsfiles will be treated as CommonJS without it.Every
.jsfile that usesimport/exportsyntax (lib/util.js,test/makeImmutable-shared-refs.js,test/22-binary.js,test/16-config/parser/custom-*.js, etc.) will throw aSyntaxErrorbecause the default"type"value when the field is absent is"commonjs", meaning Node.js treats all.jsfiles as CommonJS modules.However,
lib/config.jsusesmodule.exports, which is invalid in an ESM package, creating a conflict. The standard resolution is:🔧 Proposed fix
"main": "./lib/config.js", + "type": "module",And rename
lib/config.js→lib/config.cjs, updatepackage.json:- "main": "./lib/config.js", + "main": "./lib/config.cjs",Alternatively, rename all ESM
.jslibrary and test files to.mjs(avoids changing the package type for consumers).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 1 - 93, The package.json lacks "type":"module", causing .js files with ESM syntax to be parsed as CommonJS and conflicting with lib/config.js using module.exports; add "type": "module" to package.json and make lib/config.js a CommonJS file by renaming it to lib/config.cjs and update the "main" field from "./lib/config.js" to "./lib/config.cjs" (keep "module": "./lib/config.mjs" as-is), or alternatively convert all ESM .js files to .mjs and keep package.json without "type" — pick the former solution and ensure references to main/module (and any imports of lib/config.js) are updated to the new filename.
73-75:⚠️ Potential issue | 🟠 MajorUpdate
engines.nodeto ">= 22.12.0" — current declaration is too broad.The codebase requires Node.js 22.12.0 or higher due to three features:
require('./config.mjs').defaultrequires Node.js 22.12.0+ (stable require() of ES modules without--experimental-require-moduleflag)import.meta.dirnameis used extensively but unavailable on Node.js 20.0.0–20.10.x--no-experimental-strip-typesflag in test script is a Node.js 22+ featureDeclaring
>= 20.0.0will causeERR_REQUIRE_ESMerrors on Node.js 20.x and 22.0–22.11.x.Diff
- "node": ">= 20.0.0" + "node": ">= 22.12.0"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 73 - 75, Update the package.json engines.node field from ">= 20.0.0" to ">= 22.12.0" so the runtime requirement matches features used (referenced symbols: engines.node in package.json), because the codebase uses require('./config.mjs').default, import.meta.dirname, and the --no-experimental-strip-types flag which require Node.js 22.12.0+. Change the engines.node string accordingly and verify any CI or documentation that asserts Node versions reflects the new minimum.test/1-protected-test.js (1)
27-45:⚠️ Potential issue | 🟡 Minor
process.argv.pushinbeforeEachaccumulates duplicate entries across tests.Each
beforeEachinvocation appends another--NODE_CONFIG=…entry toprocess.argvwithout ever removing previous ones. After N tests in this suite, argv will contain N copies. Contrast withtest/2-config-test.jsLine 30 which replacesprocess.argventirely.If the config parser iterates all argv entries, duplicate overrides are parsed on every fresh config load — identical values won't break anything today, but it's a latent bug if values ever diverge or if another test file runs in the same process afterward.
Suggested fix: replace argv instead of pushing
- process.argv.push('--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}'); + process.argv = [process.argv[0], process.argv[1], '--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}'];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/1-protected-test.js` around lines 27 - 45, The test's beforeEach mutates process.argv by pushing a flag, causing duplicates across runs; instead, in beforeEach (the function that calls requireUncached('./lib/config.mjs')) replace process.argv with a fresh array that preserves the node/executable entries and sets the single --NODE_CONFIG entry (or restore a saved original argv then append the single flag), so each test starts with a deterministic argv and no accumulated duplicates.test/0-util.js (1)
1637-1644:⚠️ Potential issue | 🟡 MinorMisleading
doneparameter — leftover from Mocha-style testIn
node:test, the first callback argument is always aTestContextobject, not a done-callback. The test completes synchronously regardless of whether this parameter is invoked. Thedonename is a misleading holdover from the Mocha migration and should be removed.🛠 Proposed fix
- it('loads the values in left-right order', function(done) { + it('loads the values in left-right order', function() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1637 - 1644, The test callback for the "loads the values in left-right order" case incorrectly declares a Mocha-style done parameter; update the it(...) callback signature to remove the done argument (change function(done) { ... } to function() { ... } or to an arrow () => { ... }) and remove any references to done() within that test; the code block to change surrounds the it(...) declaration that calls util.loadFileConfigs and asserts config.db.name.
🧹 Nitpick comments (9)
test/16-custom-parser.js (1)
9-13: First suite'sbeforeEachdoesn't clearNODE_CONFIG_PARSER.If a prior test file sets
NODE_CONFIG_PARSERand it leaks, this sanity-check suite ("Using the default parser") would pick it up. Consider addingdelete process.env.NODE_CONFIG_PARSERhere for robustness.beforeEach(async function() { process.env.NODE_CONFIG_DIR = import.meta.dirname + '/16-config'; + delete process.env.NODE_CONFIG_PARSER; config = await requireUncached('./lib/config.mjs'); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/16-custom-parser.js` around lines 9 - 13, The beforeEach in the "Using the default parser" suite should explicitly clear any leaked parser env var: add a statement to delete process.env.NODE_CONFIG_PARSER at the start of the beforeEach (before setting process.env.NODE_CONFIG_DIR and calling requireUncached('./lib/config.mjs')) so the default parser is used reliably; modify the beforeEach block in test/16-custom-parser.js accordingly (refer to the beforeEach function and the NODE_CONFIG_PARSER environment variable).parser.js (1)
90-102: Unnecessary optional chaining onrequire.
requireis always defined (assigned on line 7 viacreateRequire). The?.onrequire?.extensionsis a no-op here.- if (require?.extensions['.ts'] === undefined) { + if (require.extensions['.ts'] === undefined) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 90 - 102, The optional chaining on require in Parser.tsParser is unnecessary because require is always defined (created earlier via createRequire); remove the `?.` so the code uses `require.extensions['.ts']` directly. Update the check in the Parser.tsParser function that currently reads `require?.extensions['.ts'] === undefined` to use `require.extensions['.ts'] === undefined`, ensuring the rest of the logic (including TS loading via TS_DEP and TS.register) remains unchanged.lib/config.mjs (2)
529-556:attachProtoDeepiterates withfor...inover config objects.
for (const prop in toObject)enumerates inherited properties as well. IftoObjecthas a prototype chain beyondObject.prototype(e.g., afterUtil.extendDeep), unexpected inherited keys could be traversed. AhasOwnPropertyguard (orObject.keys) would be safer, though this appears to replicate pre-existing behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 529 - 556, attachProtoDeep currently uses "for (const prop in toObject)" which iterates inherited properties; change this to iterate only own keys (e.g., use Object.keys(toObject).forEach or guard with Object.prototype.hasOwnProperty.call(toObject, prop)) so only own enumerable properties get processed; update the loop in attachProtoDeep to skip inherited properties while preserving the recursion (depth - 1) and the Util.isObject check so sub-objects receive prototypes correctly.
84-107:get()freezes config on first access — consider documenting the side effect.The first
get()call makes the entire config immutable (unlessALLOW_CONFIG_MUTATIONSis set). This is a significant side effect that may surprise callers accessing a single property. A brief inline comment or JSDoc note about the freeze-on-first-read behavior would help maintainability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 84 - 107, The config.get method has a surprising side effect: on first call it freezes the config via Object.isExtensible/this.util.makeImmutable unless LOAD_SYMBOL.initParam('ALLOW_CONFIG_MUTATIONS', false) permits mutations; add a brief JSDoc or inline comment above the get(property) implementation (or above the class) explicitly documenting the "freeze-on-first-read" behavior, mention the ALLOW_CONFIG_MUTATIONS flag and LOAD_SYMBOL interaction, and note that subsequent calls cannot mutate the config so callers should read needed values before relying on mutations.test/5-getConfigSources.js (1)
90-104: Inconsistent indentation in the lastdescribeblock.Lines 91–103 use 1-space indentation while the rest of the file uses standard 2/4-space indentation. Likely pre-existing, but now is a good time to normalize.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/5-getConfigSources.js` around lines 90 - 104, The describe block starting with "describe('Files which return empty objects still end up in getConfigSources()'" and its nested beforeEach should be re-indented to match the project's indentation style (use the same 2/4-space indentation used elsewhere in the file) so the block aligns with other tests; update the leading spaces for the lines containing describe(...), let sources;, beforeEach(async function () {, the body that sets process.env and calls requireUncached('./lib/config.mjs'), and the closing braces so they follow the file's standard indentation and keep existing code unchanged.test/8-config-extending.js (1)
26-30: RedundantNODE_CONFIG_DIRreassignment inside the test.Line 28 re-sets
NODE_CONFIG_DIRto the same value already established in thebeforehook (Line 13). This is harmless but unnecessary — consider removing it to reduce noise.Suggested fix
it('Extending a configuration with another configuration should work without error', function () { - process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config'; let filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` around lines 26 - 30, Remove the redundant reassignment of process.env.NODE_CONFIG_DIR inside the test; rely on the value set in the before hook instead. In the test "Extending a configuration with another configuration should work without error" delete the line that sets process.env.NODE_CONFIG_DIR (the one right before building filename with path.join) so the test uses the existing env from the before hook and keeps the rest of the code (filename, base_config, FileSystem.readFileSync) unchanged.test/6-strict-mode.js (1)
73-97:erroris not reset toundefinedat the start ofbeforeEach.If
requireUncacheddoes not throw in a subsequentbeforeEachrun, a staleerrorfrom a prior run would leak into the nextitblock. In the current test structure this can't happen (same env ⇒ same throw/no-throw outcome per describe), but it's a latent fragility.Suggested fix
beforeEach(async function() { + error = undefined; // Change the configuration directory for testing process.env.NODE_CONFIG_DIR = import.meta.dirname + '/6-config';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/6-strict-mode.js` around lines 73 - 97, The variable error in _expectException() can carry a stale value between runs; inside the beforeEach callback (the one that sets env and then calls requireUncached('./lib/config.mjs')) reset error to undefined at the start of that beforeEach so each test run starts with a clean state; update the beforeEach in _expectException to set error = undefined before attempting the requireUncached call.test/2-config-test.js (1)
62-67: Implicit env-var dependency between siblingdescribeblocks.The
Immutabilitydescribe (and all subsequent sibling describes) relies onprocess.env.NODE_CONFIG_DIR,NODE_ENV, etc. having been set by theLibrary initializationbeforeEach. This works becausenode:testruns sibling describes sequentially in-process, but it's fragile if tests are ever run in isolation or reordered.This appears to be a pre-existing pattern carried over from the CJS version, so not blocking — just noting for awareness.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/2-config-test.js` around lines 62 - 67, The Immutability describe block has an implicit dependency on environment variables set by the sibling "Library initialization" beforeEach; update the Immutability beforeEach that calls requireUncached('./lib/config.mjs') to explicitly set and export the required env vars (e.g., process.env.NODE_CONFIG_DIR, process.env.NODE_ENV) before loading, and restore or remove them in an afterEach so the test is isolated from other sibling describes; reference the "Immutability" describe, its beforeEach, requireUncached('./lib/config.mjs'), and the sibling "Library initialization" beforeEach when making the change.test/0-util.js (1)
1187-1235: PreferPath.join()over string concatenation forconfigDirpathsLines 1187, 1198, 1214, 1235 (and again at 1696, 1703, 1710, 1724, 1733, 1742) use
import.meta.dirname + '/config'while the rest of the file consistently usesPath.join(import.meta.dirname, ...). The concatenation hardcodes a POSIX separator, which is inconsistent and would misfire on Windows.♻️ Proposed fix (representative — same pattern applies to lines 1696–1742)
- let load = new Load({nodeEnv: 'production', configDir: import.meta.dirname + '/config'}) + let load = new Load({nodeEnv: 'production', configDir: Path.join(import.meta.dirname, 'config')})- let load = new Load({configDir: import.meta.dirname + '/config'}); + let load = new Load({configDir: Path.join(import.meta.dirname, 'config')});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1187 - 1235, The tests instantiate Load with configDir built via string concatenation (import.meta.dirname + '/config') which hardcodes a POSIX separator; update each construction (e.g. the Load(...) calls used in the tests that call load.loadCustomEnvVars()) to use Path.join(import.meta.dirname, 'config') instead so path resolution is cross-platform and consistent with the rest of the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/config.js`:
- Line 4: The current lib/config.js uses synchronous
require('./config.mjs').default which throws ERR_REQUIRE_ESM on Node.js 20.x;
change the module to load the ESM via dynamic import instead of require and
expose a compatible API (e.g., export a getConfig async function or export a
Promise) so callers can obtain the default export asynchronously. Specifically,
replace the direct require usage in lib/config.js with code that does
import('./config.mjs') and returns the module's default export (reference the
existing module.exports usage and the './config.mjs' target) so consumers call
await getConfig() or await the exported Promise; this avoids ERR_REQUIRE_ESM and
keeps behavior stable until/if you convert to ESM or rename to .cjs.
In `@test/0-util.js`:
- Around line 7-14: The package.json engines field currently states "node": ">=
20.0.0" but the codebase uses import.meta.dirname (used across the repo) which
requires Node.js 20.11.0 or newer; update package.json to set "engines": {
"node": ">= 20.11.0" } so CI and users running versions 20.0.0–20.10.x won't
break at runtime when code (e.g., tests like test/0-util.js and modules
referencing import.meta.dirname) relies on that backported feature.
In `@test/3-config/local.js`:
- Around line 1-22: The deferredOriginal property is placed at the top level but
must be nested inside the original object to match the default config and tests;
move the deferredOriginal: defer(function(cfg, original) { return original; })
entry into the existing original object (alongside original: defer(...)) so that
CONFIG.original.deferredOriginal is overridden correctly during merge and aligns
with the deferredOriginal in default.js and TypeScript equivalents.
In `@test/9-raw-configs.js`:
- Line 13: The closing brace/paren of the Mocha before hook is accidentally
appended to the same line as the await call (CONFIG = await
requireUncached('./lib/config.mjs'); });), making formatting wrong; edit the
before callback that contains CONFIG = await requireUncached('./lib/config.mjs')
so the closing "});" is moved to its own line (i.e., end the await line with a
semicolon and place the "}" and ")" on the following line), and apply the same
fix in the analogous case in test/x-deferred-configs-ts.js where the before
callback closing is appended to the await.
---
Outside diff comments:
In `@lib/util.js`:
- Around line 1391-1398: The try/catch around hostname detection uses a legacy
require('os') call which fails in ESM; update the code that sets hostName (the
try block referencing require('os')) to use the already-imported OS symbol
(import OS from 'node:os') instead of calling require, e.g. call OS.hostname()
when hostName is falsy, and keep the minimal error handling (or narrow the
catch) so failures are not silently swallowed; change only the require('os')
usage inside the hostName assignment logic (the try block that sets hostName =
OS.hostname()).
In `@package.json`:
- Around line 1-93: The package.json lacks "type":"module", causing .js files
with ESM syntax to be parsed as CommonJS and conflicting with lib/config.js
using module.exports; add "type": "module" to package.json and make
lib/config.js a CommonJS file by renaming it to lib/config.cjs and update the
"main" field from "./lib/config.js" to "./lib/config.cjs" (keep "module":
"./lib/config.mjs" as-is), or alternatively convert all ESM .js files to .mjs
and keep package.json without "type" — pick the former solution and ensure
references to main/module (and any imports of lib/config.js) are updated to the
new filename.
- Around line 73-75: Update the package.json engines.node field from ">= 20.0.0"
to ">= 22.12.0" so the runtime requirement matches features used (referenced
symbols: engines.node in package.json), because the codebase uses
require('./config.mjs').default, import.meta.dirname, and the
--no-experimental-strip-types flag which require Node.js 22.12.0+. Change the
engines.node string accordingly and verify any CI or documentation that asserts
Node versions reflects the new minimum.
In `@test/0-util.js`:
- Around line 1637-1644: The test callback for the "loads the values in
left-right order" case incorrectly declares a Mocha-style done parameter; update
the it(...) callback signature to remove the done argument (change
function(done) { ... } to function() { ... } or to an arrow () => { ... }) and
remove any references to done() within that test; the code block to change
surrounds the it(...) declaration that calls util.loadFileConfigs and asserts
config.db.name.
In `@test/1-protected-test.js`:
- Around line 27-45: The test's beforeEach mutates process.argv by pushing a
flag, causing duplicates across runs; instead, in beforeEach (the function that
calls requireUncached('./lib/config.mjs')) replace process.argv with a fresh
array that preserves the node/executable entries and sets the single
--NODE_CONFIG entry (or restore a saved original argv then append the single
flag), so each test starts with a deterministic argv and no accumulated
duplicates.
---
Duplicate comments:
In `@lib/util.js`:
- Around line 1451-1467: _loadParser currently swallows errors and returns
undefined if require fails; update the catch block in function _loadParser to
return defaultParser instead of falling through, and improve the logging to
include the caught error (use the caught variable e) so callers of _loadParser
always get a parser (defaultParser) when require(Path.resolve(dir, name)) in the
try of _loadParser fails.
In `@package.json`:
- Around line 48-50: The package.json currently declares a self-referencing
dependency "config": "^4.3.0" which duplicates the project itself; remove the
"config" entry from the "dependencies" block in package.json (and from any other
dependency lists) so the project does not depend on itself, then run your
package manager (npm install or yarn install) to update lockfiles and verify no
other references remain; look for the "config" string in the package.json and
ensure only external packages remain.
In `@test/8-config-extending.js`:
- Line 21: Replace the boolean assignment to the environment variable with a
string to match the test suite convention: change the statement setting
process.env.NODE_CONFIG_STRICT_MODE (in test/8-config-extending.js) from false
to the string '0' so the variable is consistently treated as a string like in
test/5-getConfigSources.js.
In `@test/type-files/defer.ts`:
- Line 1: The import statement for deferConfig and DeferredConfig in
test/type-files/defer.ts is using the duplicate path '../../types/lib/defer';
update this to the canonical import used elsewhere in the project (or remove the
duplicate and reuse the already-imported symbols) so there is only a single,
consistent import source for deferConfig and DeferredConfig; locate the import
statement referencing '../../types/lib/defer' and replace it with the project's
standard path or consolidate with the existing import of
deferConfig/DeferredConfig.
In `@test/x-deferred-configs-ts.js`:
- Line 20: The statement assigning CONFIG uses "CONFIG = await
requireUncached('./lib/config.mjs'); });" with the closing "});" on the same
line as the await expression; move the closing "});" onto its own properly
indented line so the await call ends on its own line (e.g., end the await
expression with a semicolon then place "});" on the next line) to satisfy the
formatting rule around requireUncached and CONFIG.
---
Nitpick comments:
In `@lib/config.mjs`:
- Around line 529-556: attachProtoDeep currently uses "for (const prop in
toObject)" which iterates inherited properties; change this to iterate only own
keys (e.g., use Object.keys(toObject).forEach or guard with
Object.prototype.hasOwnProperty.call(toObject, prop)) so only own enumerable
properties get processed; update the loop in attachProtoDeep to skip inherited
properties while preserving the recursion (depth - 1) and the Util.isObject
check so sub-objects receive prototypes correctly.
- Around line 84-107: The config.get method has a surprising side effect: on
first call it freezes the config via Object.isExtensible/this.util.makeImmutable
unless LOAD_SYMBOL.initParam('ALLOW_CONFIG_MUTATIONS', false) permits mutations;
add a brief JSDoc or inline comment above the get(property) implementation (or
above the class) explicitly documenting the "freeze-on-first-read" behavior,
mention the ALLOW_CONFIG_MUTATIONS flag and LOAD_SYMBOL interaction, and note
that subsequent calls cannot mutate the config so callers should read needed
values before relying on mutations.
In `@parser.js`:
- Around line 90-102: The optional chaining on require in Parser.tsParser is
unnecessary because require is always defined (created earlier via
createRequire); remove the `?.` so the code uses `require.extensions['.ts']`
directly. Update the check in the Parser.tsParser function that currently reads
`require?.extensions['.ts'] === undefined` to use `require.extensions['.ts'] ===
undefined`, ensuring the rest of the logic (including TS loading via TS_DEP and
TS.register) remains unchanged.
In `@test/0-util.js`:
- Around line 1187-1235: The tests instantiate Load with configDir built via
string concatenation (import.meta.dirname + '/config') which hardcodes a POSIX
separator; update each construction (e.g. the Load(...) calls used in the tests
that call load.loadCustomEnvVars()) to use Path.join(import.meta.dirname,
'config') instead so path resolution is cross-platform and consistent with the
rest of the file.
In `@test/16-custom-parser.js`:
- Around line 9-13: The beforeEach in the "Using the default parser" suite
should explicitly clear any leaked parser env var: add a statement to delete
process.env.NODE_CONFIG_PARSER at the start of the beforeEach (before setting
process.env.NODE_CONFIG_DIR and calling requireUncached('./lib/config.mjs')) so
the default parser is used reliably; modify the beforeEach block in
test/16-custom-parser.js accordingly (refer to the beforeEach function and the
NODE_CONFIG_PARSER environment variable).
In `@test/2-config-test.js`:
- Around line 62-67: The Immutability describe block has an implicit dependency
on environment variables set by the sibling "Library initialization" beforeEach;
update the Immutability beforeEach that calls
requireUncached('./lib/config.mjs') to explicitly set and export the required
env vars (e.g., process.env.NODE_CONFIG_DIR, process.env.NODE_ENV) before
loading, and restore or remove them in an afterEach so the test is isolated from
other sibling describes; reference the "Immutability" describe, its beforeEach,
requireUncached('./lib/config.mjs'), and the sibling "Library initialization"
beforeEach when making the change.
In `@test/5-getConfigSources.js`:
- Around line 90-104: The describe block starting with "describe('Files which
return empty objects still end up in getConfigSources()'" and its nested
beforeEach should be re-indented to match the project's indentation style (use
the same 2/4-space indentation used elsewhere in the file) so the block aligns
with other tests; update the leading spaces for the lines containing
describe(...), let sources;, beforeEach(async function () {, the body that sets
process.env and calls requireUncached('./lib/config.mjs'), and the closing
braces so they follow the file's standard indentation and keep existing code
unchanged.
In `@test/6-strict-mode.js`:
- Around line 73-97: The variable error in _expectException() can carry a stale
value between runs; inside the beforeEach callback (the one that sets env and
then calls requireUncached('./lib/config.mjs')) reset error to undefined at the
start of that beforeEach so each test run starts with a clean state; update the
beforeEach in _expectException to set error = undefined before attempting the
requireUncached call.
In `@test/8-config-extending.js`:
- Around line 26-30: Remove the redundant reassignment of
process.env.NODE_CONFIG_DIR inside the test; rely on the value set in the before
hook instead. In the test "Extending a configuration with another configuration
should work without error" delete the line that sets process.env.NODE_CONFIG_DIR
(the one right before building filename with path.join) so the test uses the
existing env from the before hook and keeps the rest of the code (filename,
base_config, FileSystem.readFileSync) unchanged.
5b9b4d1 to
4ed70b9
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
package.json (2)
75-81:⚠️ Potential issue | 🟡 Minor
c8coverage config misses.mjsfiles.The include patterns
["*.js", "lib/**/*.js"]won't capturelib/config.mjs, which is now the primary runtime entry point. Coverage reports will undercount.Proposed fix
"c8": { "include": [ "*.js", - "lib/**/*.js" + "lib/**/*.js", + "*.mjs", + "lib/**/*.mjs" ],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 75 - 81, The c8 coverage "include" array currently only matches .js files and therefore misses lib/config.mjs; update the c8 config's "include" array to also match .mjs files (e.g., add "*.mjs" and "lib/**/*.mjs" or replace with patterns that include both .js and .mjs) so lib/config.mjs is captured by coverage; refer to the c8 "include" array and the lib/config.mjs entry point when making the change.
1-6:⚠️ Potential issue | 🔴 CriticalMissing
"type": "module"— ESM syntax in.jsfiles will fail at runtime.
parser.js,lib/defer.js, andlib/util.jsuse top-levelimport/exportsyntax, butpackage.jsondoes not declare"type": "module". Node.js defaults.jsfiles to CommonJS, so these files will throwSyntaxError: Cannot use import statement in a module. Onlylib/config.mjs(which uses the.mjsextension) would work correctly.Add
"type": "module"to enable ESM for all.jsfiles, or rename the affected files to.mjs.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 1 - 6, The package.json lacks the "type": "module" field while files parser.js, lib/defer.js, and lib/util.js use ESM imports/exports (only lib/config.mjs currently uses .mjs); add "type": "module" to package.json to make .js files be treated as ESM, or alternatively rename the ESM .js files (parser.js, lib/defer.js, lib/util.js) to .mjs so Node treats them as modules, and keep lib/config.mjs unchanged.test/0-util.js (1)
1637-1645:⚠️ Potential issue | 🟠 Major
donecallback declared but never called — test will hang/timeout.
function(done)tellsnode:test(and Mocha) to wait fordone()to be invoked. Since the test body only runs synchronous assertions and never callsdone(), it will timeout.Proposed fix
- it('loads the values in left-right order', function(done) { + it('loads the values in left-right order', function() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1637 - 1645, The test "loads the values in left-right order" declares a done callback but never calls it, causing the test to hang; fix by making the test synchronous: remove the done parameter from the it(...) callback (so it becomes function() { ... }) or alternatively call done() after the synchronous assertion; locate the test case that calls util.loadFileConfigs and assert.strictEqual(config.db.name, ...) and either remove the unused done parameter or add a final done() invocation.
🧹 Nitpick comments (7)
test/_utils/requireUncached.mjs (1)
8-11: Cross-platform issue: appending a query string to a filesystem path may fail on Windows.On Unix,
import('/abs/path/file.mjs?c=0')works because Node.js treats/-prefixed specifiers as URL-like. On Windows,Path.resolveyieldsC:\repo\lib\config.mjs, andimport('C:\\repo\\lib\\config.mjs?c=0')is not a valid URL — Node may try to open a file literally namedconfig.mjs?c=0.The robust approach is to use
pathToFileURL:♻️ Proposed fix for cross-platform cache-busting
-import Path from 'path'; +import Path from 'path'; +import { pathToFileURL } from 'node:url'; let count = 0; export async function requireUncached(moduleName) { let path = Path.resolve(process.cwd(), moduleName); - let module = await import(`${path}?c=${count++}`); + let url = pathToFileURL(path); + url.searchParams.set('c', String(count++)); + let module = await import(url.href); return module.default; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/_utils/requireUncached.mjs` around lines 8 - 11, The requireUncached function currently builds a filesystem path via Path.resolve and appends a query string to it before dynamic import, which breaks on Windows; change it to convert the resolved path to a file:// URL using pathToFileURL (from the 'url' module) and then append the cache-busting query parameter to that URL string before calling import. Update requireUncached (and any uses of Path.resolve in this function) to use pathToFileURL(resolvedPath).href + `?c=${count++}` so imports are valid cross-platform.parser.js (1)
90-115:require?.extensions— optional chaining is unnecessary here.
require(line 7) is assigned fromcreateRequire(process.cwd())and is always defined. The optional chainingrequire?.extensionsis harmless but misleading — it suggestsrequiremight be nullish.♻️ Minor clarity fix
- if (require?.extensions['.ts'] === undefined) { + if (require.extensions['.ts'] === undefined) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 90 - 115, In Parser.tsParser remove the unnecessary optional chaining on require when checking extensions: replace the `require?.extensions['.ts'] === undefined` check with a direct access `require.extensions['.ts'] === undefined` (or a clearer explicit check like `typeof require.extensions === 'undefined'`) so the code uses the known non-nullable `require` provided by createRequire; update the conditional in Parser.tsParser accordingly.lib/config.mjs (1)
192-207: Potential stale reference:existingis read before the mutation, then extended in place.
Util.getPath(line 197) fetches the object reference afterload.setModuleDefaultshas already mutatedload.config. Since the constructor didUtil.extendDeep(this, load.config),thisandload.configshare different object trees.load.setModuleDefaultsupdatesload.configbut notthis.#configdirectly. The branch at line 199 clonesmoduleConfigintothis.#configonly when the path doesn't exist yet; on subsequent calls (line 202),extendDeep(existing, moduleConfig)mutates the sub-object already on the config instance. This seems intentional but is subtle — worth a brief inline comment to clarify the ownership semantics.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 192 - 207, The setModuleDefaults method currently reads existing via Util.getPath(this.#config, path) after calling load.setModuleDefaults(moduleName, defaultProperties), then mutates that existing object in-place with Util.extendDeep(existing, moduleConfig); add a brief inline comment in setModuleDefaults clarifying ownership and mutation semantics (that load.setModuleDefaults updates load.config and that this.#config only gets a cloned moduleConfig when the path is absent, otherwise existing is mutated in-place), and ensure references to methods/fields in the comment include setModuleDefaults, Util.getPath, load.setModuleDefaults, Util.extendDeep, attachProtoDeep, this.#config and load.config to make the intent explicit for future readers.test/22-binary.js (1)
3-3:Bufferimport is unnecessary — it's a Node.js global.
Bufferis available globally in Node.js without an explicit import. This import is harmless but redundant.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/22-binary.js` at line 3, Remove the redundant import of the Node.js global Buffer (the line "import { Buffer } from 'buffer';") in the test/22-binary.js file; locate the import statement referencing Buffer and delete it so the code uses the built-in global Buffer without an explicit import.test/2-config-test.js (1)
62-67: Env-var setup missing in "Immutability"beforeEach; relies on leakage from sibling describe.The
beforeEachat line 65 only loads config but doesn't setNODE_CONFIG_DIR,NODE_ENV, etc. This works because the "Library initialization" describe'sbeforeEach(line 18) sets these env vars onprocess.envand they persist globally. This is fragile — if test ordering changes or tests run in isolation, these tests will fail. Pre-existing concern, but worth noting during migration.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/2-config-test.js` around lines 62 - 67, The Immutability describe's beforeEach currently only calls requireUncached('./lib/config.mjs') and relies on env vars set by a different describe; update this beforeEach to explicitly set the required environment variables (e.g., NODE_CONFIG_DIR, NODE_ENV and any others used by lib/config.mjs) before calling requireUncached so the test doesn't depend on global leakage; you can either duplicate the env setup used in the "Library initialization" beforeEach or call the shared helper that initializes test env, then load requireUncached('./lib/config.mjs') to ensure deterministic, isolated test setup.test/0-util.js (1)
1459-1459: EagerloadFileConfigscall at describe-registration time.Line 1459 executes
util.loadFileConfigs(...)synchronously at describe scope rather than inside abeforeEachorbeforehook. This means it runs during test tree registration, before any setup hooks. It works here becauseloadFileConfigsis synchronous and doesn't depend on dynamic env state, but it's inconsistent with the hook-based pattern used elsewhere in this file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` at line 1459, The test eagerly calls util.loadFileConfigs(...) at describe-registration time; move that synchronous call into a setup hook (e.g., before or beforeEach) so it runs during test setup rather than at import time. Change the top-level var config declaration to a scoped let (e.g., let config) and inside a before/beforeEach assign config = util.loadFileConfigs({configDir: Path.join(import.meta.dirname, 'config')}).config so tests keep the same value but follow the hook-based pattern used elsewhere.test/1-protected-test.js (1)
27-45:process.argv.pushaccumulates entries acrossbeforeEachcalls without cleanup.Line 39 appends to
process.argvon everybeforeEachinvocation but never removes the entry. Across multiple test iterations this growsprocess.argvunboundedly. Consider saving and restoringprocess.argvin anafterEach, or switching tobefore/aftersince the env setup is identical for all tests in this block.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/1-protected-test.js` around lines 27 - 45, The test's beforeEach handler mutates process.argv with process.argv.push and never restores it, causing accumulation across tests; update the setup to save the original argv (e.g., const originalArgv = process.argv.slice()) before modifying it and restore it in an afterEach (process.argv = originalArgv), or move the entire setup into a before/after pair so you only set process.argv once; change references in this file's beforeEach/afterEach and ensure requireUncached('./lib/config.mjs') still runs with the intended argv when needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/config.mjs`:
- Around line 680-690: The cmdLineConfig variable (from
load.getCmdLineArg('NODE_CONFIG')) must be initialized to {} before attempting
JSON.parse and reset to {} inside the catch so a malformed JSON string isn't
later pushed into additional and iterated as a string; update the code around
cmdLineConfig (same pattern used for envConfig) so that cmdLineConfig = {} is
set before the try and in the catch, then keep the additional.push({ name:
"--NODE_CONFIG argument", config: cmdLineConfig }) call as-is to ensure a safe
empty object is added on parse failure.
In `@parser.js`:
- Around line 1-7: Add "type": "module" to package.json so Node treats .js files
as ESM; this fixes runtime SyntaxError for top-level import/export used in
parser.js and lib/util.js (the files that use import/export and createRequire
usage). Open package.json and add the "type": "module" field at the top-level
JSON object, commit the change, and re-run the app to verify parser.js and
lib/util.js import syntax is accepted.
In `@test/type-files/parser.ts`:
- Line 1: The import in test/type-files/parser.ts points to a non-existent
module ('../../types/parser'); update the import to reference the actual
implementation at the repo root (use '../../parser' to load parser.js) or, if
you intended a type declaration, add a corresponding types/parser.d.ts and keep
the import; ensure the symbol name (Parser) matches the exported default from
parser.js or adjust to a named import accordingly.
---
Outside diff comments:
In `@package.json`:
- Around line 75-81: The c8 coverage "include" array currently only matches .js
files and therefore misses lib/config.mjs; update the c8 config's "include"
array to also match .mjs files (e.g., add "*.mjs" and "lib/**/*.mjs" or replace
with patterns that include both .js and .mjs) so lib/config.mjs is captured by
coverage; refer to the c8 "include" array and the lib/config.mjs entry point
when making the change.
- Around line 1-6: The package.json lacks the "type": "module" field while files
parser.js, lib/defer.js, and lib/util.js use ESM imports/exports (only
lib/config.mjs currently uses .mjs); add "type": "module" to package.json to
make .js files be treated as ESM, or alternatively rename the ESM .js files
(parser.js, lib/defer.js, lib/util.js) to .mjs so Node treats them as modules,
and keep lib/config.mjs unchanged.
In `@test/0-util.js`:
- Around line 1637-1645: The test "loads the values in left-right order"
declares a done callback but never calls it, causing the test to hang; fix by
making the test synchronous: remove the done parameter from the it(...) callback
(so it becomes function() { ... }) or alternatively call done() after the
synchronous assertion; locate the test case that calls util.loadFileConfigs and
assert.strictEqual(config.db.name, ...) and either remove the unused done
parameter or add a final done() invocation.
---
Duplicate comments:
In `@lib/config.js`:
- Around line 1-4: The current module uses require('./config.mjs').default which
throws ERR_REQUIRE_ESM on Node < 22.12; replace the synchronous require with a
dynamic import and export the loaded value as a Promise to avoid require-ing an
.mjs file. Specifically, change the module.exports assignment so it uses
import('./config.mjs').then(m => m.default) (i.e., set module.exports =
import('./config.mjs').then(m => m.default)) so consumers load the config
asynchronously instead of calling require('./config.mjs').default.
In `@lib/util.js`:
- Around line 1451-1467: The _loadParser function currently falls through to
return undefined on error (catch block) which can overwrite
DEFAULT_OPTIONS.parser; update the catch in _loadParser to log the error
(include e) and explicitly return defaultParser so failures don’t propagate
undefined, referencing the function name _loadParser, the variable defaultParser
and the catch block where require(Path.resolve(dir, name)) is attempted.
In `@package.json`:
- Around line 48-49: The package.json currently lists a dependency entry
"config": "^4.3.0" which causes npm to install the published package instead of
using the repo's local module; remove that dependency entry and instead expose
your local config module via package.json "exports" so local imports resolve to
the workspace source (e.g., add an "exports" mapping that points the package
import specifier for "./config" or the package name to the local file like
./src/config/index.js or the appropriate entry), ensure any internal imports use
the package name or exact export key you define, and update package.json's
"main" or "types" if needed so tools resolve the local module correctly.
In `@test/9-raw-configs.js`:
- Line 13: The closing brace/parenthesis for the before callback is incorrectly
appended to the await line; in the test file's before callback that calls CONFIG
= await requireUncached('./lib/config.mjs') you should move the closing sequence
"});" onto its own line (i.e., place the closing brace and closing parenthesis
on a separate line after the await statement) so the before callback's structure
is properly formatted and the await line ends with just the statement.
---
Nitpick comments:
In `@lib/config.mjs`:
- Around line 192-207: The setModuleDefaults method currently reads existing via
Util.getPath(this.#config, path) after calling
load.setModuleDefaults(moduleName, defaultProperties), then mutates that
existing object in-place with Util.extendDeep(existing, moduleConfig); add a
brief inline comment in setModuleDefaults clarifying ownership and mutation
semantics (that load.setModuleDefaults updates load.config and that this.#config
only gets a cloned moduleConfig when the path is absent, otherwise existing is
mutated in-place), and ensure references to methods/fields in the comment
include setModuleDefaults, Util.getPath, load.setModuleDefaults,
Util.extendDeep, attachProtoDeep, this.#config and load.config to make the
intent explicit for future readers.
In `@parser.js`:
- Around line 90-115: In Parser.tsParser remove the unnecessary optional
chaining on require when checking extensions: replace the
`require?.extensions['.ts'] === undefined` check with a direct access
`require.extensions['.ts'] === undefined` (or a clearer explicit check like
`typeof require.extensions === 'undefined'`) so the code uses the known
non-nullable `require` provided by createRequire; update the conditional in
Parser.tsParser accordingly.
In `@test/_utils/requireUncached.mjs`:
- Around line 8-11: The requireUncached function currently builds a filesystem
path via Path.resolve and appends a query string to it before dynamic import,
which breaks on Windows; change it to convert the resolved path to a file:// URL
using pathToFileURL (from the 'url' module) and then append the cache-busting
query parameter to that URL string before calling import. Update requireUncached
(and any uses of Path.resolve in this function) to use
pathToFileURL(resolvedPath).href + `?c=${count++}` so imports are valid
cross-platform.
In `@test/0-util.js`:
- Line 1459: The test eagerly calls util.loadFileConfigs(...) at
describe-registration time; move that synchronous call into a setup hook (e.g.,
before or beforeEach) so it runs during test setup rather than at import time.
Change the top-level var config declaration to a scoped let (e.g., let config)
and inside a before/beforeEach assign config = util.loadFileConfigs({configDir:
Path.join(import.meta.dirname, 'config')}).config so tests keep the same value
but follow the hook-based pattern used elsewhere.
In `@test/1-protected-test.js`:
- Around line 27-45: The test's beforeEach handler mutates process.argv with
process.argv.push and never restores it, causing accumulation across tests;
update the setup to save the original argv (e.g., const originalArgv =
process.argv.slice()) before modifying it and restore it in an afterEach
(process.argv = originalArgv), or move the entire setup into a before/after pair
so you only set process.argv once; change references in this file's
beforeEach/afterEach and ensure requireUncached('./lib/config.mjs') still runs
with the intended argv when needed.
In `@test/2-config-test.js`:
- Around line 62-67: The Immutability describe's beforeEach currently only calls
requireUncached('./lib/config.mjs') and relies on env vars set by a different
describe; update this beforeEach to explicitly set the required environment
variables (e.g., NODE_CONFIG_DIR, NODE_ENV and any others used by
lib/config.mjs) before calling requireUncached so the test doesn't depend on
global leakage; you can either duplicate the env setup used in the "Library
initialization" beforeEach or call the shared helper that initializes test env,
then load requireUncached('./lib/config.mjs') to ensure deterministic, isolated
test setup.
In `@test/22-binary.js`:
- Line 3: Remove the redundant import of the Node.js global Buffer (the line
"import { Buffer } from 'buffer';") in the test/22-binary.js file; locate the
import statement referencing Buffer and delete it so the code uses the built-in
global Buffer without an explicit import.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
test/0-util.js (1)
1637-1645:⚠️ Potential issue | 🟡 MinorPre-existing:
donecallback declared but never called — will cause a timeout undernode:test.Line 1637 declares a
doneparameter in the test callback, but the test body (which now has a changed line at 1639) never calls it. Undernode:test, this will cause the test to hang until it times out. Since this PR touches this test (updating the path), it's worth cleaning up:- it('loads the values in left-right order', function(done) { + it('loads the values in left-right order', function() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1637 - 1645, The test callback for the "loads the values in left-right order" it-block declares a done parameter but never calls it, causing node:test to hang; remove the unused done parameter (or alternatively call done() after the assertions) so the test is synchronous and completes; locate the it(...) with the description "loads the values in left-right order" in test/0-util.js and update its function signature to not accept done (or add a done() invocation after assert.strictEqual) and ensure the assertion remains unchanged.parser.js (1)
90-115:⚠️ Potential issue | 🟠 MajorRemove the unsupported
lazy: trueoption fromts-noderegister call.The project uses ts-node
^10.9.2. In ts-node v10 (and v9/v11), theregister()method does not support alazyoption—modern ts-node already compiles on-demand by nature. The valid options here aretranspileOnly,ignore, andcompilerOptions, butlazy: true(line 95) is not recognized and should be removed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 90 - 115, In Parser.tsParser, remove the unsupported "lazy: true" option from the TS.register(...) call so ts-node is invoked with only supported options (e.g., transpileOnly, ignore, compilerOptions); update the TS.register invocation inside the TS === null branch (where TS is loaded via moduleRequire(TS_DEP)) to omit the lazy property and keep the remaining options intact.test/16-config/parser/custom-3.js (1)
1-11:⚠️ Potential issue | 🟡 MinorFix Parser isolation loss — static import creates singleton instead of fresh instance
custom-3.jsnow uses a direct static import for the Parser module, which returns the same ES module singleton across all tests. This breaks the original design that usedrequireUncachedto get a fresh instance.While the test likely still passes because
custom-3.jscallssetFilesOrder(['yaml', 'yml', 'json5'])which replaces the entire order array (preventing the 'custom' parser registered bycustom-2.jsfrom interfering), this is fragile. The 'custom' parser definition persists in the singleton and could cause unexpected behavior if:
- Test execution order changes
- Tests run in parallel
- Future refactoring relies on isolation
Consider restoring isolation by using a dynamic
import()with a cache-busting query parameter (as the test utilityrequireUncacheddoes), or documenting the expectation that test isolation is maintained viasetFilesOrderreplacement rather than fresh instances.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/16-config/parser/custom-3.js` around lines 1 - 11, The test uses a static import of Parser which returns the shared ES module singleton and breaks test isolation; change custom-3.js to dynamically import a fresh Parser instance (e.g., use import(...) with a cache-busting query param or the existing requireUncached utility) instead of "import Parser from '../../../parser.js'", then call Parser.setParser('json5', ...), setFilesOrder(['yaml','yml','json5']) and export that fresh Parser so each test gets an isolated Parser instance rather than the shared singleton.
🧹 Nitpick comments (6)
test/type-files/util.ts (1)
44-50: LGTM — one optional suggestion onresolvedreturn type.The overall pattern (static method access, call-signature check, assignability check, and negative
@ts-expect-error) is well-structured and consistent with the rest of the file.The only minor nit:
resolved(line 46) has no type annotation, so.resolve()'s return type is exercised but not explicitly validated. If the return type is part of the public API contract you want to guard, consider:♻️ Optional: annotate the resolved return type
-const resolved = rawConfig.resolve(); +const resolved: <ExpectedReturnType> = rawConfig.resolve();This is strictly optional — the existing pattern matches how other results (e.g.,
loaded,envValue) are handled throughout the file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/type-files/util.ts` around lines 44 - 50, The variable resolved is currently untyped so .resolve()'s return type isn't explicitly validated; update the declaration of resolved to include the expected return type from RawConfig.resolve (e.g., the library's ResolvedConfig/appropriate interface) so the compiler enforces that .resolve() returns the intended type—locate RawConfig.raw, the rawConfig variable, and the rawConfig.resolve() call and add the explicit type annotation to resolved to match resolve()'s declared return type.test/_utils/requireUncached.mjs (1)
1-12: LGTM — clean ESM cache-busting utility.The approach of appending a query parameter to force unique import URLs is the standard ESM cache-busting pattern. One minor note: on Windows,
Path.resolveproduces backslash paths (e.g.,C:\foo\bar), and appending?c=Nto a plain filesystem path (rather than afile://URL) may not behave as expected. If cross-platform test execution matters, consider usingpathToFileURLfromnode:url:Optional: more robust cross-platform version
import Path from 'path'; +import { pathToFileURL } from 'node:url'; let count = 0; export async function requireUncached(moduleName) { let path = Path.resolve(process.cwd(), moduleName); - let module = await import(`${path}?c=${count++}`); + let url = pathToFileURL(path); + url.searchParams.set('c', String(count++)); + let module = await import(url.href); return module.default; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/_utils/requireUncached.mjs` around lines 1 - 12, The current requireUncached uses Path.resolve to build a filesystem path and appends a query param for ESM cache-busting, which can fail on Windows; change it to convert the resolved path to a file:// URL using pathToFileURL from node:url and append the ?c=N there before calling dynamic import in requireUncached so the import target is a proper URL across platforms (update references to Path.resolve and the import(`${path}?c=${count++}`) call inside requireUncached).lib/config.mjs (1)
1-15: Minor inconsistency:'path'vs'node:path'import.Line 12 imports
Path from 'path', whilelib/util.jsline 8 usesimport Path from 'node:path'. Thenode:prefix is the recommended ESM convention for Node.js built-ins. Consider aligning for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 1 - 15, The import of Path in this module is inconsistent with lib/util.js; update the module-level import statement that declares Path (see the import currently: "import Path from 'path'") to use the Node ESM built-in prefix so it matches util.js (i.e., import Path from 'node:path'), leaving other symbols like LOAD_SYMBOL and DEFAULT_CLONE_DEPTH untouched.parser.js (1)
6-7: VerifycreateRequirebase paths are correct.
createRequireresolves modules relative to the dirname of the path you pass. For line 7,createRequire(process.cwd())where cwd is e.g./path/to/projectmeans resolution starts from/path/to/(the dirname), not from the project root. This doesn't matter here becauserequireis only called with absolute paths (injsParser,tsParser,coffeeParser), but it's worth noting the asymmetry with line 6.Line 6's
createRequire(Path.join(process.cwd(), 'node_modules'))correctly roots resolution atprocess.cwd()(since dirname of.../node_modulesis the project root), making it find packages in<cwd>/node_modules/.A more explicit approach for line 7:
-const require = createRequire(process.cwd()); +const require = createRequire(Path.join(process.cwd(), 'noop.js'));Or simply reuse
moduleRequirefor config file loading since absolute paths resolve the same regardless of base.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 6 - 7, The two createRequire calls are asymmetric and may mis-root resolution; please stop creating require with process.cwd() and instead reuse moduleRequire (the const moduleRequire created with Path.join(process.cwd(), 'node_modules')) when loading config or other modules (e.g., replace uses of the local require with moduleRequire in jsParser/tsParser/coffeeParser config loads), or if you want a dedicated require, create it from a path whose dirname is the project root (e.g., Path.join(process.cwd(), 'package.json')); update references to use the chosen require to ensure consistent resolution.test/8-config-extending.js (2)
2-2: Nit: unconventional import aliasFileSystem.The Node.js ecosystem overwhelmingly uses
fsfor this module.FileSystemdiverges from convention and may confuse contributors.Suggested fix
-import FileSystem from 'node:fs'; +import fs from 'node:fs';Then update line 30:
- let base_config = JSON.parse(FileSystem.readFileSync(filename, 'utf-8')); + let base_config = JSON.parse(fs.readFileSync(filename, 'utf-8'));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` at line 2, Replace the unconventional import alias FileSystem with the conventional fs: change the import statement "import FileSystem from 'node:fs'" to import fs from 'node:fs' (or "import * as fs from 'node:fs'") and update all usages of the FileSystem identifier (e.g., the reference on the line currently using FileSystem around line 30) to use fs instead so the code follows Node.js conventions.
28-28: RedundantNODE_CONFIG_DIRassignment.Line 13 in the
beforehook already setsNODE_CONFIG_DIRto the same value. This re-assignment inside theitblock is unnecessary.Suggested fix
- process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config'; let filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` at line 28, The test contains a redundant assignment to process.env.NODE_CONFIG_DIR inside the it block duplicating the value already set in the before hook; remove the duplicate assignment in the it block (the process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config' statement) so the before hook's configuration is the single source of setup for NODE_CONFIG_DIR.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@parser.js`:
- Around line 377-378: The file exports the Parser class using ES module syntax
(export default Parser), so update project configuration: add "type": "module"
to package.json to ensure Node treats .js files as ES modules; locate
package.json and insert or update the top-level "type" field to "module" (or
alternatively convert the export to CommonJS require/module.exports if you
prefer not to set module type) so that Parser's export works at runtime.
In `@test/2-config-test.js`:
- Around line 65-67: The tests in sibling describe blocks call beforeEach that
only does config = await requireUncached('./lib/config.mjs') without resetting
environment, so NODE_CONFIG_DIR, NODE_ENV, NODE_CONFIG, etc. can leak and break
isolated runs; fix by moving the env setup into a shared top-level beforeEach
(or a top-level before) that sets NODE_CONFIG_DIR, NODE_ENV, NODE_CONFIG (and
any other test env vars) and then calls requireUncached('./lib/config.mjs') to
assign config, or alternatively repeat the same env-setting + requireUncached
call at the start of each sibling describe block so every beforeEach that calls
requireUncached runs with a known environment.
---
Outside diff comments:
In `@parser.js`:
- Around line 90-115: In Parser.tsParser, remove the unsupported "lazy: true"
option from the TS.register(...) call so ts-node is invoked with only supported
options (e.g., transpileOnly, ignore, compilerOptions); update the TS.register
invocation inside the TS === null branch (where TS is loaded via
moduleRequire(TS_DEP)) to omit the lazy property and keep the remaining options
intact.
In `@test/0-util.js`:
- Around line 1637-1645: The test callback for the "loads the values in
left-right order" it-block declares a done parameter but never calls it, causing
node:test to hang; remove the unused done parameter (or alternatively call
done() after the assertions) so the test is synchronous and completes; locate
the it(...) with the description "loads the values in left-right order" in
test/0-util.js and update its function signature to not accept done (or add a
done() invocation after assert.strictEqual) and ensure the assertion remains
unchanged.
In `@test/16-config/parser/custom-3.js`:
- Around line 1-11: The test uses a static import of Parser which returns the
shared ES module singleton and breaks test isolation; change custom-3.js to
dynamically import a fresh Parser instance (e.g., use import(...) with a
cache-busting query param or the existing requireUncached utility) instead of
"import Parser from '../../../parser.js'", then call Parser.setParser('json5',
...), setFilesOrder(['yaml','yml','json5']) and export that fresh Parser so each
test gets an isolated Parser instance rather than the shared singleton.
---
Duplicate comments:
In `@lib/config.js`:
- Line 4: The current line module.exports = require('./config.mjs').default
attempts to require an ESM file and triggers ERR_REQUIRE_ESM on Node 20+;
replace this direct require with a CJS-compatible approach: provide a true
CommonJS entry (e.g., create lib/config.cjs) that either (a) uses dynamic
import('./config.mjs') and assigns module.exports after the import resolves, or
(b) contains a CJS implementation/build of the config and export it
synchronously, and update package.json conditional "exports" so "require"
resolves to the .cjs file while ESM import still resolves to ./config.mjs;
remove the direct require('./config.mjs') in lib/config.js so no ESM file is
required from CommonJS.
In `@lib/config.mjs`:
- Around line 680-690: The try/catch around parsing the --NODE_CONFIG
command-line value should reset cmdLineConfig to an empty object on parse
failure to avoid pushing the original string into the config pipeline; in the
catch block for JSON.parse(cmdLineConfig) update cmdLineConfig = {} (and
optionally log the parse error), so that the subsequent additional.push({ name:
"--NODE_CONFIG argument", config: cmdLineConfig }) and downstream
load.scan/addConfig/Util.extendDeep receive an object rather than the malformed
string.
In `@test/x-deferred-configs-ts.js`:
- Line 20: The closing `});` of the `before` callback is incorrectly on the same
line as the await call; open the `before` callback body so that the assignment
line ends after the semicolon and move the `});` to its own line to properly
close the `before` block—look for the CONFIG = await
requireUncached('./lib/config.mjs') statement inside the before callback and
place the `});` on the next line.
---
Nitpick comments:
In `@lib/config.mjs`:
- Around line 1-15: The import of Path in this module is inconsistent with
lib/util.js; update the module-level import statement that declares Path (see
the import currently: "import Path from 'path'") to use the Node ESM built-in
prefix so it matches util.js (i.e., import Path from 'node:path'), leaving other
symbols like LOAD_SYMBOL and DEFAULT_CLONE_DEPTH untouched.
In `@parser.js`:
- Around line 6-7: The two createRequire calls are asymmetric and may mis-root
resolution; please stop creating require with process.cwd() and instead reuse
moduleRequire (the const moduleRequire created with Path.join(process.cwd(),
'node_modules')) when loading config or other modules (e.g., replace uses of the
local require with moduleRequire in jsParser/tsParser/coffeeParser config
loads), or if you want a dedicated require, create it from a path whose dirname
is the project root (e.g., Path.join(process.cwd(), 'package.json')); update
references to use the chosen require to ensure consistent resolution.
In `@test/_utils/requireUncached.mjs`:
- Around line 1-12: The current requireUncached uses Path.resolve to build a
filesystem path and appends a query param for ESM cache-busting, which can fail
on Windows; change it to convert the resolved path to a file:// URL using
pathToFileURL from node:url and append the ?c=N there before calling dynamic
import in requireUncached so the import target is a proper URL across platforms
(update references to Path.resolve and the import(`${path}?c=${count++}`) call
inside requireUncached).
In `@test/8-config-extending.js`:
- Line 2: Replace the unconventional import alias FileSystem with the
conventional fs: change the import statement "import FileSystem from 'node:fs'"
to import fs from 'node:fs' (or "import * as fs from 'node:fs'") and update all
usages of the FileSystem identifier (e.g., the reference on the line currently
using FileSystem around line 30) to use fs instead so the code follows Node.js
conventions.
- Line 28: The test contains a redundant assignment to
process.env.NODE_CONFIG_DIR inside the it block duplicating the value already
set in the before hook; remove the duplicate assignment in the it block (the
process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config' statement) so
the before hook's configuration is the single source of setup for
NODE_CONFIG_DIR.
In `@test/type-files/util.ts`:
- Around line 44-50: The variable resolved is currently untyped so .resolve()'s
return type isn't explicitly validated; update the declaration of resolved to
include the expected return type from RawConfig.resolve (e.g., the library's
ResolvedConfig/appropriate interface) so the compiler enforces that .resolve()
returns the intended type—locate RawConfig.raw, the rawConfig variable, and the
rawConfig.resolve() call and add the explicit type annotation to resolved to
match resolve()'s declared return type.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
test/_utils/requireUncached.mjs (1)
8-11: Implementation is correct; document the default-only return behavior.The cache-busting approach using query parameters is a valid pattern for ESM. Note that
requireUncachedonly returnsmodule.default, which works well forlib/config.mjsbut would silently returnundefinedfor modules without a default export.Consider adding a brief JSDoc comment to clarify this expectation:
📝 Suggested documentation
+/** + * Dynamically import a module without caching. + * `@param` {string} moduleName - Path relative to process.cwd() + * `@returns` {Promise<any>} The module's default export + */ export async function requireUncached(moduleName) { let path = Path.resolve(process.cwd(), moduleName); let module = await import(`${path}?c=${count++}`); return module.default; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/_utils/requireUncached.mjs` around lines 8 - 11, Add a short JSDoc above the requireUncached function documenting that it performs an ESM import with a cache-busting query and returns only the module's default export (module.default), which will be undefined for modules that do not export a default; update the comment to mention the expected use (e.g., config modules with default export) so callers know this function intentionally returns default-only.test/6-strict-mode.js (1)
73-97: Consider resettingerrorat the start ofbeforeEach.The
errorvariable is declared at line 74 outsidebeforeEach, meaning it retains its value across test runs if the same test suite is re-executed. While this likely works correctly in practice, explicitly resettingerror = undefinedat the start ofbeforeEachwould make the intent clearer and prevent potential issues.return () => { beforeEach(async function() { + error = undefined; // Change the configuration directory for testing process.env.NODE_CONFIG_DIR = import.meta.dirname + '/6-config';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/6-strict-mode.js` around lines 73 - 97, In _expectException add an explicit reset of the outer-scope variable error at the start of the beforeEach block so previous test runs don't leak state; inside beforeEach (the function that sets up process.env and calls requireUncached('./lib/config.mjs')) set error = undefined before the try/catch so each test run begins with a clean error value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/defer.js`:
- Line 1: The file lib/defer.js currently uses ESM syntax (import/export) but
the project package.json lacks "type":"module", so Node will treat .js as
CommonJS and this will fail; fix by either (A) renaming lib/defer.js to
lib/defer.mjs and keeping the ESM import (isAsyncFunction import at top) or (B)
adding "type":"module" to package.json so .js files are treated as ESM—ensure
any other files using ESM (noted at the second occurrence around line 69) are
updated consistently to match the chosen approach.
In `@parser.js`:
- Around line 6-7: The createRequire calls are anchored to directories which is
incorrect; change them to use a specific file path (e.g., package.json) so
module resolution has a proper filename context: update the moduleRequire
assignment that uses createRequire(Path.join(process.cwd(), 'node_modules')) to
instead join process.cwd() with 'package.json', and likewise update the require
assignment that uses createRequire(process.cwd()) to use a file path
(process.cwd() + '/package.json' or Path.join(process.cwd(), 'package.json')) so
createRequire, moduleRequire and require are invoked with a filename rather than
a directory.
---
Duplicate comments:
In `@lib/config.js`:
- Around line 1-4: The current synchronous assignment module.exports =
require('./config.mjs').default will throw ERR_REQUIRE_ESM on Node 20.x; replace
it with an async loader: remove the require(...) usage and export an async
function (e.g., module.exports = async function loadConfig() { const mod = await
import('./config.mjs'); return mod.default; }) and then update any callers to
await loadConfig(); reference the existing module.exports and
require('./config.mjs').default sites and change them to the new async
loadConfig function so ESM is loaded via dynamic import instead of synchronous
require.
In `@lib/util.js`:
- Around line 1450-1466: The _loadParser function currently logs errors but
returns undefined on failure (e.g., when NODE_CONFIG_PARSER points to an invalid
path), which can overwrite DEFAULT_OPTIONS.parser and break callers like
options.parser.getFilesOrder(); modify _loadParser so that on any catch it
returns defaultParser (not undefined) after logging the error, ensuring the
function (and callers relying on defaultParser) remain safe; locate the
_loadParser implementation and adjust the catch block to return defaultParser
(referencing defaultParser, createRequire, and Path.resolve in your changes).
In `@test/2-config-test.js`:
- Around line 65-67: The tests call requireUncached('./lib/config.mjs') inside
sibling beforeEach blocks but do not reset NODE_CONFIG_DIR, NODE_ENV,
NODE_CONFIG, etc., causing state leakage when running isolated tests; update the
test setup to ensure environment is deterministically reset before each test by
moving the env-var initialization (set NODE_CONFIG_DIR, NODE_ENV, NODE_CONFIG,
and any other config-related vars) into a shared top-level before or beforeEach
that runs for all describe blocks, or alternatively duplicate the same env
setup/reset in every sibling beforeEach that calls requireUncached, so
requireUncached always sees a clean, explicit environment.
In `@test/type-files/parser.ts`:
- Line 1: The import at the top of test/type-files/parser.ts ("import Parser
from '../../types/parser'") points to a non-existent types/parser module; either
update that import to reference the actual root parser implementation module
used at runtime (the parser.js module) so the resolved module exists, or
alternatively add a proper type declaration file named to provide the Parser
types (e.g., a types/parser.d.ts) and export the Parser type so the existing
import works; adjust the import or add the declaration accordingly in the
Parser-related symbols to restore TypeScript compilation.
In `@test/util.js`:
- Line 43: The test calls util.loadFileConfigs with a bare string; change it to
pass a LoadOptions object so the configDir is set correctly by calling
util.loadFileConfigs({configDir: path.join(import.meta.dirname, '5-config')});
update the other test occurrence similarly (the second call around line 50) so
both calls pass an object rather than a raw string, ensuring loadFileConfigs
receives the expected LoadOptions shape.
In `@test/x-deferred-configs-ts.js`:
- Line 20: Move the stray closing tokens off the same line as the await call: in
the before callback where you call CONFIG = await
requireUncached('./lib/config.mjs'); ensure the trailing `});` that closes the
before callback is placed on its own line (i.e., separate the await statement
from the callback closure) so the `await` and the callback terminator are not on
the same line.
---
Nitpick comments:
In `@test/_utils/requireUncached.mjs`:
- Around line 8-11: Add a short JSDoc above the requireUncached function
documenting that it performs an ESM import with a cache-busting query and
returns only the module's default export (module.default), which will be
undefined for modules that do not export a default; update the comment to
mention the expected use (e.g., config modules with default export) so callers
know this function intentionally returns default-only.
In `@test/6-strict-mode.js`:
- Around line 73-97: In _expectException add an explicit reset of the
outer-scope variable error at the start of the beforeEach block so previous test
runs don't leak state; inside beforeEach (the function that sets up process.env
and calls requireUncached('./lib/config.mjs')) set error = undefined before the
try/catch so each test run begins with a clean error value.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
benchmarks/index.js (1)
12-12:⚠️ Potential issue | 🟠 MajorUpdate
benchmarks/index.jsto use dynamicimport()instead ofrequire()forlib/util.js.
lib/util.jsuses ESM syntax (import/export), butpackage.jsondoes not have"type": "module"configured. Therequire()calls at lines 12 and 56 will fail with a module loading error since CommonJS cannot load ESM syntax. Use dynamicimport()to load the ESM module, or convertbenchmarks/index.jsto ESM as well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/index.js` at line 12, Replace the CommonJS require() usage for the ESM module by loading "../lib/util.js" via dynamic import() and await its default/named exports; specifically change the require of "{ Load, Util }" (and the second require at the later occurrence) to use an awaited import() so you get the ESM exports, and if the file is not already using top-level await wrap the import in an async IIFE (or convert the file to ESM) so you can destructure the imported { Load, Util } and continue using those symbols unchanged.test/1-protected-test.js (1)
27-45:⚠️ Potential issue | 🟠 Major
process.argv.pushinbeforeEachwithout cleanup causes unbounded argv growth.The old code captured
argvOrgand restored it; removing that save/restore while keeping thepush(line 39) means each of the 11it()calls in this file triggers another append toprocess.argv. By the last test,process.argvcontains 11 duplicate--NODE_CONFIGentries, polluting the global process state for every subsequent test file.Additionally,
beforeEachreloadslib/config.mjs11 times for tests that are purely reading from config. Switching tobeforewould suffice and eliminate the issue entirely.🐛 Proposed fix — switch to `before` and reload once
-import { describe, it, beforeEach } from 'node:test'; +import { describe, it, before, after } from 'node:test'; ... - beforeEach(async function () { + before(async function () { process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config'; process.env.NODE_ENV = 'test'; process.env.NODE_APP_INSTANCE = '3'; process.env.NODE_CONFIG = '{"EnvOverride":{"parm3":"overridden from $NODE_CONFIG","parm4":100}}'; process.argv.push('--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}'); process.env['CUSTOM_JSON_ENVIRONMENT_VAR'] = 'CUSTOM VALUE FROM JSON ENV MAPPING'; config = await requireUncached('./lib/config.mjs'); }); + + after(function () { + const idx = process.argv.findIndex(a => a.startsWith('--NODE_CONFIG=')); + if (idx !== -1) process.argv.splice(idx, 1); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/1-protected-test.js` around lines 27 - 45, The test setup currently mutates process.argv inside beforeEach by calling process.argv.push(...) and reloads lib/config.mjs repeatedly via requireUncached in beforeEach, causing argv to grow across tests; change the hook to before so the NODE_CONFIG arg is appended and lib/config.mjs is loaded only once (or if you must keep beforeEach, capture a copy of process.argv (argvOrg) before pushing and restore it after each test) — update the beforeEach -> before for the setup block that sets process.env keys and calls process.argv.push and requireUncached('./lib/config.mjs') (or implement argv save/restore around process.argv.push) to prevent unbounded global state mutation.package.json (1)
75-82:⚠️ Potential issue | 🟠 Major
lib/config.mjsis excluded from coverage reporting.The
c8includeglob only matches*.jsandlib/**/*.js. The new primary modulelib/config.mjswill never appear in coverage output — any untested paths there are invisible.🔧 Proposed fix
"c8": { "include": [ "*.js", - "lib/**/*.js" + "lib/**/*.js", + "lib/**/*.mjs" ],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 75 - 82, The c8 config in package.json currently only includes "*.js" and "lib/**/*.js", which excludes lib/config.mjs; update the "c8" -> "include" globs to also match .mjs files (for example add "/*.mjs", "lib/**/*.mjs" or a generic "lib/**/*.{js,mjs}") so lib/config.mjs is picked up by coverage reporting; modify the package.json c8 include array accordingly to reference lib/config.mjs coverage inclusion.
🧹 Nitpick comments (8)
test/_utils/requireUncached.mjs (1)
8-11:module.defaultassumption limits reusability.
requireUncachedalways returnsmodule.default, silently returningundefinedif the imported module has no default export. This works forlib/config.mjsbut could cause confusing failures if reused elsewhere. Consider returning the full module namespace and letting callers destructure, or at minimum adding a guard.💡 Optional: return full module or guard
export async function requireUncached(moduleName) { let path = Path.resolve(process.cwd(), moduleName); let module = await import(`${path}?c=${count++}`); - return module.default; + return module.default ?? module; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/_utils/requireUncached.mjs` around lines 8 - 11, The helper requireUncached currently returns only module.default which can be undefined for modules without a default export; update the requireUncached function to return the full imported module namespace (the imported module object) so callers can destructure what they need, or alternatively add a guard inside requireUncached that throws or returns the module namespace when module.default is undefined; locate the requireUncached function and change its return behavior accordingly so it no longer silently returns undefined for non-default exports.test/8-config-extending.js (1)
28-30: RedundantNODE_CONFIG_DIRreassignment inside the test body.Line 13 in the
beforehook already sets this to the same value. The reassignment on line 28 is a no-op.♻️ Proposed fix
- process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config'; - let filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json'); + let filename = path.join(import.meta.dirname, '8-config', 'base-config.json');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` around lines 28 - 30, Remove the redundant reassignment of process.env.NODE_CONFIG_DIR inside the test body since the before hook already sets it; locate the code that sets process.env.NODE_CONFIG_DIR (the before hook and the line in the test that does process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config') and delete the latter assignment so only the before hook establishes NODE_CONFIG_DIR, leaving the FileSystem.readFileSync call and the rest of the test unchanged.test/22-binary.js (1)
3-3:Bufferis a Node.js global — the import is redundant.
Bufferis available as a global in all Node.js environments and does not need to be imported from'buffer'.♻️ Proposed fix
-import { Buffer } from 'buffer';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/22-binary.js` at line 3, Remove the redundant import of Buffer from 'buffer' — delete the line "import { Buffer } from 'buffer';" (the code uses the global Buffer, so no import is necessary) and run tests to confirm no other module relies on that named import; if any code explicitly expects a locally imported Buffer symbol, replace references to use the global Buffer instead.test/6-strict-mode.js (1)
73-97:erroris not reset before eachbeforeEachrun — fragile if behavior differs acrossitblocks.
erroris declared once per_expectExceptionclosure (line 74) and set only in thecatch. If a future refactor adds a test wherebeforeEachdoes not throw,errorwill still hold the stale value from a prior run. Consider resettingerror = undefinedat the top ofbeforeEachfor resilience.💡 Suggested defensive reset
beforeEach(async function() { + error = undefined; // Change the configuration directory for testing process.env.NODE_CONFIG_DIR = import.meta.dirname + '/6-config';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/6-strict-mode.js` around lines 73 - 97, In function _expectException, reset the closure-scoped variable error at the start of the beforeEach block to avoid carrying a stale error between test runs; inside the beforeEach (before the try/catch) set error = undefined so each test begins with a clean state when requireUncached('./lib/config.mjs') runs and any thrown exception is freshly captured.test/2-config-test.js (1)
41-44: Assigning numbers toprocess.envsilently coerces to strings.
process.env.CUSTOM_NUMBER_INTEGER_ENVIRONMENT_VAR = 1001stores"1001"(a string). This works but is misleading — consider using string literals for clarity and to match what consumers actually receive.💡 Suggested clarification
- process.env.CUSTOM_NUMBER_INTEGER_ENVIRONMENT_VAR = 1001; - process.env.CUSTOM_NUMBER_FLOAT_ENVIRONMENT_VAR = 3.14; + process.env.CUSTOM_NUMBER_INTEGER_ENVIRONMENT_VAR = '1001'; + process.env.CUSTOM_NUMBER_FLOAT_ENVIRONMENT_VAR = '3.14';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/2-config-test.js` around lines 41 - 44, The test assigns numeric and other non-string values directly to process.env (e.g., process.env.CUSTOM_NUMBER_INTEGER_ENVIRONMENT_VAR, process.env.CUSTOM_NUMBER_FLOAT_ENVIRONMENT_VAR, process.env.CUSTOM_NUMBER_EMPTY_ENVIRONMENT_VAR, process.env.CUSTOM_NUMBER_STRING_ENVIRONMENT_VAR) which are implicitly coerced to strings; change these assignments to explicit string literals (e.g., "1001", "3.14", "", "String") so the test reflects the actual string values consumers receive and avoids misleading numeric assignments.lib/config.mjs (2)
14-15:DEFAULT_CLONE_DEPTHis duplicated fromlib/util.js.This constant is also defined in
lib/util.js(line 77) with the same value. If it ever changes in one place but not the other, behavior would silently diverge. Consider importing it fromutil.jsor exporting it from a shared location.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 14 - 15, DEFAULT_CLONE_DEPTH is duplicated between lib/config.mjs and lib/util.js; remove the duplicate by centralizing the constant (either export DEFAULT_CLONE_DEPTH from lib/util.js or create a shared constants module) and import it into lib/config.mjs instead of redefining it. Update references in lib/config.mjs to use the imported DEFAULT_CLONE_DEPTH (leave LOAD_SYMBOL untouched) and ensure lib/util.js (or the new shared module) exports DEFAULT_CLONE_DEPTH so all callers use the single source of truth.
529-545: Known limitation: config keys namedget,has, orutilshadow prototype methods.The
if (!toObject[fnName])check (line 542) skips attachment when a truthy config property exists with the same name. This silently breaks prototype method access for those sub-objects. This is a known pre-existing bug per project learnings, so flagging for awareness only.Based on learnings: "In node-config lib/config.js, config keys named 'get', 'has', or 'util' can silently shadow prototype methods in attachProtoDeep because the check
if (!toObject[fnName])skips attachment when a truthy property exists."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 529 - 545, attachProtoDeep currently skips adding hidden prototype methods when a config sub-object has a truthy key named 'get'/'has'/'util'/LOAD_SYMBOL, which lets those keys silently shadow the methods; update the loop in attachProtoDeep so it detects owned properties with Object.prototype.hasOwnProperty.call(toObject, fnName) and then always call Util.makeHidden(toObject, fnName, this.#config[fnName]) when the existing property is not a function (i.e., change the condition to attach if the prop is missing or its typeof !== 'function'), referencing attachProtoDeep, Util.makeHidden, and LOAD_SYMBOL so named keys no longer block provisioning the method behaviors.parser.js (1)
90-103:tsParserre-registersts-nodeon every call when.tsextension is not yet set.When
require?.extensions['.ts']isundefined, the parser loads and callsTS.register()— but only guards against repeated registration withif (TS === null). Once registered,require.extensions['.ts']may still beundefineddepending onts-node's lazy mode, causing repeated entry into the guarded block (which is fine due to the null check). The logic is correct but the outer condition (require?.extensions['.ts'] === undefined) and the inner guard (TS === null) overlap in a confusing way.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 90 - 103, The current logic in Parser.tsParser redundantly checks require?.extensions['.ts'] === undefined before guarding registration with TS === null, which is confusing and can lead to repeated conditional evaluation; change the flow to first check the module-level TS variable (if TS === null) and only then load and call TS.register(), and after successful registration ensure the `.ts` loader is present (e.g., set or rely on require.extensions['.ts'] if needed) so repeated calls to Parser.tsParser no longer re-enter the registration path; update references in this function (Parser.tsParser, TS, and TS.register) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/util.js`:
- Line 1455: The call to createRequire in _loadParser uses dir which may be
relative (coming from fromEnvironment / NODE_CONFIG_DIR); normalize dir to an
absolute path (or file URL) before passing to createRequire—e.g., use the
existing _toAbsolutePath helper (or path.resolve) to convert dir to an absolute
path inside _loadParser so createRequire receives a valid absolute path/file
URL; update the code around const require = createRequire(dir) accordingly and
keep behavior consistent with loadFileConfigs.
In `@test/16-custom-parser.js`:
- Around line 45-50: Make the parser paths consistent by either adding or
removing the `.js` extension; update the uses of process.env.NODE_CONFIG_PARSER
in the beforeEach blocks (and the similar occurrence around lines referencing
custom-3) so that `custom-2` and `custom-3` include the `.js` extension to match
`custom-1.js` (or alternatively remove the extension from `custom-1.js` to match
the others) — change the string values assigned to
process.env.NODE_CONFIG_PARSER and any other test assignments so all three
parser names use the same `.js` extension convention.
In `@test/22-binary.js`:
- Around line 10-16: The test sets NODE_CONFIG_DIR, NODE_ENV, and
NODE_APP_INSTANCE in the beforeEach but never cleans them up; add an afterEach
that restores the original values saved before mutation (or deletes the env var
if it was undefined) to avoid leaking into other tests—capture originals at the
start of beforeEach (or in an outer scope) and implement the cleanup in an
afterEach next to beforeEach in the same test file (referencing the existing
beforeEach and requireUncached usage to locate where to save/restore).
---
Outside diff comments:
In `@benchmarks/index.js`:
- Line 12: Replace the CommonJS require() usage for the ESM module by loading
"../lib/util.js" via dynamic import() and await its default/named exports;
specifically change the require of "{ Load, Util }" (and the second require at
the later occurrence) to use an awaited import() so you get the ESM exports, and
if the file is not already using top-level await wrap the import in an async
IIFE (or convert the file to ESM) so you can destructure the imported { Load,
Util } and continue using those symbols unchanged.
In `@package.json`:
- Around line 75-82: The c8 config in package.json currently only includes
"*.js" and "lib/**/*.js", which excludes lib/config.mjs; update the "c8" ->
"include" globs to also match .mjs files (for example add "/*.mjs",
"lib/**/*.mjs" or a generic "lib/**/*.{js,mjs}") so lib/config.mjs is picked up
by coverage reporting; modify the package.json c8 include array accordingly to
reference lib/config.mjs coverage inclusion.
In `@test/1-protected-test.js`:
- Around line 27-45: The test setup currently mutates process.argv inside
beforeEach by calling process.argv.push(...) and reloads lib/config.mjs
repeatedly via requireUncached in beforeEach, causing argv to grow across tests;
change the hook to before so the NODE_CONFIG arg is appended and lib/config.mjs
is loaded only once (or if you must keep beforeEach, capture a copy of
process.argv (argvOrg) before pushing and restore it after each test) — update
the beforeEach -> before for the setup block that sets process.env keys and
calls process.argv.push and requireUncached('./lib/config.mjs') (or implement
argv save/restore around process.argv.push) to prevent unbounded global state
mutation.
---
Duplicate comments:
In `@lib/config.js`:
- Line 4: Replace the direct require of an .mjs file (module.exports =
require('./config.mjs').default) because require('./config.mjs') throws on
Node.js <22.12; instead change lib/config.js to require a CommonJS export first
(require('./config.cjs').default) and, if that file is missing, fall back to
dynamically importing the ESM file via
import(pathToFileURL(require.resolve('./config.mjs')).href) and export its
default; update the module.exports assignment to use the CommonJS require when
available and asynchronously load the .mjs only as a fallback so Node
20.x/22.0–22.11 users aren’t broken (refer to the module.exports assignment in
lib/config.js to locate the change).
In `@lib/util.js`:
- Around line 1450-1466: The catch in _loadParser currently logs errors but
falls through to return undefined, which can overwrite DEFAULT_OPTIONS.parser
via the fromEnvironment spread and later cause a crash in
options.parser.getFilesOrder(); modify _loadParser to return the fallback
defaultParser when require() fails (or rethrow) so that callers like
fromEnvironment retain a valid parser; specifically update the catch in function
_loadParser to return defaultParser (using the existing defaultParser symbol)
instead of returning undefined.
In `@package.json`:
- Line 5: The package.json currently adds a non-standard "module" field but
lacks a proper Node.js "exports" map; add an "exports" field that mirrors the
entry points (e.g., main, module, and subpath exports) so runtime subpath
imports like "config/parser" and "config/lib/defer" resolve correctly alongside
your existing typesVersions. Update package.json to include an exports object
that exposes the package root and the required internal subpaths, mapping them
to their corresponding ESM ("./lib/config.mjs") and CJS ("./lib/config.cjs" or
"./lib/config.js") files as appropriate, and ensure the default export and
conditional exports (import/require/types) are covered.
In `@parser.js`:
- Around line 377-378: The file uses an ESM-style "export default" for the
Parser symbol in parser.js while the project isn't declared as ESM; replace the
ambiguous export with a clear CommonJS export to avoid node's CJS/ESM fallback
(e.g., remove "export default Parser" and export Parser via module.exports) so
consumers and Node resolve consistently; alternatively, if you intend ESM,
ensure package.json has "type": "module" or rename the file to .mjs and update
imports accordingly.
- Around line 6-7: The second createRequire call is passing a directory
(process.cwd()) instead of a filename; update the require initialization so both
createRequire calls are anchored to a file path (e.g., use
Path.join(process.cwd(), 'package.json') or reuse the already-created
moduleRequire) by changing the argument passed to createRequire for the variable
require (or simply assign require = moduleRequire) so createRequire receives a
filename consistently.
In `@test/2-config-test.js`:
- Around line 62-67: The Immutability describe block (and its siblings) rely on
environment variables that are only set in the "Library initialization"
beforeEach, causing implicit ordering dependency; update the tests so env setup
is deterministic by moving the environment initialization (setting
NODE_CONFIG_DIR, NODE_ENV, NODE_APP_INSTANCE, NODE_CONFIG, process.argv and
CUSTOM_* vars) into a top-level beforeEach that runs before all sibling describe
blocks, or alternatively repeat that env reset at the start of each sibling
beforeEach (the one that calls requireUncached('./lib/config.mjs')) so
requireUncached always runs with the expected environment.
In `@test/type-files/parser.ts`:
- Line 1: The import path '../../types/parser' in the file referencing the
symbol Parser is unresolved; either add a types declaration (e.g., create
types/parser.d.ts that exports the Parser type) or update the import to the
correct existing module path that exports Parser (or adjust tsconfig paths to
map 'types/*' to the correct folder). Locate the import statement that reads
"import Parser from '../../types/parser'" and either (A) add a declaration file
exporting Parser (matching the expected default/export shape), or (B) change the
import to the actual file that defines Parser, and re-run TypeScript to confirm
the error is gone.
In `@test/util.js`:
- Around line 43-53: The test is passing a raw string into loadFileConfigs which
still delegates to the Load constructor that expects an options object
(configDir), so update the test call to pass an object like {configDir:
path.join(import.meta.dirname, '5-config')} instead of a plain string; locate
the call in the test util.js and change the two occurrences that call
util.loadFileConfigs(...) to pass the options object so they map to the Load
constructor's expected configDir parameter (symbols: loadFileConfigs, Load,
configDir).
In `@test/x-deferred-configs-ts.js`:
- Line 20: The closing of the before callback is on the same line as the await
call; move the trailing closing sequence "});" off the await line so that the
await expression "CONFIG = await requireUncached('./lib/config.mjs');" ends on
its own line and the callback closure "});" sits on the following line (this
affects the before callback surrounding CONFIG and requireUncached), preserving
indentation and semicolon placement.
---
Nitpick comments:
In `@lib/config.mjs`:
- Around line 14-15: DEFAULT_CLONE_DEPTH is duplicated between lib/config.mjs
and lib/util.js; remove the duplicate by centralizing the constant (either
export DEFAULT_CLONE_DEPTH from lib/util.js or create a shared constants module)
and import it into lib/config.mjs instead of redefining it. Update references in
lib/config.mjs to use the imported DEFAULT_CLONE_DEPTH (leave LOAD_SYMBOL
untouched) and ensure lib/util.js (or the new shared module) exports
DEFAULT_CLONE_DEPTH so all callers use the single source of truth.
- Around line 529-545: attachProtoDeep currently skips adding hidden prototype
methods when a config sub-object has a truthy key named
'get'/'has'/'util'/LOAD_SYMBOL, which lets those keys silently shadow the
methods; update the loop in attachProtoDeep so it detects owned properties with
Object.prototype.hasOwnProperty.call(toObject, fnName) and then always call
Util.makeHidden(toObject, fnName, this.#config[fnName]) when the existing
property is not a function (i.e., change the condition to attach if the prop is
missing or its typeof !== 'function'), referencing attachProtoDeep,
Util.makeHidden, and LOAD_SYMBOL so named keys no longer block provisioning the
method behaviors.
In `@parser.js`:
- Around line 90-103: The current logic in Parser.tsParser redundantly checks
require?.extensions['.ts'] === undefined before guarding registration with TS
=== null, which is confusing and can lead to repeated conditional evaluation;
change the flow to first check the module-level TS variable (if TS === null) and
only then load and call TS.register(), and after successful registration ensure
the `.ts` loader is present (e.g., set or rely on require.extensions['.ts'] if
needed) so repeated calls to Parser.tsParser no longer re-enter the registration
path; update references in this function (Parser.tsParser, TS, and TS.register)
accordingly.
In `@test/_utils/requireUncached.mjs`:
- Around line 8-11: The helper requireUncached currently returns only
module.default which can be undefined for modules without a default export;
update the requireUncached function to return the full imported module namespace
(the imported module object) so callers can destructure what they need, or
alternatively add a guard inside requireUncached that throws or returns the
module namespace when module.default is undefined; locate the requireUncached
function and change its return behavior accordingly so it no longer silently
returns undefined for non-default exports.
In `@test/2-config-test.js`:
- Around line 41-44: The test assigns numeric and other non-string values
directly to process.env (e.g.,
process.env.CUSTOM_NUMBER_INTEGER_ENVIRONMENT_VAR,
process.env.CUSTOM_NUMBER_FLOAT_ENVIRONMENT_VAR,
process.env.CUSTOM_NUMBER_EMPTY_ENVIRONMENT_VAR,
process.env.CUSTOM_NUMBER_STRING_ENVIRONMENT_VAR) which are implicitly coerced
to strings; change these assignments to explicit string literals (e.g., "1001",
"3.14", "", "String") so the test reflects the actual string values consumers
receive and avoids misleading numeric assignments.
In `@test/22-binary.js`:
- Line 3: Remove the redundant import of Buffer from 'buffer' — delete the line
"import { Buffer } from 'buffer';" (the code uses the global Buffer, so no
import is necessary) and run tests to confirm no other module relies on that
named import; if any code explicitly expects a locally imported Buffer symbol,
replace references to use the global Buffer instead.
In `@test/6-strict-mode.js`:
- Around line 73-97: In function _expectException, reset the closure-scoped
variable error at the start of the beforeEach block to avoid carrying a stale
error between test runs; inside the beforeEach (before the try/catch) set error
= undefined so each test begins with a clean state when
requireUncached('./lib/config.mjs') runs and any thrown exception is freshly
captured.
In `@test/8-config-extending.js`:
- Around line 28-30: Remove the redundant reassignment of
process.env.NODE_CONFIG_DIR inside the test body since the before hook already
sets it; locate the code that sets process.env.NODE_CONFIG_DIR (the before hook
and the line in the test that does process.env.NODE_CONFIG_DIR =
import.meta.dirname + '/8-config') and delete the latter assignment so only the
before hook establishes NODE_CONFIG_DIR, leaving the FileSystem.readFileSync
call and the rest of the test unchanged.
a691d9e to
ab50588
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
test/6-strict-mode.js (1)
77-97:⚠️ Potential issue | 🟠 MajorClear env vars you don’t set in
_expectExceptionto prevent cross-case leakage.In the
beforeEach(Line 77-97),NODE_ENV/NODE_CONFIG_ENVare only set if provided, but never deleted otherwise. Since this helper creates many sibling describes in one file, a prior case can leave one of these set and influence later cases.Proposed adjustment
beforeEach(async function() { // Change the configuration directory for testing process.env.NODE_CONFIG_DIR = import.meta.dirname + '/6-config'; process.env.NODE_CONFIG_STRICT_MODE = 1; process.env.NODE_APP_INSTANCE = opts.APP_INSTANCE; + delete process.env.NODE_ENV; + delete process.env.NODE_CONFIG_ENV; + if (!!opts.NODE_ENV) { process.env.NODE_ENV = opts.NODE_ENV; } if (!!opts.NODE_CONFIG_ENV) { process.env.NODE_CONFIG_ENV = opts.NODE_CONFIG_ENV; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/6-strict-mode.js` around lines 77 - 97, In the beforeEach hook that sets up test env for requireUncached('./lib/config.mjs'), ensure you clear NODE_ENV and NODE_CONFIG_ENV when opts doesn't provide them to avoid cross-case leakage: modify the beforeEach logic (the same block that currently sets process.env.NODE_ENV and process.env.NODE_CONFIG_ENV based on opts) to explicitly delete process.env.NODE_ENV and process.env.NODE_CONFIG_ENV in the else path (so when !!opts.NODE_ENV or !!opts.NODE_CONFIG_ENV is false), leaving the existing delete process.env.NODE_CONFIG and requireUncached usage intact.parser.js (1)
151-158:⚠️ Potential issue | 🟡 Minor
icedParserreturnsundefined— it only registers the compiler but never loads the config file.The function sets up
Icedand callsregister(), but unlikecoffeeParser(which ends withreturn require(filename)), this function has no return statement. Any.icedconfig file will parse toundefined. This appears to be a pre-existing bug (not introduced by this PR), but worth noting since this code was touched.Likely missing return
Parser.icedParser = function(filename, content) { - Iced = moduleRequire(ICED_DEP); + if (!Iced) { + Iced = moduleRequire(ICED_DEP); + } // coffee-script >= 1.7.0 requires explicit registration for require() to work if (Iced.register) { Iced.register(); } + return require(filename); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 151 - 158, Parser.icedParser currently only registers the Iced compiler and never returns the loaded config, causing .iced files to parse to undefined; update Parser.icedParser to, after calling Iced.register(), require and return the parsed module for the given filename (mirror the behavior of coffeeParser), ensuring the function returns the result of require(filename) so callers receive the loaded config.test/1-protected-test.js (1)
27-45:⚠️ Potential issue | 🟡 Minor
process.argv.pushinbeforeEachaccumulates duplicate--NODE_CONFIGentries across tests.Line 39 pushes a
--NODE_CONFIGargument intoprocess.argvon everybeforeEachinvocation. Sincenode:testruns the outerbeforeEachfor eachitblock (including nested ones),process.argvgrows with duplicate entries throughout the suite. WhilegetCmdLineArgreturns on the first match so results are correct, consider cleaning up in anafterEachor switching tobeforeif per-test isolation isn't needed.Option A: Clean up argv
+ import { describe, it, beforeEach, afterEach } from 'node:test'; ... + afterEach(function() { + const idx = process.argv.indexOf('--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}'); + if (idx !== -1) process.argv.splice(idx, 1); + });Option B: Use `before` instead of `beforeEach`
If tests don't need per-test config isolation, switch the outer hook to
beforeso the push only happens once.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/1-protected-test.js` around lines 27 - 45, The test hook currently pushes a persistent CLI arg via process.argv.push('--NODE_CONFIG=...') inside beforeEach, causing duplicates across tests; fix by either moving that push into a before hook (so it runs once) or by adding an afterEach that removes the argument (e.g., find the last index of the exact '--NODE_CONFIG=...' string and splice it out) to restore process.argv; update the hook surrounding the call to process.argv.push (the beforeEach block that calls requireUncached('./lib/config.mjs')) accordingly and ensure any cleanup runs before/after requireUncached to preserve test isolation.test/16-config/parser/custom-3.js (1)
1-11:⚠️ Potential issue | 🔴 CriticalParser configuration files cannot be loaded via synchronous
require().These files use ESM
import/exportsyntax (lines 1 and 11), but_loadParser()inlib/util.jsloads them via synchronousrequire()(line 1464). Node.js does not support loading ES modules withexportviarequire(), even withcreateRequire(). This will fail at runtime.Convert to CommonJS (
module.exports), or refactor_loadParser()to use asyncimport().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/16-config/parser/custom-3.js` around lines 1 - 11, The parser config file uses ESM import/export (Parser.setParser in custom-3.js and default export) but the loader function _loadParser in lib/util.js uses synchronous require(), which breaks for ES modules; fix by converting this config file to CommonJS (replace import/export with require() and module.exports and keep Parser.setParser/Parser.setFilesOrder calls) OR update _loadParser to dynamically import ES modules using async import() (detect ESM by package/type or file extension and await import(path), then read exported default or module.exports) and adjust callers to handle the async load; reference Parser.setParser, Parser.setFilesOrder in the config and _loadParser in lib/util.js when making the change.
🧹 Nitpick comments (8)
test/16-custom-parser.js (1)
78-80: Restore the priorNODE_CONFIG_PARSERvalue instead of unconditional delete.Unconditionally deleting in
after()can surprise other tests running in the same process (or local debugging). Capturing the previous value at file scope and restoring it is more robust.Proposed tweak
describe('Tests for a custom parser provided by NODE_CONFIG_PARSER', function() { + const prevParser = process.env.NODE_CONFIG_PARSER; + ... after(async function () { - delete process.env.NODE_CONFIG_PARSER; + if (prevParser === undefined) delete process.env.NODE_CONFIG_PARSER; + else process.env.NODE_CONFIG_PARSER = prevParser; }); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/16-custom-parser.js` around lines 78 - 80, The after() hook currently deletes process.env.NODE_CONFIG_PARSER unconditionally; capture the prior value at file scope (e.g., const OLD_NODE_CONFIG_PARSER = process.env.NODE_CONFIG_PARSER) before tests run and in the after(async function () { ... }) restore it instead of deleting: if the captured value is undefined delete the env var, otherwise set process.env.NODE_CONFIG_PARSER = OLD_NODE_CONFIG_PARSER. Update references around the existing after(...) block and the top of the test file where NODE_CONFIG_PARSER is touched.test/0-util.js (1)
1187-1199: PreferPath.join(import.meta.dirname, ...)over string concatenation forconfigDir.There are multiple places building
configDirviaimport.meta.dirname + '/config'(Line 1187, Line 1199, Line 1215, etc.) while other call sites correctly usePath.join(...). Mixing separators is usually fine, but this is easy to make fully consistent.Proposed adjustment (pattern)
- let load = new Load({nodeEnv: 'production', configDir: import.meta.dirname + '/config'}) + let load = new Load({nodeEnv: 'production', configDir: Path.join(import.meta.dirname, 'config')})Also applies to: 1696-1711, 1724-1744
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/0-util.js` around lines 1187 - 1199, The tests construct configDir using string concatenation with import.meta.dirname (e.g., in the Load instantiation calls around Load and load.loadCustomEnvVars), which is inconsistent with other call sites that use Path.join; update those Load({nodeEnv: ..., configDir: import.meta.dirname + '/config'}) usages to build the path with Path.join(import.meta.dirname, 'config') (and similarly for any other concatenations noted) so path separators are handled consistently across platforms.test/makeImmutable-shared-refs.js (1)
1-5: Consider importingUtildirectly to reduce env-sensitive coupling in this test.
const util = Config.utilmakes this test depend on../lib/config.mjssingleton initialization (and whatever env/argv it reads) even though the assertions only needmakeImmutable. If other tests leaveNODE_CONFIG_*vars behind, this file can become order-dependent.If the intent is “public API only”, keep as-is; otherwise consider importing
{ Util }from../lib/util.jshere and using that instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/makeImmutable-shared-refs.js` around lines 1 - 5, Replace the env-sensitive indirect access via Config.util by importing the utility module directly: remove the const util = Config.util line and instead import { Util } (or the specific exported makeImmutable) from '../lib/util.js' and use Util.makeImmutable (or the imported makeImmutable) in the tests; this replaces references to Config and the Config.util symbol with direct references to Util or makeImmutable to avoid depending on Config's singleton initialization.test/x-config-ts/default.ts (1)
3-44: Add minimal typing forbootstrap({ defer })to avoid implicit-any/implicit-this TS breakage.If the repo’s TS settings get stricter,
function bootstrap({ defer })will likely tripnoImplicitAny. You can keep it minimal and local to the fixture.Proposed tweak
import { Config } from './types'; -function bootstrap({ defer }) { +type DeferFn = (fn: any) => any; + +function bootstrap({ defer }: { defer: DeferFn }): Config { const defaultConfig: Config = { siteTitle: 'Site title', latitude: 1, longitude: 2, arrayWithNull: [null], }; @@ return defaultConfig; } export default bootstrap;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/x-config-ts/default.ts` around lines 3 - 44, Add explicit TypeScript types to the bootstrap signature to avoid implicit-any/implicit-this: change function bootstrap({ defer }) to annotate the destructured param and the return type (e.g. function bootstrap({ defer }: { defer: any }): Config) so the compiler knows defer's type and the function returns Config; ensure the Config type reference remains and adjust to a tighter defer signature later if desired.test/_utils/requireUncached.mjs (1)
8-11:module.defaultassumption limits reusability.This utility always returns
module.default, which works for the current use case (lib/config.mjsexports a default). If it's ever used with a module that only has named exports, the caller will silently getundefined. Consider returningmodule.default ?? modulefor resilience, or at minimum document the expectation.Suggested improvement
export async function requireUncached(moduleName) { let path = Path.resolve(process.cwd(), moduleName); let module = await import(`${path}?c=${count++}`); - return module.default; + return module.default ?? module; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/_utils/requireUncached.mjs` around lines 8 - 11, The helper requireUncached currently always returns module.default which yields undefined for modules that only export named members; update the function requireUncached to return the default export if present otherwise the whole module (e.g., return module.default ?? module) so callers receive either the default or the module object, ensuring resilience across both default and named-export modules.lib/config.mjs (1)
14-15:DEFAULT_CLONE_DEPTHis duplicated fromlib/util.js.This constant is also defined at
lib/util.js:78. Having it in two places risks divergence.Consider importing from util.js
Export the constant from
lib/util.jsand import it here, or move it to a shared constants module.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 14 - 15, DEFAULT_CLONE_DEPTH is duplicated in lib/config.mjs and lib/util.js; remove the duplicate and import the canonical value instead: export DEFAULT_CLONE_DEPTH from lib/util.js (or move it to a new shared constants module) and replace the local const DEFAULT_CLONE_DEPTH in lib/config.mjs with an import of that exported symbol (keep LOAD_SYMBOL unchanged).test/8-config-extending.js (1)
28-29: RedundantNODE_CONFIG_DIRassignment inside test body.Line 13 in
before()already setsprocess.env.NODE_CONFIG_DIRtoimport.meta.dirname + '/8-config'. Line 28 assigns the exact same value again inside the test, making it dead code.♻️ Suggested cleanup
it('Extending a configuration with another configuration should work without error', function () { - process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config'; - let filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json'); + let filename = path.join(import.meta.dirname, '8-config', 'base-config.json'); let base_config = JSON.parse(FileSystem.readFileSync(filename, 'utf-8'));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` around lines 28 - 29, The assignment to process.env.NODE_CONFIG_DIR inside the test is redundant because before() already sets it; remove the duplicate assignment on the test lines (the second process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config') so the test uses the value established by before() and avoid dead code while keeping the existing filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json') reference intact.test/11-regexp.js (1)
8-14:beforeEachreloads config before every test unnecessarily — preferbefore.All five tests in this suite share identical environment settings and none of them mutate
config. UsingbeforeEachcauses five async module re-imports where one would suffice. Every other migrated test file in this PR (test/21-date.js,test/8-config-extending.js) correctly usesbeforefor the same pattern.♻️ Suggested change
-import { describe, it, beforeEach } from 'node:test'; +import { describe, it, before } from 'node:test'; import assert from 'assert'; import { requireUncached } from './_utils/requireUncached.mjs'; describe('Tests for regexp', function() { let config; - beforeEach(async function() { + before(async function() { process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config'; process.env.NODE_ENV = 'test'; process.env.NODE_APP_INSTANCE = 'regexp'; config = await requireUncached('./lib/config.mjs'); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/11-regexp.js` around lines 8 - 14, The test setup uses beforeEach to set env and reload config (process.env assignments and config = await requireUncached('./lib/config.mjs')) which causes unnecessary repeated async imports; change beforeEach to before so the environment variables (import.meta.dirname + '/config', NODE_ENV, NODE_APP_INSTANCE) and the single requireUncached('./lib/config.mjs') call run once for the suite. Update the hook name from beforeEach to before in this file (referencing beforeEach, before, config, requireUncached, import.meta.dirname) and ensure the async function signature is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/16-custom-parser.js`:
- Around line 9-13: The beforeEach block must force-disable any custom parser by
clearing NODE_CONFIG_PARSER before loading the module: add a line to delete
process.env.NODE_CONFIG_PARSER (or set it to undefined/empty) at the start of
beforeEach (the same block that sets NODE_CONFIG_DIR and calls
requireUncached('./lib/config.mjs')), so the default parser sanity check always
runs regardless of the developer’s environment or previous tests.
In `@test/2-config-test.js`:
- Line 3: The requireUncached implementation should build a file:// URL instead
of interpolating a filesystem path into import(); in
test/_utils/requireUncached.mjs replace Path.resolve(...) string interpolation
with pathToFileURL(fsPath) (imported from 'node:url'), append a cache-busting
search param (e.g. url.searchParams.set('c', String(count++))) and call
import(url.href) to load the module; keep the exported function name
requireUncached and the incrementing count logic so Windows paths like C:\...
become valid file URLs.
---
Outside diff comments:
In `@parser.js`:
- Around line 151-158: Parser.icedParser currently only registers the Iced
compiler and never returns the loaded config, causing .iced files to parse to
undefined; update Parser.icedParser to, after calling Iced.register(), require
and return the parsed module for the given filename (mirror the behavior of
coffeeParser), ensuring the function returns the result of require(filename) so
callers receive the loaded config.
In `@test/1-protected-test.js`:
- Around line 27-45: The test hook currently pushes a persistent CLI arg via
process.argv.push('--NODE_CONFIG=...') inside beforeEach, causing duplicates
across tests; fix by either moving that push into a before hook (so it runs
once) or by adding an afterEach that removes the argument (e.g., find the last
index of the exact '--NODE_CONFIG=...' string and splice it out) to restore
process.argv; update the hook surrounding the call to process.argv.push (the
beforeEach block that calls requireUncached('./lib/config.mjs')) accordingly and
ensure any cleanup runs before/after requireUncached to preserve test isolation.
In `@test/16-config/parser/custom-3.js`:
- Around line 1-11: The parser config file uses ESM import/export
(Parser.setParser in custom-3.js and default export) but the loader function
_loadParser in lib/util.js uses synchronous require(), which breaks for ES
modules; fix by converting this config file to CommonJS (replace import/export
with require() and module.exports and keep Parser.setParser/Parser.setFilesOrder
calls) OR update _loadParser to dynamically import ES modules using async
import() (detect ESM by package/type or file extension and await import(path),
then read exported default or module.exports) and adjust callers to handle the
async load; reference Parser.setParser, Parser.setFilesOrder in the config and
_loadParser in lib/util.js when making the change.
In `@test/6-strict-mode.js`:
- Around line 77-97: In the beforeEach hook that sets up test env for
requireUncached('./lib/config.mjs'), ensure you clear NODE_ENV and
NODE_CONFIG_ENV when opts doesn't provide them to avoid cross-case leakage:
modify the beforeEach logic (the same block that currently sets
process.env.NODE_ENV and process.env.NODE_CONFIG_ENV based on opts) to
explicitly delete process.env.NODE_ENV and process.env.NODE_CONFIG_ENV in the
else path (so when !!opts.NODE_ENV or !!opts.NODE_CONFIG_ENV is false), leaving
the existing delete process.env.NODE_CONFIG and requireUncached usage intact.
---
Duplicate comments:
In `@lib/config.js`:
- Line 4: The current CommonJS file lib/config.js uses
require('./config.mjs').default which causes ERR_REQUIRE_ESM on Node 20+;
replace the synchronous require with a dynamic import or provide a CJS wrapper:
update lib/config.js to export a Promise that resolves to the ES module default
via import('./config.mjs').then(m => m.default) (or alternatively rename/create
a CJS module export that re-exports the default from config.mjs as a synchronous
CommonJS export by converting config.mjs to a .cjs file); locate the offending
expression require('./config.mjs').default and change it to use
import('./config.mjs').then(m => m.default) or move the implementation into a
.cjs file so consumers no longer call require() on an .mjs file.
In `@lib/defer.js`:
- Line 1: The file uses ESM-style import "import { isAsyncFunction } from
'node:util/types';" in a .js file without "type":"module"; replace that import
with a CommonJS require (const { isAsyncFunction } = require('node:util/types'))
or alternatively convert the file to .mjs or set "type":"module" in
package.json; update lib/defer.js to use the chosen module style and ensure any
usages of isAsyncFunction continue to work with the new import form.
In `@lib/util.js`:
- Around line 1456-1472: In _loadParser, ensure createRequire is called with an
absolute path by wrapping dir with _toAbsolutePath (i.e.
createRequire(_toAbsolutePath(dir))) to avoid issues when NODE_CONFIG_DIR is
relative, and modify the catch block so it does not implicitly return undefined
— on failure return defaultParser (or DEFAULT_OPTIONS.parser where appropriate)
and still log the error (keep the console.warn/console.log or processLogger) so
downstream code (e.g. DEFAULT_OPTIONS.parser consumers) is not clobbered by an
undefined value.
In `@parser.js`:
- Around line 6-7: The second createRequire call is still given a directory
(process.cwd()) while the first uses Path.join(process.cwd(), 'package.json');
update the require initializer to use an anchor file like the first one for
consistency: replace createRequire(process.cwd()) with
createRequire(Path.join(process.cwd(), 'package.json')) so both moduleRequire
and require are created from the same package.json anchor (locate the two
createRequire assignments for moduleRequire and require).
In `@test/16-config/parser/custom-2.js`:
- Around line 1-21: The file uses ESM import/export but tests expect CJS
loading; update the module to support both formats by keeping the existing
import of Parser and at the end add a CJS export fallback so the Parser object
is available to require()-based consumers (i.e., ensure module.exports = Parser
when module.exports is defined). Keep the existing Parser.setParser(...) and
Parser.setFilesOrder(...) calls unchanged so behavior remains identical.
In `@test/16-custom-parser.js`:
- Around line 26-31: Tests use mixed conventions for NODE_CONFIG_PARSER paths
(custom-1.js vs custom-2/custom-3 without extension) which can break an ESM
import() loader; make the convention explicit by updating the test to include
the .js extension for all parser env values (change occurrences that set
NODE_CONFIG_PARSER for custom-2 and custom-3 to end with .js), and re-run tests;
if instead you prefer loader compatibility, ensure the parser loader
(_loadParser / setParserOrder / setParser in lib/util.js) resolves missing
extensions via createRequire or a resolver before calling import() so bare names
won’t fail—pick one approach and apply consistently across the test cases.
In `@test/2-config-test.js`:
- Around line 18-47: The tests are order-dependent because only the first
beforeEach sets process-wide variables (NODE_CONFIG_DIR, NODE_ENV, NODE_CONFIG,
process.argv and related process.env keys) before calling
requireUncached('./lib/config.mjs'); fix by centralizing that setup: create a
shared setup helper (or move the existing beforeEach block) that sets/clears
NODE_CONFIG_DIR, NODE_ENV, NODE_APP_INSTANCE, NODE_CONFIG, all CUSTOM_* env vars
and resets process.argv, and call it in every describe’s beforeEach (or the
top-level beforeEach) and ensure you always call
requireUncached('./lib/config.mjs') after this reset so each describe gets a
clean env/argv for config initialization.
In `@test/9-config/default.js`:
- Around line 1-16: The file currently uses CommonJS module.exports
(module.exports = ({raw}) => {...}) which can break when the package is
published as "type": "module"; update this to an ESM-friendly export or
explicitly make the file CommonJS: either (A) change the export to "export
default ({raw}) => {...}" to be ESM-compatible, or (B) rename the file to a .cjs
extension or ensure package.json has "type": "commonjs" so module.exports
remains valid; verify the change covers the factory and referenced symbols
(module.exports, raw, yell, aPromise, nestedRaw) and align the decision with the
findings in test/3-config/local.js.
In `@test/type-files/defer.ts`:
- Line 1: The import of deferConfig and DeferredConfig from
'../../types/lib/defer' cannot be resolved; confirm whether the module file
exists and either add the missing types file (exporting deferConfig and
DeferredConfig from types/lib/defer as .ts or .d.ts) or change the import in
test/type-files/defer.ts to the correct existing module path that exports those
symbols; locate references to deferConfig and DeferredConfig in the repo to
decide whether to create the types file or update the import accordingly.
---
Nitpick comments:
In `@lib/config.mjs`:
- Around line 14-15: DEFAULT_CLONE_DEPTH is duplicated in lib/config.mjs and
lib/util.js; remove the duplicate and import the canonical value instead: export
DEFAULT_CLONE_DEPTH from lib/util.js (or move it to a new shared constants
module) and replace the local const DEFAULT_CLONE_DEPTH in lib/config.mjs with
an import of that exported symbol (keep LOAD_SYMBOL unchanged).
In `@test/_utils/requireUncached.mjs`:
- Around line 8-11: The helper requireUncached currently always returns
module.default which yields undefined for modules that only export named
members; update the function requireUncached to return the default export if
present otherwise the whole module (e.g., return module.default ?? module) so
callers receive either the default or the module object, ensuring resilience
across both default and named-export modules.
In `@test/0-util.js`:
- Around line 1187-1199: The tests construct configDir using string
concatenation with import.meta.dirname (e.g., in the Load instantiation calls
around Load and load.loadCustomEnvVars), which is inconsistent with other call
sites that use Path.join; update those Load({nodeEnv: ..., configDir:
import.meta.dirname + '/config'}) usages to build the path with
Path.join(import.meta.dirname, 'config') (and similarly for any other
concatenations noted) so path separators are handled consistently across
platforms.
In `@test/11-regexp.js`:
- Around line 8-14: The test setup uses beforeEach to set env and reload config
(process.env assignments and config = await requireUncached('./lib/config.mjs'))
which causes unnecessary repeated async imports; change beforeEach to before so
the environment variables (import.meta.dirname + '/config', NODE_ENV,
NODE_APP_INSTANCE) and the single requireUncached('./lib/config.mjs') call run
once for the suite. Update the hook name from beforeEach to before in this file
(referencing beforeEach, before, config, requireUncached, import.meta.dirname)
and ensure the async function signature is preserved.
In `@test/16-custom-parser.js`:
- Around line 78-80: The after() hook currently deletes
process.env.NODE_CONFIG_PARSER unconditionally; capture the prior value at file
scope (e.g., const OLD_NODE_CONFIG_PARSER = process.env.NODE_CONFIG_PARSER)
before tests run and in the after(async function () { ... }) restore it instead
of deleting: if the captured value is undefined delete the env var, otherwise
set process.env.NODE_CONFIG_PARSER = OLD_NODE_CONFIG_PARSER. Update references
around the existing after(...) block and the top of the test file where
NODE_CONFIG_PARSER is touched.
In `@test/8-config-extending.js`:
- Around line 28-29: The assignment to process.env.NODE_CONFIG_DIR inside the
test is redundant because before() already sets it; remove the duplicate
assignment on the test lines (the second process.env.NODE_CONFIG_DIR =
import.meta.dirname + '/8-config') so the test uses the value established by
before() and avoid dead code while keeping the existing filename =
path.join(process.env.NODE_CONFIG_DIR,'base-config.json') reference intact.
In `@test/makeImmutable-shared-refs.js`:
- Around line 1-5: Replace the env-sensitive indirect access via Config.util by
importing the utility module directly: remove the const util = Config.util line
and instead import { Util } (or the specific exported makeImmutable) from
'../lib/util.js' and use Util.makeImmutable (or the imported makeImmutable) in
the tests; this replaces references to Config and the Config.util symbol with
direct references to Util or makeImmutable to avoid depending on Config's
singleton initialization.
In `@test/x-config-ts/default.ts`:
- Around line 3-44: Add explicit TypeScript types to the bootstrap signature to
avoid implicit-any/implicit-this: change function bootstrap({ defer }) to
annotate the destructured param and the return type (e.g. function bootstrap({
defer }: { defer: any }): Config) so the compiler knows defer's type and the
function returns Config; ensure the Config type reference remains and adjust to
a tighter defer signature later if desired.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
parser.js (1)
151-158:⚠️ Potential issue | 🔴 Critical
icedParseris missing its lazy-load guard and always returnsundefined.Two bugs in this function:
- Line 152 (changed in this PR): The
if (!Iced)guard was removed, causingmoduleRequire(ICED_DEP)andIced.register()to execute on every.icedfile invocation, unlike every other lazy-loaded parser in this file.- There is no
return require(filename)— unlikecoffeeParser, the function returnsundefinedfor every.icedconfig file.🐛 Proposed fix
Parser.icedParser = function(filename, content) { - Iced = moduleRequire(ICED_DEP); - - // coffee-script >= 1.7.0 requires explicit registration for require() to work - if (Iced.register) { - Iced.register(); + if (!Iced) { + Iced = moduleRequire(ICED_DEP); + // coffee-script >= 1.7.0 requires explicit registration for require() to work + if (Iced.register) { + Iced.register(); + } } + return require(filename); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 151 - 158, Parser.icedParser currently lacks the lazy-load guard and a return, causing moduleRequire(ICED_DEP) and Iced.register() to run every time and the function to return undefined; add the same lazy-load pattern used by other parsers: check if (!Iced) then set Iced = moduleRequire(ICED_DEP) and call Iced.register() if available, and finally return require(filename) so Parser.icedParser returns the parsed module. Ensure you reference the existing symbols Iced, moduleRequire, ICED_DEP, Iced.register, and require(filename) when making the change.
🧹 Nitpick comments (2)
lib/config.mjs (1)
15-15:DEFAULT_CLONE_DEPTHduplicates the constant already defined inlib/util.js.Both files define
20independently. Exporting it fromlib/util.jsand importing it here would prevent a future silent divergence.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` at line 15, Replace the duplicated numeric literal by importing the shared constant from lib/util.js: remove the local declaration of DEFAULT_CLONE_DEPTH in lib/config.mjs and add an import for the exported DEFAULT_CLONE_DEPTH from lib/util.js (or export it from lib/util.js first if it isn’t exported), then use that imported DEFAULT_CLONE_DEPTH wherever the local constant was used.parser.js (1)
21-32: Preferconstfor module-level constants that are never reassigned.All dependency name strings (lines 21–32) and the
Parserobject (line 38) are declared withletbut never reassigned.♻️ Proposed change
-let COFFEE_2_DEP = 'coffeescript', +const COFFEE_2_DEP = 'coffeescript', COFFEE_DEP = 'coffee-script', ... TS_DEP = 'ts-node'; -let Parser = {}; +const Parser = {};Also applies to: 38-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 21 - 32, The module-level dependency name variables (COFFEE_2_DEP, COFFEE_DEP, ICED_DEP, JS_YAML_DEP, YAML_DEP, JSON5_DEP, HJSON_DEP, TOML_DEP, CSON_DEP, PPARSER_DEP, XML_DEP, TS_DEP) and the Parser object are declared with let but never reassigned; change their declarations to const to reflect immutability—update the multi-variable declaration starting with COFFEE_2_DEP to use const and change the declaration of Parser to const as well (preserving the same identifiers and values).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/config.mjs`:
- Around line 680-699: The code mutates envConfig by calling
Util.extendDeep(envConfig, cmdLineConfig, {}), causing the object already pushed
into additional to be modified and the CLI settings applied twice; fix by
merging into a new object instead of mutating envConfig — call
Util.extendDeep({}, envConfig, cmdLineConfig) (remove the redundant trailing
{}), use that result in load.setEnv('NODE_CONFIG', ...) so additional still
holds the original envConfig reference, and then call load.scan(additional) as
before.
In `@parser.js`:
- Line 91: The conditional uses a misleading and incomplete optional chain;
change the check to fully guard the extensions property by replacing "if
(require?.extensions['.ts'] === undefined)" with a safe chained access like "if
(require.extensions?.['.ts'] === undefined)" so that access to ['.ts'] is
protected if extensions is absent (remove the unnecessary optional on require
since it is non-nullable).
---
Outside diff comments:
In `@parser.js`:
- Around line 151-158: Parser.icedParser currently lacks the lazy-load guard and
a return, causing moduleRequire(ICED_DEP) and Iced.register() to run every time
and the function to return undefined; add the same lazy-load pattern used by
other parsers: check if (!Iced) then set Iced = moduleRequire(ICED_DEP) and call
Iced.register() if available, and finally return require(filename) so
Parser.icedParser returns the parsed module. Ensure you reference the existing
symbols Iced, moduleRequire, ICED_DEP, Iced.register, and require(filename) when
making the change.
---
Duplicate comments:
In `@lib/util.js`:
- Around line 1468-1471: The catch block in _loadParser currently just logs the
error and lets the function return undefined, which then overwrites
DEFAULT_OPTIONS.parser in the Load constructor and causes
options.parser.getFilesOrder() to throw; update the catch inside _loadParser to
return the provided defaultParser (and optionally include the error in the
warning message) so callers always receive a valid parser instance and
DEFAULT_OPTIONS.parser cannot be replaced with undefined.
---
Nitpick comments:
In `@lib/config.mjs`:
- Line 15: Replace the duplicated numeric literal by importing the shared
constant from lib/util.js: remove the local declaration of DEFAULT_CLONE_DEPTH
in lib/config.mjs and add an import for the exported DEFAULT_CLONE_DEPTH from
lib/util.js (or export it from lib/util.js first if it isn’t exported), then use
that imported DEFAULT_CLONE_DEPTH wherever the local constant was used.
In `@parser.js`:
- Around line 21-32: The module-level dependency name variables (COFFEE_2_DEP,
COFFEE_DEP, ICED_DEP, JS_YAML_DEP, YAML_DEP, JSON5_DEP, HJSON_DEP, TOML_DEP,
CSON_DEP, PPARSER_DEP, XML_DEP, TS_DEP) and the Parser object are declared with
let but never reassigned; change their declarations to const to reflect
immutability—update the multi-variable declaration starting with COFFEE_2_DEP to
use const and change the declaration of Parser to const as well (preserving the
same identifiers and values).
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
parser.js (1)
151-158:⚠️ Potential issue | 🟠 Major
icedParserdoes not return the parsed configuration.When parsing
.icedfiles, the function registers iced-coffee-script but fails to return the result ofrequire(filename), causing it to silently returnundefined. UnlikecoffeeParser(lines 122-145), which explicitly returns the parsed config,icedParseronly performs setup without completing the parse operation. Additionally, it lacks the cache guard used bycoffeeParser, causing the module to reload on every invocation.Add the missing
return require(filename)statement and implement module caching:Proposed fix
Parser.icedParser = function(filename, content) { - Iced = moduleRequire(ICED_DEP); + if (!Iced) { + Iced = moduleRequire(ICED_DEP); + } // coffee-script >= 1.7.0 requires explicit registration for require() to work if (Iced.register) { Iced.register(); } + return require(filename); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 151 - 158, Parser.icedParser currently only registers iced-coffee-script and never returns the parsed module or uses caching; modify Parser.icedParser to mirror coffeeParser by checking require.cache for filename and returning the cached module.exports if present, otherwise require(filename) and return its exports (i.e., add a cache guard and a final "return require(filename)" equivalent) so the parsed configuration is returned and modules aren't reloaded on every call.
♻️ Duplicate comments (1)
lib/config.mjs (1)
697-697:⚠️ Potential issue | 🟠 Major
Util.extendDeep(envConfig, cmdLineConfig, {})mutatesenvConfigalready held by reference inadditional.When both
NODE_CONFIGenv var and--NODE_CONFIGCLI arg are set:
envConfigreference is pushed intoadditionalat line 680- Line 697 mutates
envConfigin-place to includecmdLineConfigadditional[0].confignow contains the merged resultload.scan(additional)then applies(envConfig ∪ cmdLine)andcmdLineConfigseparately — CLI values are applied twiceAdditionally, the trailing
{}argument is unnecessary and misleading.🐛 Proposed fix
- load.setEnv('NODE_CONFIG', JSON.stringify(Util.extendDeep(envConfig, cmdLineConfig, {}))); + load.setEnv('NODE_CONFIG', JSON.stringify(Util.extendDeep({}, envConfig, cmdLineConfig)));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` at line 697, The current call to Util.extendDeep(envConfig, cmdLineConfig, {}) mutates envConfig (which was earlier pushed into additional), causing CLI values to be applied twice; change the call to merge into a fresh object so envConfig is not mutated (use Util.extendDeep({}, envConfig, cmdLineConfig)) and remove the trailing empty object argument so load.setEnv receives a new merged object without altering additional[0].config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@parser.js`:
- Around line 151-158: Parser.icedParser currently only registers
iced-coffee-script and never returns the parsed module or uses caching; modify
Parser.icedParser to mirror coffeeParser by checking require.cache for filename
and returning the cached module.exports if present, otherwise require(filename)
and return its exports (i.e., add a cache guard and a final "return
require(filename)" equivalent) so the parsed configuration is returned and
modules aren't reloaded on every call.
---
Duplicate comments:
In `@lib/config.mjs`:
- Line 697: The current call to Util.extendDeep(envConfig, cmdLineConfig, {})
mutates envConfig (which was earlier pushed into additional), causing CLI values
to be applied twice; change the call to merge into a fresh object so envConfig
is not mutated (use Util.extendDeep({}, envConfig, cmdLineConfig)) and remove
the trailing empty object argument so load.setEnv receives a new merged object
without altering additional[0].config.
are breaking ESM migration badly.
dynamic imports will not rerun '.js' files even if they detect them as ESM files, and I don't know why.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
parser.js (1)
151-158:⚠️ Potential issue | 🔴 Critical
Parser.icedParserdrops parsed output and can break config loading.At Line 152-158, the function only registers
iced-coffee-scriptand returnsundefined. That can propagate an undefined config object into merge/load paths.🔧 Proposed fix
Parser.icedParser = function(filename, content) { - Iced = moduleRequire(ICED_DEP); - - // coffee-script >= 1.7.0 requires explicit registration for require() to work - if (Iced.register) { - Iced.register(); - } + if (!Iced) { + Iced = moduleRequire(ICED_DEP); + // coffee-script >= 1.7.0 requires explicit registration for require() to work + if (Iced.register) { + Iced.register(); + } + } + return require(filename); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@parser.js` around lines 151 - 158, Parser.icedParser currently only registers the iced-coffee-script module and returns undefined; modify Parser.icedParser to actually parse/compile the provided content and return the result (so callers get a valid config object instead of undefined). Specifically, after requiring Iced and calling Iced.register(), invoke the appropriate Iced parsing/compilation API (e.g., Iced.compile or Iced.parse depending on the installed iced-coffee-script version) with the content and filename and return that value from Parser.icedParser so downstream merge/load paths receive the parsed output.test/1-protected-test.js (1)
27-45:⚠️ Potential issue | 🟡 Minor
process.argv.pushmodification persists across tests.Line 39 pushes
--NODE_CONFIG=...toprocess.argv, but this is never removed. SincebeforeEachruns before every test, this will keep appending the same argument, and the modifications will persist to subsequent test files.🛡️ Proposed fix - save and restore argv
let config; + let originalArgv; beforeEach(async function () { + originalArgv = process.argv.slice(); // Change the configuration directory for testing process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config'; // ... existing setup ... process.argv.push('--NODE_CONFIG={"EnvOverride":{"parm5":"overridden from --NODE_CONFIG","parm6":101}}'); config = await requireUncached('./lib/config.mjs'); }); + afterEach(function() { + process.argv = originalArgv; + });Note: Import
afterEachfromnode:test.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/1-protected-test.js` around lines 27 - 45, The test setup pushes a persistent arg into process.argv inside beforeEach (the beforeEach block that calls requireUncached('./lib/config.mjs')), causing argv pollution across tests; fix by saving the original process.argv at the start of beforeEach (or module top) and restore it in an afterEach hook (import afterEach from 'node:test') so the push in beforeEach is undone after each test, ensuring process.argv is returned to its original value before calling requireUncached.
♻️ Duplicate comments (7)
lib/config.mjs (1)
696-697:⚠️ Potential issue | 🟠 Major
Util.extendDeep(envConfig, cmdLineConfig, {})still mutatesenvConfigheld inadditional.This was flagged in a previous review. When both
NODE_CONFIGenv var and--NODE_CONFIGCLI arg are set:
- Line 680 pushes
{ config: envConfig }intoadditional- Line 697 mutates
envConfigin-place, soadditional[0].confignow contains the merged resultload.scan(additional)applies the CLI values twiceThe trailing
{}argument is also unnecessary.🐛 Proposed fix
- load.setEnv('NODE_CONFIG', JSON.stringify(Util.extendDeep(envConfig, cmdLineConfig, {}))); + load.setEnv('NODE_CONFIG', JSON.stringify(Util.extendDeep({}, envConfig, cmdLineConfig)));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.mjs` around lines 696 - 697, The current call Util.extendDeep(envConfig, cmdLineConfig, {}) mutates envConfig (which is stored in additional[0].config) causing CLI values to be applied twice; change the merge to start from a fresh target so envConfig is not modified (e.g., call Util.extendDeep({}, envConfig, cmdLineConfig)) and remove the unnecessary trailing {} argument, then pass the resulting stringified object into load.setEnv('NODE_CONFIG', ...); this preserves additional's original envConfig and prevents double application when load.scan(additional) runs.lib/config.js (1)
1-4:⚠️ Potential issue | 🔴 Critical
require('./config.mjs')is incompatible with the supported Node.js versions and will throwERR_REQUIRE_ESMon Node.js 20.x.Synchronous
require()of ES modules (.mjsfiles) is only supported unflagged starting in Node.js 22.12.0. On Node.js 20.x (which is within the supported engine range>= 20.11.0), this code path will throw immediately, blocking all CommonJS consumers.This CommonJS bridge file should use
createRequirefromnode:moduleinstead, matching the pattern used inparser.jsandlib/util.js.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/config.js` around lines 1 - 4, Replace the direct synchronous require of an .mjs ES module with Node's createRequire so CommonJS consumers on Node 20.x won't hit ERR_REQUIRE_ESM: import createRequire from 'node:module' (use the same pattern as parser.js/lib/util.js), build a require function via createRequire(import.meta.url) or equivalent used in those files, then load the ESM with that require and set module.exports = requiredModule.default while keeping the existing /** `@typedef` */ and /** `@type` */ comments intact.test/22-binary.js (1)
10-16:⚠️ Potential issue | 🟡 MinorEnvironment variables set in
beforeEachare not cleaned up.
NODE_CONFIG_DIR,NODE_ENV, andNODE_APP_INSTANCEare set but never restored. They persist for any test files that execute after this one, potentially interfering with tests that don't explicitly configure those variables.🛡️ Proposed fix - add afterEach cleanup
+ let originalConfigDir, originalNodeEnv, originalAppInstance; + + afterEach(function() { + if (originalConfigDir === undefined) delete process.env.NODE_CONFIG_DIR; + else process.env.NODE_CONFIG_DIR = originalConfigDir; + if (originalNodeEnv === undefined) delete process.env.NODE_ENV; + else process.env.NODE_ENV = originalNodeEnv; + if (originalAppInstance === undefined) delete process.env.NODE_APP_INSTANCE; + else process.env.NODE_APP_INSTANCE = originalAppInstance; + }); + beforeEach(async function() { + originalConfigDir = process.env.NODE_CONFIG_DIR; + originalNodeEnv = process.env.NODE_ENV; + originalAppInstance = process.env.NODE_APP_INSTANCE; process.env.NODE_CONFIG_DIR = import.meta.dirname + '/config';Note: Import
afterEachfromnode:testas well.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/22-binary.js` around lines 10 - 16, The beforeEach sets environment variables NODE_CONFIG_DIR, NODE_ENV, and NODE_APP_INSTANCE but never restores them; add an afterEach (imported from node:test) that clears or restores those env vars after each test run and also clears any cached module state used by requireUncached (mirror the teardown behavior to avoid leaking NODE_CONFIG_DIR/NODE_ENV/NODE_APP_INSTANCE into other tests); ensure afterEach resets process.env.NODE_CONFIG_DIR, process.env.NODE_ENV, process.env.NODE_APP_INSTANCE (or restores prior values) and re-invokes requireUncached cleanup if necessary.test/x-deferred-configs-ts.js (1)
20-20:⚠️ Potential issue | 🟡 MinorFormatting issue: closing brace on same line as
await.The
});that closes thebeforecallback is on the same line as theawaitstatement. This appears to be a merge artifact that wasn't fully resolved.🔧 Proposed fix
- CONFIG = await requireUncached('./lib/config.mjs'); }); + CONFIG = await requireUncached('./lib/config.mjs'); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/x-deferred-configs-ts.js` at line 20, The closing brace and parenthesis for the before callback are on the same line as the await call making the formatting wrong; edit the test where CONFIG is assigned via requireUncached('./lib/config.mjs') so that the await expression ends with its semicolon on one line and the closing sequence `});` is moved to its own following line (i.e., separate the await call from the `});` that closes the before callback), locating the code around the CONFIG assignment and the before block (symbols: CONFIG, requireUncached, before).test/2-config-test.js (1)
65-67:⚠️ Potential issue | 🟡 Minor
beforeEachin siblingdescribeblocks doesn't reset environment variables.In
node:test,beforeEachis scoped to its owndescribeblock. The "Immutability" block (and several other sibling blocks) only callsrequireUncachedwithout resettingNODE_CONFIG_DIR,NODE_ENV,NODE_CONFIG, etc.If tests run in isolation (e.g.,
--test-name-pattern), environment variables will be whatever the process inherited, potentially causing config load failures. Consider either promoting the env-var setup to a sharedbeforeat the top-level describe, or repeating the env setup in each sibling block.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/2-config-test.js` around lines 65 - 67, Test environment variables are not being reset for sibling describe blocks because beforeEach in each block only calls requireUncached('./lib/config.mjs') without restoring NODE_CONFIG_DIR, NODE_ENV, NODE_CONFIG, etc.; fix by moving the env setup/reset into a shared hook or adding env-reset in each sibling beforeEach: update the top-level describe (or each describe containing beforeEach) to set/clear process.env.NODE_CONFIG_DIR, process.env.NODE_ENV, process.env.NODE_CONFIG to known values before requiring via requireUncached, and ensure any previous values are restored/cleared after each test to avoid cross-test pollution when running isolated tests; look for beforeEach and requireUncached usages in test/2-config-test.js and adjust accordingly.test/util.js (1)
43-44:⚠️ Potential issue | 🔴 Critical
loadFileConfigscalled with incorrect argument type.Line 43 passes a string directly:
util.loadFileConfigs(path.join(import.meta.dirname, '5-config')). The function expects either aLoadinstance or aLoadOptionsobject. Per the JSDoc and implementation, the correct form is:util.loadFileConfigs({configDir: path.join(import.meta.dirname, '5-config')})as used intest/0-util.jsline 1394. Passing a string will result inoptions.configDirbeing undefined, causing the function to fail.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/util.js` around lines 43 - 44, The test calls loadFileConfigs with a raw string which doesn't match the expected signature; update the call to pass a LoadOptions object instead—use util.loadFileConfigs({ configDir: path.join(import.meta.dirname, '5-config') }) so that loadFileConfigs receives options.configDir correctly (refer to the loadFileConfigs function and existing usage in test/0-util.js for the expected shape).test/16-custom-parser.js (1)
9-13:⚠️ Potential issue | 🟠 MajorForce-clear
NODE_CONFIG_PARSERin the default-parser sanity setup.Line 9-13 loads config without resetting
NODE_CONFIG_PARSER, so external shell state can make this test non-deterministic.Suggested fix
beforeEach(async function() { process.env.NODE_CONFIG_DIR = import.meta.dirname + '/16-config'; + delete process.env.NODE_CONFIG_PARSER; config = await requireUncached('./lib/config.mjs'); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/16-custom-parser.js` around lines 9 - 13, The test's beforeEach setup doesn't clear NODE_CONFIG_PARSER so external shell state can affect requireUncached('./lib/config.mjs'); modify the beforeEach (the function containing process.env.NODE_CONFIG_DIR and the call to requireUncached) to explicitly unset or clear process.env.NODE_CONFIG_PARSER (e.g., delete process.env.NODE_CONFIG_PARSER or set it to an empty string) before calling requireUncached so the default parser is used deterministically.
🧹 Nitpick comments (1)
test/8-config-extending.js (1)
26-37: RedundantNODE_CONFIG_DIRassignment inside the test.Line 28 sets
NODE_CONFIG_DIRagain, but it was already set in thebeforehook at line 13 with the same value. This is unnecessary duplication.♻️ Proposed fix
describe('Extending a base configuration with another configuration', function() { it('Extending a configuration with another configuration should work without error', function () { - process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config'; let filename = path.join(process.env.NODE_CONFIG_DIR,'base-config.json');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/8-config-extending.js` around lines 26 - 37, Remove the redundant environment reset inside the test: delete the `process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config';` line from the `it` block since the `before` hook already sets `NODE_CONFIG_DIR`; leave the rest of the test (variables `filename`, `base_config`, `CONFIG.util.attachProtoDeep`, and the `Util.extendDeep` assertion) unchanged so behavior remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@parser.js`:
- Around line 151-158: Parser.icedParser currently only registers the
iced-coffee-script module and returns undefined; modify Parser.icedParser to
actually parse/compile the provided content and return the result (so callers
get a valid config object instead of undefined). Specifically, after requiring
Iced and calling Iced.register(), invoke the appropriate Iced
parsing/compilation API (e.g., Iced.compile or Iced.parse depending on the
installed iced-coffee-script version) with the content and filename and return
that value from Parser.icedParser so downstream merge/load paths receive the
parsed output.
In `@test/1-protected-test.js`:
- Around line 27-45: The test setup pushes a persistent arg into process.argv
inside beforeEach (the beforeEach block that calls
requireUncached('./lib/config.mjs')), causing argv pollution across tests; fix
by saving the original process.argv at the start of beforeEach (or module top)
and restore it in an afterEach hook (import afterEach from 'node:test') so the
push in beforeEach is undone after each test, ensuring process.argv is returned
to its original value before calling requireUncached.
---
Duplicate comments:
In `@lib/config.js`:
- Around line 1-4: Replace the direct synchronous require of an .mjs ES module
with Node's createRequire so CommonJS consumers on Node 20.x won't hit
ERR_REQUIRE_ESM: import createRequire from 'node:module' (use the same pattern
as parser.js/lib/util.js), build a require function via
createRequire(import.meta.url) or equivalent used in those files, then load the
ESM with that require and set module.exports = requiredModule.default while
keeping the existing /** `@typedef` */ and /** `@type` */ comments intact.
In `@lib/config.mjs`:
- Around line 696-697: The current call Util.extendDeep(envConfig,
cmdLineConfig, {}) mutates envConfig (which is stored in additional[0].config)
causing CLI values to be applied twice; change the merge to start from a fresh
target so envConfig is not modified (e.g., call Util.extendDeep({}, envConfig,
cmdLineConfig)) and remove the unnecessary trailing {} argument, then pass the
resulting stringified object into load.setEnv('NODE_CONFIG', ...); this
preserves additional's original envConfig and prevents double application when
load.scan(additional) runs.
In `@test/16-custom-parser.js`:
- Around line 9-13: The test's beforeEach setup doesn't clear NODE_CONFIG_PARSER
so external shell state can affect requireUncached('./lib/config.mjs'); modify
the beforeEach (the function containing process.env.NODE_CONFIG_DIR and the call
to requireUncached) to explicitly unset or clear process.env.NODE_CONFIG_PARSER
(e.g., delete process.env.NODE_CONFIG_PARSER or set it to an empty string)
before calling requireUncached so the default parser is used deterministically.
In `@test/2-config-test.js`:
- Around line 65-67: Test environment variables are not being reset for sibling
describe blocks because beforeEach in each block only calls
requireUncached('./lib/config.mjs') without restoring NODE_CONFIG_DIR, NODE_ENV,
NODE_CONFIG, etc.; fix by moving the env setup/reset into a shared hook or
adding env-reset in each sibling beforeEach: update the top-level describe (or
each describe containing beforeEach) to set/clear process.env.NODE_CONFIG_DIR,
process.env.NODE_ENV, process.env.NODE_CONFIG to known values before requiring
via requireUncached, and ensure any previous values are restored/cleared after
each test to avoid cross-test pollution when running isolated tests; look for
beforeEach and requireUncached usages in test/2-config-test.js and adjust
accordingly.
In `@test/22-binary.js`:
- Around line 10-16: The beforeEach sets environment variables NODE_CONFIG_DIR,
NODE_ENV, and NODE_APP_INSTANCE but never restores them; add an afterEach
(imported from node:test) that clears or restores those env vars after each test
run and also clears any cached module state used by requireUncached (mirror the
teardown behavior to avoid leaking NODE_CONFIG_DIR/NODE_ENV/NODE_APP_INSTANCE
into other tests); ensure afterEach resets process.env.NODE_CONFIG_DIR,
process.env.NODE_ENV, process.env.NODE_APP_INSTANCE (or restores prior values)
and re-invokes requireUncached cleanup if necessary.
In `@test/util.js`:
- Around line 43-44: The test calls loadFileConfigs with a raw string which
doesn't match the expected signature; update the call to pass a LoadOptions
object instead—use util.loadFileConfigs({ configDir:
path.join(import.meta.dirname, '5-config') }) so that loadFileConfigs receives
options.configDir correctly (refer to the loadFileConfigs function and existing
usage in test/0-util.js for the expected shape).
In `@test/x-deferred-configs-ts.js`:
- Line 20: The closing brace and parenthesis for the before callback are on the
same line as the await call making the formatting wrong; edit the test where
CONFIG is assigned via requireUncached('./lib/config.mjs') so that the await
expression ends with its semicolon on one line and the closing sequence `});` is
moved to its own following line (i.e., separate the await call from the `});`
that closes the before callback), locating the code around the CONFIG assignment
and the before block (symbols: CONFIG, requireUncached, before).
---
Nitpick comments:
In `@test/8-config-extending.js`:
- Around line 26-37: Remove the redundant environment reset inside the test:
delete the `process.env.NODE_CONFIG_DIR = import.meta.dirname + '/8-config';`
line from the `it` block since the `before` hook already sets `NODE_CONFIG_DIR`;
leave the rest of the test (variables `filename`, `base_config`,
`CONFIG.util.attachProtoDeep`, and the `Util.extendDeep` assertion) unchanged so
behavior remains identical.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (45)
async.jsdefer.jslib/config.jslib/config.mjslib/defer.jslib/util.jspackage.jsonparser.jsraw.jstest/0-util.jstest/1-protected-test.jstest/11-regexp.jstest/15-async-configs.jstest/15-config/default.jstest/15-config/local.jstest/16-config/parser/custom-1.jstest/16-config/parser/custom-2.jstest/16-config/parser/custom-3.jstest/16-custom-parser.jstest/2-config-test.jstest/21-date.jstest/21-reentrant/default.jstest/21-reentrant/nested/default.jstest/22-binary.jstest/23-serialize.jstest/3-config/local.jstest/3-deferred-configs.jstest/5-getConfigSources.jstest/6-strict-mode.jstest/8-config-extending.jstest/9-config/default.jstest/9-raw-configs.jstest/_utils/requireUncached.jstest/_utils/requireUncached.mjstest/config/default-array-merge.jstest/makeImmutable-shared-refs.jstest/type-files/async.tstest/type-files/defer.tstest/type-files/parser.tstest/type-files/raw.tstest/type-files/util.tstest/util.jstest/x-config-ts/default.tstest/x-config-ts/local.tstest/x-deferred-configs-ts.js
💤 Files with no reviewable changes (12)
- test/_utils/requireUncached.js
- test/21-reentrant/nested/default.js
- raw.js
- test/21-reentrant/default.js
- test/15-config/local.js
- test/15-config/default.js
- test/15-async-configs.js
- test/config/default-array-merge.js
- defer.js
- async.js
- test/type-files/async.ts
- test/type-files/raw.ts
🚧 Files skipped from review as they are similar to previous changes (8)
- test/type-files/defer.ts
- test/9-config/default.js
- test/type-files/parser.ts
- test/16-config/parser/custom-1.js
- test/_utils/requireUncached.mjs
- test/23-serialize.js
- package.json
- test/makeImmutable-shared-refs.js
Summary by CodeRabbit
Breaking Changes
New Features
Chores