fix(esbuild): honor package.patterns negations in packaged artifacts - #13795
fix(esbuild): honor package.patterns negations in packaged artifacts#13795czubocha wants to merge 2 commits into
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughChangesEsbuild packaging now supports ordered Esbuild package pattern filtering
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant EsbuildPackaging
participant PackagePatterns
participant Archive
EsbuildPackaging->>PackagePatterns: Compile service and function patterns
EsbuildPackaging->>PackagePatterns: Filter includes and node_modules entries
EsbuildPackaging->>Archive: Append retained entries in sorted order
Archive-->>EsbuildPackaging: Return packaged artifact and exclusion diagnostics
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. 🔧 ESLint
packages/serverless/lib/plugins/esbuild/index.jsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. packages/serverless/test/unit/lib/plugins/esbuild/package-patterns-filtering.test.jsESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. 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: 1
🧹 Nitpick comments (4)
packages/serverless/test/unit/lib/plugins/esbuild/package-patterns-filtering.test.js (1)
32-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd cleanup for the temporary service directories.
makeServiceDirandmakeRacyServiceDircreate directories underos.tmpdir()withmkdtempSync. No test removes them. This file creates more than 30 fixtures, each holding anode_modulestree and generated zip artifacts, so repeated local runs accumulate data.Track the created paths and remove them in an
afterAllhook withfs.rmSync(dir, { recursive: true, force: true }).♻️ Proposed cleanup helper
+const createdServiceDirs = [] + +afterAll(() => { + for (const dir of createdServiceDirs) { + fs.rmSync(dir, { recursive: true, force: true }) + } +}) + function makeServiceDir({ emptyNodeModules = false } = {}) { const serviceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sls-esbuild-pat-')) + createdServiceDirs.push(serviceDir)Also applies to: 93-100
🤖 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/serverless/test/unit/lib/plugins/esbuild/package-patterns-filtering.test.js` around lines 32 - 57, Track every temporary directory returned by makeServiceDir and makeRacyServiceDir in a shared collection, then add an afterAll cleanup hook that removes each tracked path with fs.rmSync using recursive and force options. Ensure all fixture creators register their generated directories while preserving existing test behavior.packages/serverless/lib/plugins/esbuild/index.js (3)
1034-1043: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace
_.unionwith a nativeSetunion.Line 1034 uses
_.union. The coding guidelines prefer native JavaScript over lodash.♻️ Proposed change
- const unionedIncludes = _.union( - packageIncludes, - functionIncludes, - ).sort() + const unionedIncludes = [ + ...new Set([...packageIncludes, ...functionIncludes]), + ].sort()The filtering and the exclusion arithmetic on lines 1038-1043 are correct.
As per coding guidelines: "Prefer native JavaScript over lodash and use async/await for asynchronous code."
🤖 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/serverless/lib/plugins/esbuild/index.js` around lines 1034 - 1043, Replace the lodash _.union call in the unionedIncludes construction with a native Set-based union, preserving the combined unique values and the existing sorted array output used by filterPaths and excludedEntryCount.Source: Coding guidelines
989-1012: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
node_modulesentry filter in_packageand_packageAll. Both packaging paths declare the same three counters and the samenodeModulesEntryFilterbody. The only difference is the compiled pattern source. One shared factory removes the divergence risk.
packages/serverless/lib/plugins/esbuild/index.js#L989-L1012: replace the inline counters and filter with a call to a sharedcreateNodeModulesEntryFilter(compiledPatterns)helper, and read the counters from the returned object when calling_reportPatternFiltering.packages/serverless/lib/plugins/esbuild/index.js#L1188-L1211: replace this copy with the same helper call.🤖 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/serverless/lib/plugins/esbuild/index.js` around lines 989 - 1012, In packages/serverless/lib/plugins/esbuild/index.js at lines 989-1012 and 1188-1211, extract the duplicated counters and nodeModulesEntryFilter logic into a shared createNodeModulesEntryFilter(compiledPatterns) helper. Replace both inline implementations with calls to that helper, and use its returned counters when invoking _reportPatternFiltering; only the compiled pattern source should remain path-specific.
111-116: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueConsider skipping
lstatfor entries inside an already-excluded directory.Line 113 runs
lstatEntryfor every entry before the filter decides. If the patterns exclude a whole subtree, such as!node_modules/**, the walk still performs one syscall per file in that subtree and discards the result.entriesis sorted, so a parent directory is always visited before its children. You can record excluded directory prefixes and skip their children before thelstatcall.This is a throughput improvement only. Current behavior is correct.
🤖 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/serverless/lib/plugins/esbuild/index.js` around lines 111 - 116, Optimize the entry loop around lstatEntry by tracking directory prefixes rejected by filter and skipping any later entries beneath those prefixes before calling lstatEntry. Since entries are sorted, record an excluded entry when its stats identify a directory, preserve existing filter and appendFileEntry behavior for other entries, and ensure prefix matching only skips descendants of the excluded directory.
🤖 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/serverless/test/unit/lib/plugins/esbuild/package-patterns-filtering.test.js`:
- Line 592: Update both determinism tests in the package-patterns filtering test
suite to pass 60_000 as each test’s third timeout argument, rather than relying
on the file-level jest.setTimeout configuration. Remove or leave the broader
timeout only as appropriate, but ensure each determinism test explicitly uses
the 60-second timeout.
---
Nitpick comments:
In `@packages/serverless/lib/plugins/esbuild/index.js`:
- Around line 1034-1043: Replace the lodash _.union call in the unionedIncludes
construction with a native Set-based union, preserving the combined unique
values and the existing sorted array output used by filterPaths and
excludedEntryCount.
- Around line 989-1012: In packages/serverless/lib/plugins/esbuild/index.js at
lines 989-1012 and 1188-1211, extract the duplicated counters and
nodeModulesEntryFilter logic into a shared
createNodeModulesEntryFilter(compiledPatterns) helper. Replace both inline
implementations with calls to that helper, and use its returned counters when
invoking _reportPatternFiltering; only the compiled pattern source should remain
path-specific.
- Around line 111-116: Optimize the entry loop around lstatEntry by tracking
directory prefixes rejected by filter and skipping any later entries beneath
those prefixes before calling lstatEntry. Since entries are sorted, record an
excluded entry when its stats identify a directory, preserve existing filter and
appendFileEntry behavior for other entries, and ensure prefix matching only
skips descendants of the excluded directory.
In
`@packages/serverless/test/unit/lib/plugins/esbuild/package-patterns-filtering.test.js`:
- Around line 32-57: Track every temporary directory returned by makeServiceDir
and makeRacyServiceDir in a shared collection, then add an afterAll cleanup hook
that removes each tracked path with fs.rmSync using recursive and force options.
Ensure all fixture creators register their generated directories while
preserving existing test behavior.
🪄 Autofix
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: da37a188-36e9-44df-9c6a-ab78e22e7f4c
📒 Files selected for processing (6)
docs/sf/providers/aws/guide/building.mdpackages/serverless/lib/plugins/esbuild/index.jspackages/serverless/lib/utils/package-patterns.jspackages/serverless/test/unit/lib/plugins/esbuild/package-patterns-filtering.test.jspackages/serverless/test/unit/lib/utils/package-patterns-parity.test.jspackages/serverless/test/unit/lib/utils/package-patterns.test.js
- Scope the 6-run determinism test timeouts per test (jest.setTimeout is file-global regardless of describe placement) - Extract the duplicated node_modules entry filter into a shared createNodeModulesEntryFilter factory so the twin packaging paths cannot drift - Replace lodash union with a native Set union - Track and remove temporary fixture directories after the filtering suite
|
Addressed the review in d6d28bb:
Full suite re-run after the changes: 9 esbuild suites / 69 tests green, lint and Prettier clean. |
Summary
package.patternsnegation patterns (!...) when the built-in esbuild build packages function artifacts — both withpackage.individually(per-function zips) and for combined-service packagingindividually, function-level patterns are ignored, consistent with classic packaging, which already warns about that combinationnode_modulessubtrees leave no empty directory entries in the zippackage.json/lockfile are always packaged and cannot be excluded via patternsExcluded <N> entries from <zip> via package.patternsinfo line (visible with--verbose), a once-per-run warning when patterns exclude everything undernode_moduleswhilepackages: externalis active, and debug traces of the merged pattern list and included/excluded entry countspackages/serverless/lib/utils/package-patterns.jsmirrors classic packaging's ordered last-match-wins pattern semantics; a parity test suite runs the helper and the real classicresolveFilePathsFromPatternsagainst the same fixture trees and fails CI on any driftdocs/sf/providers/aws/guide/building.mdgains a "Packaging Patterns" section stating which parts of an esbuild artifact patterns apply toRoot cause
With
build.esbuildactive,package.patternswere only used as a positive glob to add extra files from the source directory. Negation patterns match nothing in a positive glob, so they were silently ignored, and the entire shared.serverless/build/node_modulestree was copied into every function artifact viazip.directory()with no filtering. This contradicted the packaging docs ("Packaging functions separately"), which state that per-function patterns merge with service-wide patterns and support!exclusions.The fix applies the ordered pattern toggle (last match wins, same semantics as classic packaging) as a predicate over the deterministic sorted entry walk introduced in #13794, and runs the additional-include list through the same compiled toggle, so a function-level negation can also drop a service-level include. Filtering happens after the sort, so entry-order determinism is preserved (pinned by 6-run hash-stability tests with filtering active). Patterns cannot suppress the fail-fast contract: an unreadable file under
node_modulesstill fails packaging withCANNOT_READ_FILEeven when patterns exclude it (test-enforced on both packaging paths).Test plan
package-patterns.test.js), classic-engine parity (package-patterns-parity.test.js), and artifact filtering incl. whole-zip sha256 identity for pattern-less configs, 6-run hash stability with filtering active, directory-entry pruning, warning dedupe, unreadable-entry fail-fast, and_packageAll(package-patterns-filtering.test.js)main: the function with negations loses exactly the 416 targeted entries (2361 → 1945 zip entries), zero collateral; the function without patterns produces a byte-identical artifact (whole-zip sha256 equal between themainCLI and this branch's CLI)!node_modules/<dep>/**negation — hashes constant across runs, entry-list diff shows exactly that dependency's subtree (+ its directory entry) removed and nothing else--verbosegit statusonpackages/serverless/lib/plugins/package/clean)Coverage notes, for reviewer awareness:
devDependencyExcludeSetordering (untested on both engines) or directory-entry pruning (classic packaging enumerates files only, so zip directory entries have no classic counterpart to compare against; the pruning behavior is pinned by dedicated esbuild-side tests instead).Excluded <N> entriesline is emitted at theinfolevel, so it appears only with--verbose— consistent with classic packaging's exclusion logging.onlyFilesdefault makes unreachable from user config) intentionally receive no pattern predicate; commented at both call sites.How to test
Use a project with
build.esbuild+packages: external,package.individually: true, and per-function negation patterns:where both functions depend on
fastifyandserverless-httpamong other dependencies.Release note callout (behavior-activating fix)
This fix activates configuration that was previously inert. Any existing
package.patternsnegations in abuild.esbuildservice — patterns that have had no effect until now — take effect on the next deploy and remove the matched files from the artifacts.Two configurations deserve explicit attention:
!node_modules/**combined withpackages: external(common in Lambda-layer setups, or carried over from before adoptingbuild.esbuild) now produces artifacts without their dependencies. The new once-per-run warning flags exactly this case.!node_modules/**+node_modules/some-dep/**. Re-include patterns match only the named package's own files — never its transitive dependencies, which live as sibling directories. These are the documented semantics (classic packaging has always worked this way), but the re-include list has not been enforced whilebuild.esbuildignored patterns, so lists written before that period may no longer cover everything the kept packages need. Verify the list still includes the transitive dependencies of every package you keep, or the function fails at invocation withCannot find module. No warning covers this case, since partial exclusion is indistinguishable from intended use.Recommendation: ship in the same minor release as #13794 with a prominent release-note entry asking users to review existing negation patterns under
build.esbuildbefore upgrading. This change adds no independent artifact churn on top of #13794: configurations without negation patterns produce byte-identical artifacts to that base, so the two changes share a single "artifacts re-upload once on the first post-upgrade deploy" story.Related
Closes #12813
Closes #12742
Summary by CodeRabbit
New Features
package.patternswhen packaging esbuild artifacts.Bug Fixes