Skip to content

chore: replace eslint-plugin-import with eslint-plugin-import-x#7044

Open
43081j wants to merge 6 commits into
netlify:mainfrom
43081j:import-xify
Open

chore: replace eslint-plugin-import with eslint-plugin-import-x#7044
43081j wants to merge 6 commits into
netlify:mainfrom
43081j:import-xify

Conversation

@43081j
Copy link
Copy Markdown
Contributor

@43081j 43081j commented May 5, 2026

This replaces eslint-plugin-import with the much lighter and more modern eslint-plugin-import-x.

The gain here other than a smaller/faster plugin, is that modern package exports and typescript resolution is supported.

Also bumps typescript-eslint which caught a bunch of minor warnings and errors.

I'll leave comments explaining each notable change.

Notes

  • I added temporary suppressions for some default values TSESLint flagged (as their type specifies they're not optional, so the default should never be hit. but this'd be a runtime behaviour change, so i ignored for now)
  • TSESLint changed x !== undefined && x.foo to x?.foo

This replaces `eslint-plugin-import` with the much lighter and more modern `eslint-plugin-import-x`.

The gain here other than a smaller/faster plugin, is that modern package exports and typescript resolution is supported.

**Also bumps typescript-eslint** which caught a bunch of minor warnings and errors.
@43081j 43081j requested a review from a team as a code owner May 5, 2026 10:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Review Change Stack

Warning

Rate limit exceeded

@43081j has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 37 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6006f7e4-f591-4248-b77b-be4ba81e40d7

📥 Commits

Reviewing files that changed from the base of the PR and between dad92b4 and 4498597.

📒 Files selected for processing (1)
  • packages/functions-utils/src/main.ts
📝 Walkthrough

Walkthrough

