Skip to content

refactor(build): remove micro-utility dependencies#7095

Open
43081j wants to merge 2 commits into
netlify:mainfrom
43081j:jg/the-purge-0
Open

refactor(build): remove micro-utility dependencies#7095
43081j wants to merge 2 commits into
netlify:mainfrom
43081j:jg/the-purge-0

Conversation

@43081j

@43081j 43081j commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

This removes 9 dependencies as follows:

  • ansi-escapes was only used for ANSI links, which we can inline instead
  • clean-stack was mostly made redundant by the fact we already do our own filtering beforehand. If we inline the only
    remaining case (node internals), it becomes one extra small RegExp
  • filter-obj can be achieved by a filtered Object.entries
  • is-plain-obj can be inlined as our own util
  • p-event can be replaced with once (from node:events)
  • p-filter was never used with a concurrency limit, so can be replaced by a regular map of async tasks
  • p-locate was never used with a concurrency limit, so can be replaced by a regular for...of
  • p-map can be replaced by a basic pool of for...of on a shared iterator
  • path-exists can be replaced by fs.access

Reasons:

  • Reducing the supply chain footprint is good for security
  • In most cases, we use a fraction of the provided functionality, so are wastefully pulling in a larger dependency than
    needed
  • If we keep doing this, size of the build package will reduce over time (baby steps but still progress)

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63cd428f-cdfa-400a-ba10-3af924f32d60

📥 Commits

Reviewing files that changed from the base of the PR and between 5741b2c and 11e0485.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (46)
  • eslint_temporary_suppressions.js
  • packages/build/package.json
  • packages/build/src/core/bin.js
  • packages/build/src/core/constants.ts
  • packages/build/src/core/dry.js
  • packages/build/src/core/missing_side_file.js
  • packages/build/src/env/changes.js
  • packages/build/src/env/main.ts
  • packages/build/src/env/metadata.js
  • packages/build/src/error/api.js
  • packages/build/src/error/handle.ts
  • packages/build/src/error/parse/clean_stack.js
  • packages/build/src/install/main.js
  • packages/build/src/install/missing.js
  • packages/build/src/log/messages/core.ts
  • packages/build/src/log/messages/mutations.js
  • packages/build/src/plugins/child/diff.js
  • packages/build/src/plugins/child/load.ts
  • packages/build/src/plugins/child/status.ts
  • packages/build/src/plugins/compatibility.ts
  • packages/build/src/plugins/ipc.js
  • packages/build/src/plugins/list.ts
  • packages/build/src/plugins/manifest/validate.js
  • packages/build/src/plugins_core/blobs_upload/index.ts
  • packages/build/src/plugins_core/db_setup/migrations.ts
  • packages/build/src/plugins_core/db_setup/utils.ts
  • packages/build/src/plugins_core/deploy/buildbot_client.ts
  • packages/build/src/plugins_core/dev_blobs_upload/index.ts
  • packages/build/src/plugins_core/edge_functions/index.ts
  • packages/build/src/plugins_core/frameworks_api/util.ts
  • packages/build/src/plugins_core/functions/index.ts
  • packages/build/src/plugins_core/functions_install/index.js
  • packages/build/src/steps/update_config.js
  • packages/build/src/utils/blobs.ts
  • packages/build/src/utils/is_plain_object.ts
  • packages/build/src/utils/omit.ts
  • packages/build/src/utils/path_exists.ts
  • packages/build/src/utils/remove_falsy.ts
  • packages/build/tests/constants/fixtures/functions_src_missing/plugin.js
  • packages/build/tests/constants/fixtures/publish_missing/plugin.js
  • packages/build/tests/core/tests.js
  • packages/build/tests/edge_functions/fixtures/src_missing/plugin.js
  • packages/build/tests/edge_functions/tests.js
  • packages/build/tests/functions/tests.js
  • packages/build/tests/install/tests.js
  • packages/build/tests/mutate_save/tests.js
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • netlify/blueprints (manual)
💤 Files with no reviewable changes (1)
  • packages/build/package.json
✅ Files skipped from review due to trivial changes (9)
  • packages/build/tests/constants/fixtures/publish_missing/plugin.js
  • packages/build/src/plugins_core/db_setup/utils.ts
  • packages/build/src/plugins_core/edge_functions/index.ts
  • packages/build/src/plugins_core/functions_install/index.js
  • packages/build/src/core/missing_side_file.js
  • packages/build/src/plugins/child/status.ts
  • eslint_temporary_suppressions.js
  • packages/build/src/plugins/manifest/validate.js
  • packages/build/tests/functions/tests.js
🚧 Files skipped from review as they are similar to previous changes (36)
  • packages/build/src/utils/is_plain_object.ts
  • packages/build/src/error/handle.ts
  • packages/build/src/install/missing.js
  • packages/build/tests/constants/fixtures/functions_src_missing/plugin.js
  • packages/build/src/install/main.js
  • packages/build/src/plugins_core/db_setup/migrations.ts
  • packages/build/src/utils/remove_falsy.ts
  • packages/build/tests/edge_functions/tests.js
  • packages/build/src/utils/path_exists.ts
  • packages/build/src/log/messages/mutations.js
  • packages/build/src/plugins_core/functions/index.ts
  • packages/build/src/utils/blobs.ts
  • packages/build/tests/edge_functions/fixtures/src_missing/plugin.js
  • packages/build/src/plugins_core/frameworks_api/util.ts
  • packages/build/src/error/api.js
  • packages/build/tests/mutate_save/tests.js
  • packages/build/src/plugins/child/diff.js
  • packages/build/tests/install/tests.js
  • packages/build/src/core/bin.js
  • packages/build/src/plugins_core/deploy/buildbot_client.ts
  • packages/build/src/utils/omit.ts
  • packages/build/src/log/messages/core.ts
  • packages/build/src/plugins_core/dev_blobs_upload/index.ts
  • packages/build/tests/core/tests.js
  • packages/build/src/steps/update_config.js
  • packages/build/src/env/metadata.js
  • packages/build/src/env/changes.js
  • packages/build/src/plugins_core/blobs_upload/index.ts
  • packages/build/src/plugins/ipc.js
  • packages/build/src/core/constants.ts
  • packages/build/src/error/parse/clean_stack.js
  • packages/build/src/env/main.ts
  • packages/build/src/core/dry.js
  • packages/build/src/plugins/compatibility.ts
  • packages/build/src/plugins/list.ts
  • packages/build/src/plugins/child/load.ts

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of build and deploy checks, including file-existence handling and plugin compatibility detection.
    • Enhanced error and stack output to better clean up and present messages in the terminal.
  • Chores

    • Reduced reliance on several external helpers by consolidating common utility behavior.
    • Updated tests and internal validations to match the new implementation paths.

Walkthrough

Adds local isPlainObject and pathExists helpers, reworks object filtering and async control flow to use native constructs, removes several package dependencies, updates stack cleaning, and switches tests and fixtures to synchronous filesystem checks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • pieh
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the dependency removals, but it does not follow the required template or include the issue reference and checklist items. Add the template sections, include Fixes #<issue_number>, and complete the checklist items for tests, docs, and status checks.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: removing small build dependencies.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
packages/build/src/log/messages/mutations.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.14][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

packages/build/src/plugins/child/diff.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.31][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

packages/build/src/plugins/child/load.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.26][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

  • 28 others

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.

@43081j 43081j marked this pull request as ready for review June 25, 2026 13:20
@43081j 43081j requested a review from a team as a code owner June 25, 2026 13:20

@coderabbitai coderabbitai Bot left a comment

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.

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 (1)
packages/build/src/utils/blobs.ts (1)

139-147: 🩺 Stability & Availability | 🟡 Minor

Update the return type annotation for data to remove the ArrayBuffer generic constraint.

The function returns Buffer<ArrayBuffer>, but readFile returns Buffer which is typed as Buffer<ArrayBufferLike> in @types/node v22+. Since ArrayBufferLike is a wider union (including SharedArrayBuffer) and TypeScript generic parameters are invariant, Buffer<ArrayBufferLike> is not assignable to the narrower Buffer<ArrayBuffer>.

Update the return type to just Buffer to allow the inferred type from readFile.

Current code ```typescript ): Promise<{ data: Buffer; metadata: Record }> => { ```

Change to:

): Promise<{ data: Buffer; metadata: Record<string, string> }> => {
🤖 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/utils/blobs.ts` around lines 139 - 147, The return type on
the blob-reading helper is too narrow because it declares data as
Buffer<ArrayBuffer>, which conflicts with the type returned by readFile in
modern `@types/node`. Update the Promise return annotation in the blob utility
function that reads contentPath and metadataPath so data is just Buffer, keeping
metadata unchanged, and let the inferred type from readFile flow through without
the ArrayBuffer generic constraint.
🧹 Nitpick comments (1)
packages/build/src/plugins/child/diff.js (1)

5-6: 🎯 Functional Correctness | 🔵 Trivial

isPlainObject semantics diverge from is-plain-obj but are safe for this context

The internal helper omits Symbol.toStringTag and Symbol.iterator checks found in is-plain-obj v4. This is acceptable: netlifyConfig is derived from JSON/YAML (which lack symbols), and the validation paths handle standard POJOs. The simplified logic reduces dependencies without impacting correctness for these inputs.

🤖 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/diff.js` around lines 5 - 6, The concern is
the simplified isPlainObject behavior differs from is-plain-obj, but this path
is safe because diff.js only validates netlifyConfig coming from JSON/YAML
inputs. Keep using the internal isPlainObject helper in the child diff plugin
and do not replace it with the external package; ensure the validation logic
continues to rely on standard POJO checks only, since the existing netlifyConfig
handling already covers the intended inputs.
🤖 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/plugins_core/blobs_upload/index.ts`:
- Around line 60-67: The shared-iterator worker loop in blobs_upload/index.ts
keeps pulling items from queue even after one upload fails, because Promise.all
rejects but the other async workers continue running. Update the worker logic
around the queue consumption and blobStore.set path so that the first caught
error flips a shared failure flag and prevents any further iterations from
reading new entries; reference the Promise.all worker block and the queue
iterator to gate new pulls once an error is seen.

In `@packages/build/src/plugins_core/dev_blobs_upload/index.ts`:
- Around line 63-73: The dev blob upload worker pool can keep processing after
one worker fails, because the shared queue iterator in dev_blobs_upload/index.ts
is still consumed by other async workers spawned in the Promise.all block.
Update the worker loop around the queue iteration in the upload routine to use a
shared stop flag that is set on first rejection, and check it before pulling the
next item from the queue so no further blobs are uploaded after a failure. Use
the existing worker-pool logic in this block (the queue, Promise.all, and the
per-item upload loop) as the place to add the early-exit behavior.

In `@packages/build/src/plugins/ipc.js`:
- Around line 53-58: Add an error listener to getEventFromChild so child_process
errors are handled alongside message and exit. Update the cleanup logic and
event wiring in getEventFromChild to register an error handler that rejects or
otherwise settles the promise, and make sure it is removed in cleanup with the
existing onMessage and onExit listeners.

---

Outside diff comments:
In `@packages/build/src/utils/blobs.ts`:
- Around line 139-147: The return type on the blob-reading helper is too narrow
because it declares data as Buffer<ArrayBuffer>, which conflicts with the type
returned by readFile in modern `@types/node`. Update the Promise return annotation
in the blob utility function that reads contentPath and metadataPath so data is
just Buffer, keeping metadata unchanged, and let the inferred type from readFile
flow through without the ArrayBuffer generic constraint.

---

Nitpick comments:
In `@packages/build/src/plugins/child/diff.js`:
- Around line 5-6: The concern is the simplified isPlainObject behavior differs
from is-plain-obj, but this path is safe because diff.js only validates
netlifyConfig coming from JSON/YAML inputs. Keep using the internal
isPlainObject helper in the child diff plugin and do not replace it with the
external package; ensure the validation logic continues to rely on standard POJO
checks only, since the existing netlifyConfig handling already covers the
intended inputs.
🪄 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: c42c6848-b4b7-49b6-83fd-d2f9dedf84d5

📥 Commits

Reviewing files that changed from the base of the PR and between 96b864c and 5741b2c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (46)
  • eslint_temporary_suppressions.js
  • packages/build/package.json
  • packages/build/src/core/bin.js
  • packages/build/src/core/constants.ts
  • packages/build/src/core/dry.js
  • packages/build/src/core/missing_side_file.js
  • packages/build/src/env/changes.js
  • packages/build/src/env/main.ts
  • packages/build/src/env/metadata.js
  • packages/build/src/error/api.js
  • packages/build/src/error/handle.ts
  • packages/build/src/error/parse/clean_stack.js
  • packages/build/src/install/main.js
  • packages/build/src/install/missing.js
  • packages/build/src/log/messages/core.ts
  • packages/build/src/log/messages/mutations.js
  • packages/build/src/plugins/child/diff.js
  • packages/build/src/plugins/child/load.ts
  • packages/build/src/plugins/child/status.ts
  • packages/build/src/plugins/compatibility.ts
  • packages/build/src/plugins/ipc.js
  • packages/build/src/plugins/list.ts
  • packages/build/src/plugins/manifest/validate.js
  • packages/build/src/plugins_core/blobs_upload/index.ts
  • packages/build/src/plugins_core/db_setup/migrations.ts
  • packages/build/src/plugins_core/db_setup/utils.ts
  • packages/build/src/plugins_core/deploy/buildbot_client.ts
  • packages/build/src/plugins_core/dev_blobs_upload/index.ts
  • packages/build/src/plugins_core/edge_functions/index.ts
  • packages/build/src/plugins_core/frameworks_api/util.ts
  • packages/build/src/plugins_core/functions/index.ts
  • packages/build/src/plugins_core/functions_install/index.js
  • packages/build/src/steps/update_config.js
  • packages/build/src/utils/blobs.ts
  • packages/build/src/utils/is_plain_object.ts
  • packages/build/src/utils/omit.ts
  • packages/build/src/utils/path_exists.ts
  • packages/build/src/utils/remove_falsy.ts
  • packages/build/tests/constants/fixtures/functions_src_missing/plugin.js
  • packages/build/tests/constants/fixtures/publish_missing/plugin.js
  • packages/build/tests/core/tests.js
  • packages/build/tests/edge_functions/fixtures/src_missing/plugin.js
  • packages/build/tests/edge_functions/tests.js
  • packages/build/tests/functions/tests.js
  • packages/build/tests/install/tests.js
  • packages/build/tests/mutate_save/tests.js
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • netlify/blueprints (manual)
💤 Files with no reviewable changes (1)
  • packages/build/package.json

Comment thread packages/build/src/plugins_core/blobs_upload/index.ts
Comment on lines +63 to +73
const queue = blobsToUpload[Symbol.iterator]()
await Promise.all(
Array.from({ length: 10 }, async () => {
for (const { key, contentPath, metadataPath } of queue) {
if (debug && !quiet) {
log(logs, `- Uploading blob ${key}`, { indent: true })
}
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
await blobStore.set(key, new Blob([data]), { metadata })
}
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
// `data` is a `Buffer`, typed by @types/node as `Buffer<ArrayBufferLike>`, which TS
// rejects as a `BlobPart`; the runtime value is a valid blob part.
await blobStore.set(key, new Blob([data as BlobPart]), { metadata })
},
{ concurrency: 10 },
}),

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Stop dev upload workers after the first failure.

This worker pool has the same failure mode: once one worker rejects, Promise.all returns, but other workers can continue pulling from queue and uploading in the background. Add the same stop flag before consuming more items.

Proposed fix
   try {
+    let shouldStop = false
     const queue = blobsToUpload[Symbol.iterator]()
     await Promise.all(
       Array.from({ length: 10 }, async () => {
-        for (const { key, contentPath, metadataPath } of queue) {
-          if (debug && !quiet) {
-            log(logs, `- Uploading blob ${key}`, { indent: true })
-          }
-          const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
-          await blobStore.set(key, new Blob([data]), { metadata })
+        while (!shouldStop) {
+          const next = queue.next()
+          if (next.done) {
+            return
+          }
+
+          const { key, contentPath, metadataPath } = next.value
+          try {
+            if (debug && !quiet) {
+              log(logs, `- Uploading blob ${key}`, { indent: true })
+            }
+            const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
+            await blobStore.set(key, new Blob([data]), { metadata })
+          } catch (error) {
+            shouldStop = true
+            throw error
+          }
         }
       }),
     )
📝 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 queue = blobsToUpload[Symbol.iterator]()
await Promise.all(
Array.from({ length: 10 }, async () => {
for (const { key, contentPath, metadataPath } of queue) {
if (debug && !quiet) {
log(logs, `- Uploading blob ${key}`, { indent: true })
}
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
await blobStore.set(key, new Blob([data]), { metadata })
}
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
// `data` is a `Buffer`, typed by @types/node as `Buffer<ArrayBufferLike>`, which TS
// rejects as a `BlobPart`; the runtime value is a valid blob part.
await blobStore.set(key, new Blob([data as BlobPart]), { metadata })
},
{ concurrency: 10 },
}),
let shouldStop = false
const queue = blobsToUpload[Symbol.iterator]()
await Promise.all(
Array.from({ length: 10 }, async () => {
while (!shouldStop) {
const next = queue.next()
if (next.done) {
return
}
const { key, contentPath, metadataPath } = next.value
try {
if (debug && !quiet) {
log(logs, `- Uploading blob ${key}`, { indent: true })
}
const { data, metadata } = await getFileWithMetadata(key, contentPath, metadataPath)
await blobStore.set(key, new Blob([data]), { metadata })
} catch (error) {
shouldStop = true
throw error
}
}
}),
🤖 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/dev_blobs_upload/index.ts` around lines 63 -
73, The dev blob upload worker pool can keep processing after one worker fails,
because the shared queue iterator in dev_blobs_upload/index.ts is still consumed
by other async workers spawned in the Promise.all block. Update the worker loop
around the queue iteration in the upload routine to use a shared stop flag that
is set on first rejection, and check it before pulling the next item from the
queue so no further blobs are uploaded after a failure. Use the existing
worker-pool logic in this block (the queue, Promise.all, and the per-item upload
loop) as the place to add the early-exit behavior.

Comment thread packages/build/src/plugins/ipc.js
This removes 8 dependencies as follows:

- `ansi-escapes` was only used for ANSI links, which we can inline instead
- `clean-stack` was mostly made redundant by the fact we already do our own filtering beforehand. If we inline the only
  remaining case (node internals), it becomes one extra small RegExp
- `filter-obj` can be achieved by a filtered `Object.entries`
- `is-plain-obj` can be inlined as our own util
- `p-event` can be replaced with `once` (from `node:events`)
- `p-filter` was never used with a concurrency limit, so can be replaced by a regular `map` of async tasks
- `p-locate` was never used with a concurrency limit, so can be replaced by a regular `for...of`
- `p-map` can be replaced by a basic pool of `for...of` on a shared iterator

Reasons:

- Reducing the supply chain footprint is good for security
- In most cases, we use a fraction of the provided functionality, so are wastefully pulling in a larger dependency than
  needed
- If we keep doing this, size of the build package will reduce over time (baby steps but still progress)
@github-actions

Copy link
Copy Markdown
Contributor

e18e dependency analysis

No dependency warnings found.

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.

2 participants