This PR performs a comprehensive modernization of the build system, focusing on ESLint tooling, module import standardization, and code quality improvements. The changes migrate the import linting plugin from eslint-plugin-import to eslint-plugin-import-x, standardize module imports across the codebase to use namespace form for better ESM interoperability, consolidate type dependency paths to use the @netlify/api package, centralize the isTrustedPlugin utility function, and apply defensive programming patterns like optional chaining while removing unnecessary type casts. Over 100 files are touched, with changes ranging from configuration updates to minor import style conversions and code simplifications.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description explains motivation (lighter, faster, modern plugin with better TypeScript/export support) and notes changes from typescript-eslint bump, but lacks issue reference and is missing several required checklist items. Add issue reference (Fixes #), confirm all checklist items (tests, documentation, status checks), and clarify which default-value suppressions require follow-up discussion.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: replacing eslint-plugin-import with eslint-plugin-import-x, which aligns with the majority of changes throughout the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Linked repositories: Couldn't analyze netlify/build - clone failed: Clone operation failed: fatal: unable to access 'https://github.com/netlify/build.git/': Empty reply from server

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

const results = (
await Promise.all(
patterns.map((pattern) => {
patterns.map(async (pattern) => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is so the undefined return is still a Promise - basically to satisfy TSESLint. we can also just eslint-ignore the line

@@ -1,4 +1,5 @@
import { NetlifyConfig, NetlifyPlugin } from '../index.js'
import { NetlifyPlugin } from '../types/netlify_plugin.js'
import { NetlifyConfig } from '../types/config/netlify_config.js'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changed to remove a circular dependency, in that index.js imports this file

const { indent = false, color } = config
const stringA = indent ? indentString(string, INDENT_SIZE) : string
const stringB = String(stringA).replace(EMPTY_LINES_REGEXP, EMPTY_LINE)
const stringB = stringA.replace(EMPTY_LINES_REGEXP, EMPTY_LINE)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string is typed as being a string, so this should be fine. it is a slight behavioural change in that we were prev able to pass non-strings to this at runtime, though afaict we're the consumers of this

"exports": "./lib/index.js",
"exports": {
".": "./lib/index.js",
"./lib/types.js": "./lib/types.js"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we import this ourselves in other monorepo packages. so this is correct but we could also just re-export the types from the index

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/config/src/case.ts (1)

2-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Build configuration is silently discarded when build is absent — confirmed behavioral regression.

Commit fc92012 removed the build = Build fallback from the destructuring, introducing a critical regression. The function's documented purpose is to handle uppercase-capitalized properties, yet when a config provides only Build (e.g., in netlify.toml or defaultConfig), the value is now lost.

When build is undefined, normalizeBuildCase(undefined) returns an empty object due to its = {} default, and the provided Build value is permanently dropped. The TypeScript index signature [key: string]: unknown prevents compile-time detection when callers omit the lowercase key.

Proposed fix:

 export const normalizeConfigCase = function ({
   Build,
   build,
   ...config
 }: {
   Build: Record<string, unknown>
   build: Record<string, unknown>
   [key: string]: unknown
 }): Record<string, unknown> {
-  const buildA = normalizeBuildCase(build)
+  const buildA = normalizeBuildCase(build ?? Build)
   return { ...config, build: buildA }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/config/src/case.ts` around lines 2 - 13, normalizeConfigCase is
dropping the uppercase Build value when lowercase build is absent; restore the
original fallback by using the Build value for build before calling
normalizeBuildCase (e.g., in normalizeConfigCase use destructuring with build =
Build or compute const buildValue = build ?? Build and pass buildValue to
normalizeBuildCase), so normalizeBuildCase receives the provided Build when
build is undefined and the returned object spreads config with build:
normalizeBuildCase(buildValue).
packages/build/src/plugins_core/frameworks_api/index.ts (1)

58-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard optional logger on the error path

Line 65 calls systemLog(...) unconditionally, but systemLog is optional in CoreStepFunctionArgs. This can throw in the catch path and mask the original loadConfigFile error.

💡 Proposed fix
-const coreStep: CoreStepFunction = async function ({ buildDir, netlifyConfig, packagePath, systemLog }) {
+const coreStep: CoreStepFunction = async function ({ buildDir, netlifyConfig, packagePath, systemLog }) {
   await handleSkewProtection(buildDir, packagePath)
   let config: Partial<NetlifyConfig> | undefined

   try {
     config = await loadConfigFile(buildDir, packagePath)
   } catch (err) {
-    systemLog(`Failed to read Frameworks API: ${err.message}`)
+    systemLog?.(`Failed to read Frameworks API: ${(err as Error).message}`)

     throw new Error('An error occured while processing the platform configurarion defined by your framework')
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/build/src/plugins_core/frameworks_api/index.ts` around lines 58 -
66, The catch block in the coreStep (CoreStepFunction) calls the optional
systemLog unguarded which can throw and hide the original loadConfigFile error;
update the error path to check for existence of systemLog before invoking it
(e.g., if (systemLog) systemLog(...)) and then rethrow or handle the original
error accordingly so the original loadConfigFile failure is preserved; locate
the catch around the await loadConfigFile(...) and modify the systemLog usage
there.
🧹 Nitpick comments (3)
packages/zip-it-and-ship-it/src/runtimes/node/bundlers/zisi/list_imports.ts (1)

3-3: ⚡ Quick win

Use named import to eliminate eslint-disable suppression

The no-named-as-default-member rule flags accessing named exports as properties on default imports, which is likely a mistake. Here paperwork is a named export of precinct, confirming the suppress comment is only needed because the default import is used.

Converting to a named import removes the suppression and aligns with the import style used elsewhere in the file (e.g., tmp-promise on line 4):

♻️ Proposed refactor
-import precinct from 'precinct'
+import { paperwork } from 'precinct'
-  // eslint-disable-next-line import-x/no-named-as-default-member
-  return precinct.paperwork(path, { includeCore: false })
+  return paperwork(path, { includeCore: false })

Also applies to: 78-79

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/zip-it-and-ship-it/src/runtimes/node/bundlers/zisi/list_imports.ts`
at line 3, Replace the default import of precinct with a named import for the
paperwork export in list_imports.ts (i.e., import { paperwork } from 'precinct')
and update any usages that access paperwork via the default (precinct.paperwork)
to use the named symbol directly; then remove the now-unnecessary eslint-disable
comment that was suppressing no-named-as-default-member (previously around the
precinct.paperwork usage).
packages/build/src/plugins/child/utils.ts (1)

24-35: ⚡ Quick win

Consider adding a defensive default for deployEnvVars parameter.

The function receives deployEnvVars without a default value, and operations on it at lines 73 and 77 (.findIndex(), .push()) would crash if the value were undefined. While the type system (CoreStepFunctionArgs) currently requires this parameter and all current callers provide it, a defensive fallback improves robustness against future changes.

Suggested defensive patch
 export const getUtils = function ({
   event,
   constants: { FUNCTIONS_SRC, INTERNAL_FUNCTIONS_SRC, CACHE_DIR },
   generatedFunctions = [],
   runState,
-  deployEnvVars,
+  deployEnvVars = [],
 }: {
   event: BuildEvent
   constants: NetlifyPluginConstants
   generatedFunctions?: ReturnValue['generatedFunctions']
   runState: RunState
-  deployEnvVars: DeployEnvVarsData
+  deployEnvVars?: DeployEnvVarsData
 }): NetlifyPluginUtils {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/build/src/plugins/child/utils.ts` around lines 24 - 35, Add a
defensive default for the deployEnvVars parameter in getUtils so methods like
.findIndex() and .push() won't throw if undefined: in the getUtils parameter
destructuring set deployEnvVars = [] (i.e. deployEnvVars: DeployEnvVarsData =
[]) and ensure any usages (the code that calls .findIndex() and .push()) operate
on that array; update related type annotation if needed to keep TS happy.
packages/js-client/package.json (1)

6-9: 💤 Low value

Consider adding an explicit types conditional to the new subpath export.

Without it, TypeScript relies on automatically finding ./lib/types.d.ts via .js.d.ts substitution, which only works with moduleResolution: node16, nodenext, or bundler. Consumers with older resolution settings (node) cannot resolve the subpath at all. An explicit types entry makes the contract unambiguous and is the recommended practice for published packages.

♻️ Proposed improvement
  "exports": {
    ".": "./lib/index.js",
-   "./lib/types.js": "./lib/types.js"
+   "./lib/types.js": {
+     "types": "./lib/types.d.ts",
+     "default": "./lib/types.js"
+   }
  },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/js-client/package.json` around lines 6 - 9, The "./lib/types.js"
export should include an explicit types conditional so TypeScript can resolve
the declaration for consumers using older moduleResolution modes: change the
exports entry for "./lib/types.js" from a string to an export object that
includes a "types" key pointing to "./lib/types.d.ts" (in addition to keeping
any existing "import"/"require" or default fields if needed), ensuring the
package.json "exports" map unambiguously exposes the type declarations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/build/src/error/parse/parse.ts`:
- Line 29: The code currently destructures location from errorInfo and passes it
into getLocationInfo/formatter paths that expect an object; guard against
location being undefined by checking if location is present before calling
getLocationInfo or any plugin/location formatter (e.g., in the code paths
referencing getLocationInfo, plugin, and tsConfig), and if absent call the
generic non-location formatter or return a safe default (empty message/object)
so the error reporting path never receives undefined and cannot crash.

In `@packages/config/src/mutations/config_prop_name.ts`:
- Line 11: The normalization logic for numeric keys is wrong because
Number.isInteger(key) will always be false for string-typed keys; update the
computation of normalizedKey (the line that uses Number.isInteger(key) and
DYNAMIC_OBJECT_PROPS) to coerce or validate the string key as a number before
checking integerness (e.g., use Number.isInteger(Number(key)) or a /^\d+$/ test)
so numeric string indices like '0' normalize to '*' when appropriate while
keeping the DYNAMIC_OBJECT_PROPS.has(propName) branch unchanged.

In `@packages/edge-bundler/node/formats/tarball.ts`:
- Line 215: finalizeTarballBundle currently awaits bundleDir.cleanup(), letting
cleanup rejections bubble and fail the overall build; wrap the call to
bundleDir.cleanup() in a try/catch inside finalizeTarballBundle/finalizeBundle
so that cleanup failures are caught, logged as a non-fatal warning (use the
existing logger, e.g., processLogger.warn or console.warn) and do not rethrow;
ensure the tarball write and hash steps (the code around the writeFile/hash at
line ~213) remain untouched and that the catch only demotes the error instead of
causing the function to reject.

In `@packages/redirect-parser/src/all.ts`:
- Around line 12-15: Restore runtime-safe defaults on the exported function
parseAllRedirects: ensure its parameters have safe default values (e.g.,
redirectsFiles defaults to [], netlifyConfigPath to an empty string,
configRedirects to {} and minimal to false) in the function signature or
immediately assign those defaults at the top of parseAllRedirects so callers
that omit or pass partial values won't hit runtime errors when using
redirectsFiles.map or accessing configRedirects.

In `@packages/zip-it-and-ship-it/src/runtimes/node/index.ts`:
- Line 97: The bundler output field finalMainFile can be undefined and later
used by zipNodeJs and extname, so ensure finalMainFile always has a safe
fallback string before downstream use (e.g., default to "index.js" or another
known entry). Update the assignment where finalMainFile is set (the object
property mainFile: finalMainFile) to coalesce with a fallback (finalMainFile ??
'index.js') or normalize it immediately after bundler output, and ensure any
subsequent calls in zipNodeJs and usages of extname(...) operate on this
guaranteed non-undefined string.

In `@packages/zip-it-and-ship-it/tests/main.test.ts`:
- Around line 1952-1955: The destructure of args sets { cwd, env: environment }
= {} but environment can be undefined and is later dereferenced
(environment.CARGO_TARGET_DIR) in the cargo branch; change the destructure to
provide a defensive default for env (e.g., { cwd, env: environment = {} } = {})
so environment is always an object, then keep the existing usage of
environment.CARGO_TARGET_DIR in the cargo branch (refer to the const binding of
rootCommand, args and the cargo branch that computes directory).

---

Outside diff comments:
In `@packages/build/src/plugins_core/frameworks_api/index.ts`:
- Around line 58-66: The catch block in the coreStep (CoreStepFunction) calls
the optional systemLog unguarded which can throw and hide the original
loadConfigFile error; update the error path to check for existence of systemLog
before invoking it (e.g., if (systemLog) systemLog(...)) and then rethrow or
handle the original error accordingly so the original loadConfigFile failure is
preserved; locate the catch around the await loadConfigFile(...) and modify the
systemLog usage there.

In `@packages/config/src/case.ts`:
- Around line 2-13: normalizeConfigCase is dropping the uppercase Build value
when lowercase build is absent; restore the original fallback by using the Build
value for build before calling normalizeBuildCase (e.g., in normalizeConfigCase
use destructuring with build = Build or compute const buildValue = build ??
Build and pass buildValue to normalizeBuildCase), so normalizeBuildCase receives
the provided Build when build is undefined and the returned object spreads
config with build: normalizeBuildCase(buildValue).

---

Nitpick comments:
In `@packages/build/src/plugins/child/utils.ts`:
- Around line 24-35: Add a defensive default for the deployEnvVars parameter in
getUtils so methods like .findIndex() and .push() won't throw if undefined: in
the getUtils parameter destructuring set deployEnvVars = [] (i.e. deployEnvVars:
DeployEnvVarsData = []) and ensure any usages (the code that calls .findIndex()
and .push()) operate on that array; update related type annotation if needed to
keep TS happy.

In `@packages/js-client/package.json`:
- Around line 6-9: The "./lib/types.js" export should include an explicit types
conditional so TypeScript can resolve the declaration for consumers using older
moduleResolution modes: change the exports entry for "./lib/types.js" from a
string to an export object that includes a "types" key pointing to
"./lib/types.d.ts" (in addition to keeping any existing "import"/"require" or
default fields if needed), ensuring the package.json "exports" map unambiguously
exposes the type declarations.

In `@packages/zip-it-and-ship-it/src/runtimes/node/bundlers/zisi/list_imports.ts`:
- Line 3: Replace the default import of precinct with a named import for the
paperwork export in list_imports.ts (i.e., import { paperwork } from 'precinct')
and update any usages that access paperwork via the default (precinct.paperwork)
to use the named symbol directly; then remove the now-unnecessary eslint-disable
comment that was suppressing no-named-as-default-member (previously around the
precinct.paperwork usage).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c05cc19e-61cd-4b68-94d0-ab20669e3d75

📥 Commits

Reviewing files that changed from the base of the PR and between ed5ab4d and fc92012.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (89)
  • eslint.config.js
  • package.json
  • packages/build-info/src/file-system.ts
  • packages/build-info/src/node/file-system.ts
  • packages/build-info/src/workspaces/get-workspace-packages.ts
  • packages/build-info/tests/mock-file-system.ts
  • packages/build/src/core/main.ts
  • packages/build/src/core/types.ts
  • packages/build/src/core/user_node_version.js
  • packages/build/src/error/parse/parse.ts
  • packages/build/src/error/types.ts
  • packages/build/src/log/logger.ts
  • packages/build/src/log/messages/compatibility.ts
  • packages/build/src/plugins/child/utils.ts
  • packages/build/src/plugins/compatibility.ts
  • packages/build/src/plugins/expected_version.ts
  • packages/build/src/plugins/internal.ts
  • packages/build/src/plugins/list.ts
  • packages/build/src/plugins/node_version.ts
  • packages/build/src/plugins/options.ts
  • packages/build/src/plugins/plugin_conditions.ts
  • packages/build/src/plugins/spawn.ts
  • packages/build/src/plugins_core/frameworks_api/index.ts
  • packages/build/src/plugins_core/functions/zisi.ts
  • packages/build/src/plugins_core/types.ts
  • packages/build/src/status/validations.ts
  • packages/build/src/steps/error.ts
  • packages/build/src/steps/plugin.js
  • packages/build/src/time/main.ts
  • packages/build/src/utils/semver.js
  • packages/build/tests/blobs_upload/tests.js
  • packages/build/tests/core/tests.js
  • packages/build/tests/edge_functions/tests.js
  • packages/build/tests/frameworks_api/tests.js
  • packages/build/tests/functions/tests.js
  • packages/build/tests/mutate_save/tests.js
  • packages/build/tests/plugins/tests.js
  • packages/build/tests/unit/logger/tests.js
  • packages/config/src/case.ts
  • packages/config/src/env/main.ts
  • packages/config/src/mutations/config_prop_name.ts
  • packages/config/src/validate/validations.js
  • packages/edge-bundler/node/bridge.test.ts
  • packages/edge-bundler/node/bridge.ts
  • packages/edge-bundler/node/bundler.test.ts
  • packages/edge-bundler/node/bundler.ts
  • packages/edge-bundler/node/config.test.ts
  • packages/edge-bundler/node/config.ts
  • packages/edge-bundler/node/deploy_config.test.ts
  • packages/edge-bundler/node/downloader.test.ts
  • packages/edge-bundler/node/downloader.ts
  • packages/edge-bundler/node/formats/tarball.ts
  • packages/edge-bundler/node/import_map.test.ts
  • packages/edge-bundler/node/main.test.ts
  • packages/edge-bundler/node/npm_dependencies.ts
  • packages/edge-bundler/node/package_json.test.ts
  • packages/edge-bundler/node/server/server.test.ts
  • packages/edge-bundler/node/stage_2.test.ts
  • packages/edge-bundler/node/types.test.ts
  • packages/edge-bundler/node/validation/manifest/index.ts
  • packages/edge-bundler/test/integration/test.js
  • packages/edge-bundler/test/util.ts
  • packages/functions-utils/src/main.ts
  • packages/js-client/package.json
  • packages/opentelemetry-sdk-setup/src/bin.ts
  • packages/redirect-parser/src/all.ts
  • packages/run-utils/tests/main.test.ts
  • packages/testing/src/server.ts
  • packages/testing/src/tcp_server.ts
  • packages/zip-it-and-ship-it/src/deps.d.ts
  • packages/zip-it-and-ship-it/src/runtimes/index.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/bundlers/esbuild/bundler.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/bundlers/esbuild/index.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/bundlers/esbuild/plugin_native_modules.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/bundlers/esbuild/src_files.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/bundlers/zisi/list_imports.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/bundlers/zisi/nested.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/in_source_config/properties/schedule.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/index.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/parser/exports.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/parser/helpers.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/parser/imports.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/parser/index.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/utils/included_files.ts
  • packages/zip-it-and-ship-it/src/runtimes/node/utils/node_version.ts
  • packages/zip-it-and-ship-it/src/runtimes/rust/builder.ts
  • packages/zip-it-and-ship-it/tests/main.test.ts
  • packages/zip-it-and-ship-it/tests/telemetry.test.ts
  • packages/zip-it-and-ship-it/tests/v2api.test.ts
💤 Files with no reviewable changes (1)
  • packages/zip-it-and-ship-it/src/deps.d.ts

Comment thread packages/build/src/error/parse/parse.ts Outdated
// In those cases, we replace it by "*".
return keys.reduce((propName, key) => {
const normalizedKey = Number.isInteger(key) || DYNAMIC_OBJECT_PROPS.has(propName) ? '*' : String(key)
const normalizedKey = Number.isInteger(key) || DYNAMIC_OBJECT_PROPS.has(propName) ? '*' : key
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Confirm current implementation and call sites.
rg -n -C2 '\bgetPropName\b|\bNumber\.isInteger\s*\(' --type ts

# 2) Check for tests that cover numeric-string keys.
rg -n -C2 "getPropName\\(\\[.*['\"][0-9]+['\"].*\\]\\)" --type ts

# 3) Reproduce JS semantics proving the check fails for string indices.
python - <<'PY'
print("Number.isInteger equivalent on '0' in JS semantics should be false; Python sanity for string-digit:")
key = '0'
is_array_index_current = False  # mirrors Number.isInteger('0') result
is_array_index_fixed = key.isdigit()
print("current_check_for_'0':", is_array_index_current)
print("fixed_check_for_'0':", is_array_index_fixed)
PY

Repository: netlify/build

Length of output: 2958


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find test files for config_prop_name
echo "=== Looking for tests related to config_prop_name ==="
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "getPropName" 2>/dev/null || echo "No test matches found"

# Search for any file that imports getPropName
echo -e "\n=== Files importing getPropName ==="
rg -l "getPropName" --type ts

# Show context around getPropName function definition
echo -e "\n=== Full function definition ==="
cat -n packages/config/src/mutations/config_prop_name.ts

Repository: netlify/build

Length of output: 1055


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for test files in the config package
echo "=== Test files in config package ==="
find packages/config -name "*.test.ts" -o -name "*.spec.ts" 2>/dev/null

# Search for mutations tests
echo -e "\n=== Testing mutations directory ==="
ls -la packages/config/src/mutations/ 2>/dev/null || echo "No mutations directory"

# Find all test files in config
echo -e "\n=== All ts/js files in config/src that might have tests ==="
find packages/config -type f \( -name "*.test.ts" -o -name "*.spec.ts" \) 2>/dev/null

Repository: netlify/build

Length of output: 495


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look at how getPropName is used in the codebase
echo "=== All usages of getPropName ==="
rg "getPropName" -A2 -B2 --type ts

# Check the DYNAMIC_OBJECT_PROPS constant
echo -e "\n=== DYNAMIC_OBJECT_PROPS definition ==="
rg "DYNAMIC_OBJECT_PROPS" -B2 -A5 --type ts

Repository: netlify/build

Length of output: 1977


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check apply.js and update.js for getPropName usage
echo "=== apply.js ==="
cat -n packages/config/src/mutations/apply.js | head -50

echo -e "\n=== Looking for getPropName in apply.js ==="
grep -n "getPropName\|propName" packages/config/src/mutations/apply.js || echo "Not found"

echo -e "\n=== update.js ==="
cat -n packages/config/src/mutations/update.js | head -50

echo -e "\n=== Looking for getPropName in update.js ==="
grep -n "getPropName" packages/config/src/mutations/update.js || echo "Not found"

Repository: netlify/build

Length of output: 5046


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check blueprints repo for getPropName usage
echo "=== Searching blueprints for getPropName ==="
rg "getPropName" --type ts --type js 2>/dev/null || echo "Not found in blueprints"

# Also check for config imports from config package
echo -e "\n=== Imports from `@netlify/build`:config ==="
rg "from.*@netlify/build.*config|from.*netlify/config" --type ts --type js 2>/dev/null || echo "Not found"

Repository: netlify/blueprints

Length of output: 185


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for where configMutations are created or populated
echo "=== Where configMutations are created/used ==="
rg "configMutations|keys.*:.*value.*:" --type ts --type js -B2 -A2 | head -100

# Look for MUTABLE_PROPS which defines valid mutation targets
echo -e "\n=== MUTABLE_PROPS definition ==="
rg "MUTABLE_PROPS" --type ts --type js -B2 -A10 | head -50

# Search for array index mutations specifically
echo -e "\n=== Array-like property modifications ==="
rg "\\[\s*['\"]\\d+['\"]\\s*\\]|functions\\.\\[|array.*index" --type ts --type js | head -20

Repository: netlify/build

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the full MUTABLE_PROPS definition in apply.js
echo "=== Full MUTABLE_PROPS from apply.js ==="
grep -n "MUTABLE_PROPS" packages/config/src/mutations/apply.js

# Get more context around where mutations are applied
echo -e "\n=== Full apply.js file ==="
wc -l packages/config/src/mutations/apply.js

echo -e "\n=== Content of apply.js ==="
cat packages/config/src/mutations/apply.js

Repository: netlify/build

Length of output: 4027


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check where configMutations originate or are created
echo "=== Search for where mutations with 'keys' are created ==="
rg "keys\s*:" -B2 -A2 --type ts --type js | head -60

# Look for any array property mutations
echo -e "\n=== Look for array-like config properties ==="
rg "\\[\s*['\"]\\d+['\"]\\s*\\]|\\[0\\]|\\[1\\]" --type ts --type js | head -20

# Check the setProp utility that's called with keys
echo -e "\n=== Check setProp implementation ==="
find . -name "set.js" -o -name "set.ts" 2>/dev/null | xargs cat 2>/dev/null || echo "setProp not found"

Repository: netlify/build

Length of output: 8403


Number.isInteger(key) always returns false for string arguments—numeric indices won't normalize to '*'

Line 11 uses Number.isInteger(key) to detect array indices, but since key is typed as string, this check will always fail. For example, Number.isInteger('0') returns false, so numeric string indices like '0', '1' won't be normalized to '*' as intended by the comment.

The fix is correct:

Proposed fix
 export const getPropName = function (keys: string[]) {
   // Some properties are user-defined, i.e. we need to replace them with a "*" token
   // Check if a property name is dynamic, such as `functions.{functionName}`, or
   // is an array index.
   // In those cases, we replace it by "*".
   return keys.reduce((propName, key) => {
-    const normalizedKey = Number.isInteger(key) || DYNAMIC_OBJECT_PROPS.has(propName) ? '*' : key
+    const isArrayIndex = /^\d+$/.test(key)
+    const normalizedKey = isArrayIndex || DYNAMIC_OBJECT_PROPS.has(propName) ? '*' : key
     return propName === '' ? normalizedKey : `${propName}.${normalizedKey}`
   }, '')
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const normalizedKey = Number.isInteger(key) || DYNAMIC_OBJECT_PROPS.has(propName) ? '*' : key
export const getPropName = function (keys: string[]) {
// Some properties are user-defined, i.e. we need to replace them with a "*" token
// Check if a property name is dynamic, such as `functions.{functionName}`, or
// is an array index.
// In those cases, we replace it by "*".
return keys.reduce((propName, key) => {
const isArrayIndex = /^\d+$/.test(key)
const normalizedKey = isArrayIndex || DYNAMIC_OBJECT_PROPS.has(propName) ? '*' : key
return propName === '' ? normalizedKey : `${propName}.${normalizedKey}`
}, '')
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/config/src/mutations/config_prop_name.ts` at line 11, The
normalization logic for numeric keys is wrong because Number.isInteger(key) will
always be false for string-typed keys; update the computation of normalizedKey
(the line that uses Number.isInteger(key) and DYNAMIC_OBJECT_PROPS) to coerce or
validate the string key as a number before checking integerness (e.g., use
Number.isInteger(Number(key)) or a /^\d+$/ test) so numeric string indices like
'0' normalize to '*' when appropriate while keeping the
DYNAMIC_OBJECT_PROPS.has(propName) branch unchanged.

Comment thread packages/edge-bundler/node/formats/tarball.ts Outdated
Comment thread packages/redirect-parser/src/all.ts Outdated
Comment thread packages/zip-it-and-ship-it/src/runtimes/node/index.ts Outdated
Comment thread packages/zip-it-and-ship-it/tests/main.test.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant