[bug#74900,v2,2/6] build-system/node: New API for modifying json.

Message ID 20250217234823.10533-3-ngraves@ngraves.fr
State New
Headers
Series build-system/node: Replace (guix build json) by (json). |

Commit Message

Nicolas Graves Feb. 17, 2025, 11:43 p.m. UTC
  From: Daniel Khodabakhsh <d.khodabakhsh@gmail.com>

Introduce (modify-json), (delete-fields), and (replace-fields) to node-build-system

This change introduces helper procedures (modify-json) which takes in lambdas
 which modify the target json #:file which defaults to package.json
This change also includes (delete-fields) and (replace-fields) to help deleting
 and replacing the value of fields in a package.json file.

* guix/build/node-build-system.scm
(modify-json, modify-json-fields, delete-fields, replace-fields,
delete-dev-dependencies): New API exported procedures.
(with-atomic-json-file-replacement, delete-dependencies): Change
procedures to adapt to the new API.

* gnu/packages/node.scm (node-ms-bootstrap,
node-binary-search-bootstrap, node-debug-boostrap,
node-llparse-builder-bootstrap, node-llparse-frontend-boostrap,
node-llparse-bootstrap): Use new API procedures.

* gnu/packages/node-xyz.scm (node-acorn, node-addon-api,
node-buffer-crc32, node-crx3, node-debug, node-file-uri-to-path,
node-ieee754, node-inherits, node-irc, node-irc-colors, node-minimist,
node-ms, node-nan, node-normalize-path, node-once, node-path-key,
node-pbf, node-protocol-buffers-schema, node-readable-stream,
node-resolve-protobuf-schema, node-safe-buffer,
node-safe-stable-stringify, node-semver, node-serialport,
node-serialport-bindings, node-serialport-stream, node-sqlite3,
node-string-decoder, node-tiddlywiki, node-wrappy, node-yazl): Use new
API procedures.

Signed-off-by: Daniel Khodabakhsh <d.khodabakhsh@gmail.com>
Change-Id: I957f7ca814078d2136d5261985174820235f1369
---
 gnu/packages/node-xyz.scm        | 395 ++++++++++++++-----------------
 gnu/packages/node.scm            |  86 +++----
 guix/build/node-build-system.scm | 198 +++++++++++++---
 3 files changed, 392 insertions(+), 287 deletions(-)
  

Patch

diff --git a/gnu/packages/node-xyz.scm b/gnu/packages/node-xyz.scm
index 84adde9e50..54e3376e4e 100644
--- a/gnu/packages/node-xyz.scm
+++ b/gnu/packages/node-xyz.scm
@@ -69,18 +69,7 @@  (define-public node-acorn
              ;; We need to remove the prepare script from "package.json", as
              ;; it would try to use the build environment and would block the
              ;; automatic building by other packages making use of node-acorn.
-             ;; TODO: Add utility function
-             (with-atomic-json-file-replacement (lambda (pkg-meta-alist)
-               (map
-                 (match-lambda
-                   (("scripts" . scripts-alist)
-                     (cons "scripts" (filter
-                       (match-lambda
-                         (("prepare" . _) #f)
-                         (_ #t))
-                       scripts-alist)))
-                   (other other))
-                 pkg-meta-alist)))))
+             (modify-json (delete-fields '(("scripts" "prepare"))))))
          (replace 'build
            (lambda* (#:key inputs native-inputs #:allow-other-keys)
              (let ((esbuild (search-input-file (or native-inputs inputs)
@@ -136,38 +125,25 @@  (define-public node-addon-api
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies
-              `("benchmark"
-                "bindings"
-                "clang-format"
-                "eslint"
-                "eslint-config-semistandard"
-                "eslint-config-standard"
-                "eslint-plugin-import"
-                "eslint-plugin-node"
-                "eslint-plugin-promise"
-                "fs-extra"
-                "neostandard"
-                "path"
-                "pre-commit"
-                "semver"))))
-         (add-after 'unpack 'skip-js-tests
-           ;; We can't run the js-based tests,
-           ;; but we can still do the C++ parts
-           (lambda args
-             (define new-test-script
-               "echo stopping after pretest on Guix")
-             (with-atomic-json-file-replacement (lambda (pkg-meta-alist)
-               (map
-                 (match-lambda
-                   (("scripts" . scripts-alist)
-                     (cons "scripts" (map
-                       (match-lambda
-                         (("test" . _) (cons "test" new-test-script))
-                         (other other))
-                       scripts-alist)))
-                   (other other))
-                 pkg-meta-alist))))))))
+             (modify-json (delete-dependencies
+                           `("benchmark"
+                             "bindings"
+                             "clang-format"
+                             "eslint"
+                             "eslint-config-semistandard"
+                             "eslint-config-standard"
+                             "eslint-plugin-import"
+                             "eslint-plugin-node"
+                             "eslint-plugin-promise"
+                             "fs-extra"
+                             "neostandard"
+                             "path"
+                             "pre-commit"
+                             "semver"))
+               ;; We can't run the js-based tests,
+               ;; but we can still do the C++ parts
+               (replace-fields (list (cons
+                 "scripts.test" "echo stopping after pretest on Guix")))))))))
     (home-page "https://github.com/nodejs/node-addon-api")
     (synopsis "Node.js API (Node-API) header-only C++ wrappers")
     (description "This module contains header-only C++ wrapper classes which
@@ -232,7 +208,7 @@  (define-public node-buffer-crc32
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("tap")))))))
+                      (modify-json (delete-dependencies '("tap"))))))))
     (home-page "https://github.com/brianloveswords/buffer-crc32")
     (synopsis "CRC32 implementation in Javascript")
     (description
@@ -288,14 +264,15 @@  (define-public node-crx3
                          "minimist"))))
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("c8"
-                                             "docdash"
-                                             "eslint"
-                                             "eslint-plugin-jsdoc"
-                                             "jsdoc"
-                                             "tap-diff"
-                                             "tape"
-                                             "tape-catch")))))))
+                      (modify-json (delete-dependencies
+                                    '("c8"
+                                      "docdash"
+                                      "eslint"
+                                      "eslint-plugin-jsdoc"
+                                      "jsdoc"
+                                      "tap-diff"
+                                      "tape"
+                                      "tape-catch"))))))))
     (inputs (list node-minimist node-pbf node-yazl))
     (home-page "https://github.com/ahwayakchih/crx3")
     (synopsis "Create CRXv3 browser extensions with Javascript")
@@ -325,18 +302,19 @@  (define-public node-debug
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("brfs"
-                                    "browserify"
-                                    "coveralls"
-                                    "istanbul"
-                                    "karma"
-                                    "karma-browserify"
-                                    "karma-chrome-launcher"
-                                    "karma-mocha"
-                                    "mocha"
-                                    "mocha-lcov-reporter"
-                                    "xo"
-                                    "supports-color")))))
+             (modify-json (delete-dependencies
+                           `("brfs"
+                             "browserify"
+                             "coveralls"
+                             "istanbul"
+                             "karma"
+                             "karma-browserify"
+                             "karma-chrome-launcher"
+                             "karma-mocha"
+                             "mocha"
+                             "mocha-lcov-reporter"
+                             "xo"
+                             "supports-color"))))))
        #:tests? #f))
     (home-page "https://github.com/debug-js/debug")
     (synopsis "Debugging utility for Node.js")
@@ -421,21 +399,22 @@  (define-public node-file-uri-to-path
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("@types/mocha"
-                                    "@types/node"
-                                    "@typescript-eslint/eslint-plugin"
-                                    "@typescript-eslint/parser"
-                                    "cpy-cli"
-                                    "eslint"
-                                    "eslint-config-airbnb"
-                                    "eslint-config-prettier"
-                                    "eslint-import-resolver-typescript"
-                                    "eslint-plugin-import"
-                                    "eslint-plugin-jsx-a11y"
-                                    "eslint-plugin-react"
-                                    "mocha"
-                                    "rimraf"
-                                    "typescript"))))
+             (modify-json (delete-dependencies
+                           `("@types/mocha"
+                             "@types/node"
+                             "@typescript-eslint/eslint-plugin"
+                             "@typescript-eslint/parser"
+                             "cpy-cli"
+                             "eslint"
+                             "eslint-config-airbnb"
+                             "eslint-config-prettier"
+                             "eslint-import-resolver-typescript"
+                             "eslint-plugin-import"
+                             "eslint-plugin-jsx-a11y"
+                             "eslint-plugin-react"
+                             "mocha"
+                             "rimraf"
+                             "typescript")))))
          (replace 'build
            (lambda* (#:key inputs native-inputs #:allow-other-keys)
              (copy-recursively "src" "dist")
@@ -497,7 +476,9 @@  (define-public node-ieee754
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("airtap" "standard" "tape")))))))
+                      (modify-json
+                       (delete-dependencies
+                        '("airtap" "standard" "tape"))))))))
     (home-page "https://github.com/feross/ieee754")
     (synopsis "Read/write IEEE754 floating point numbers in Javascript")
     (description "This package can read and write IEEE754 floating point
@@ -524,7 +505,7 @@  (define-public node-inherits
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("tap")))))
+             (modify-json (delete-dependencies '("tap"))))))
        ;; FIXME: Tests depend on node-tap
        #:tests? #f))
     (home-page "https://github.com/isaacs/inherits")
@@ -553,8 +534,9 @@  (define-public node-irc
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies
-              `("ansi-color" "faucet" "jscs" "tape")))))
+             (modify-json
+              (delete-dependencies
+               `("ansi-color" "faucet" "jscs" "tape"))))))
        #:tests? #f))
     (inputs
      (list node-irc-colors))
@@ -583,7 +565,7 @@  (define-public node-irc-colors
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("istanbul" "vows")))))
+             (modify-json (delete-dependencies `("istanbul" "vows"))))))
        #:tests? #f))
     (home-page "https://github.com/fent/irc-colors.js")
     (synopsis "Node.js module providing color and formatting for IRC")
@@ -657,7 +639,8 @@  (define-public node-minimist
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("covert" "tap" "tape")))))))
+                      (modify-json (delete-dependencies
+                                    '("covert" "tap" "tape"))))))))
     (home-page "https://github.com/substack/minimist")
     (synopsis "Parse CLI arguments in Javascript")
     (description "This package can scan for CLI flags and arguments in
@@ -683,12 +666,13 @@  (define-public node-ms
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("eslint"
-                                    "expect.js"
-                                    "husky"
-                                    "lint-staged"
-                                    "mocha"
-                                    "prettier")))))
+             (modify-json (delete-dependencies
+                           `("eslint"
+                             "expect.js"
+                             "husky"
+                             "lint-staged"
+                             "mocha"
+                             "prettier"))))))
        #:tests? #f))
     (home-page "https://github.com/vercel/ms")
     (synopsis "Convert time to milliseconds")
@@ -716,14 +700,14 @@  (define-public node-nan
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies
-              '("bindings"
-                "commander"
-                "glob"
-                "request"
-                "node-gyp" ;; would be needed for tests
-                "tap"
-                "xtend")))))
+             (modify-json (delete-dependencies
+                           '("bindings"
+                             "commander"
+                             "glob"
+                             "request"
+                             "node-gyp" ;; would be needed for tests
+                             "tap"
+                             "xtend"))))))
        ;; tests need tap and other dependencies
        #:tests? #f))
     (inputs
@@ -755,7 +739,8 @@  (define-public node-normalize-path
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("gulp-format-md" "mocha")))))))
+                      (modify-json (delete-dependencies
+                                    '("gulp-format-md" "mocha"))))))))
     (native-inputs (list node-minimist))
     (home-page "https://github.com/jonschlinkert/normalize-path")
     (synopsis "Normalize slashes in a file path")
@@ -784,7 +769,7 @@  (define-public node-once
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("tap")))))
+             (modify-json (delete-dependencies '("tap"))))))
        ;; FIXME: Tests depend on node-tap
        #:tests? #f))
     (inputs
@@ -841,7 +826,9 @@  (define-public node-path-key
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("@types/node" "ava" "tsd" "xo")))))))
+                      (modify-json
+                       (delete-dependencies
+                        '("@types/node" "ava" "tsd" "xo"))))))))
     (home-page "https://github.com/sindresorhus/path-key")
     (synopsis "Cross-platform utility to compute the PATH environment variable key")
     (description "@code{path-key} provides an implementation to compute the
@@ -867,17 +854,17 @@  (define-public node-pbf
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies
-                       '("benchmark"
-                         "browserify"
-                         "eslint"
-                         "eslint-config-mourner"
-                         "mkdirp"
-                         "protobufjs"
-                         "protocol-buffers"
-                         "tap"
-                         "tile-stats-runner"
-                         "uglify-js")))))))
+                      (modify-json (delete-dependencies
+                                    '("benchmark"
+                                      "browserify"
+                                      "eslint"
+                                      "eslint-config-mourner"
+                                      "mkdirp"
+                                      "protobufjs"
+                                      "protocol-buffers"
+                                      "tap"
+                                      "tile-stats-runner"
+                                      "uglify-js"))))))))
     (inputs (list node-ieee754 node-resolve-protobuf-schema))
     (home-page "https://github.com/mapbox/pbf")
     (synopsis "Decode and encode protocol buffers in Javascript")
@@ -908,7 +895,8 @@  (define-public node-protocol-buffers-schema
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("standard" "tape")))))))
+                      (modify-json (delete-dependencies
+                                    '("standard" "tape"))))))))
     (home-page "https://github.com/mafintosh/protocol-buffers-schema")
     (synopsis "Protocol buffers schema parser written in Javascript")
     (description "This package provides a protocol buffers schema parser
@@ -935,26 +923,27 @@  (define-public node-readable-stream
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("@babel/cli"
-                                    "@babel/core"
-                                    "@babel/polyfill"
-                                    "@babel/preset-env"
-                                    "airtap"
-                                    "assert"
-                                    "bl"
-                                    "deep-strict-equal"
-                                    "events.once"
-                                    "glob"
-                                    "gunzip-maybe"
-                                    "hyperquest"
-                                    "lolex"
-                                    "nyc"
-                                    "pump"
-                                    "rimraf"
-                                    "tap"
-                                    "tape"
-                                    "tar-fs"
-                                    "util-promisify")))))
+             (modify-json (delete-dependencies
+                           `("@babel/cli"
+                             "@babel/core"
+                             "@babel/polyfill"
+                             "@babel/preset-env"
+                             "airtap"
+                             "assert"
+                             "bl"
+                             "deep-strict-equal"
+                             "events.once"
+                             "glob"
+                             "gunzip-maybe"
+                             "hyperquest"
+                             "lolex"
+                             "nyc"
+                             "pump"
+                             "rimraf"
+                             "tap"
+                             "tape"
+                             "tar-fs"
+                             "util-promisify"))))))
        #:tests? #f))
     (inputs (list node-util-deprecate node-string-decoder node-inherits))
     (home-page "https://github.com/nodejs/readable-stream")
@@ -983,7 +972,8 @@  (define-public node-resolve-protobuf-schema
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("standard" "tape")))))))
+                      (modify-json (delete-dependencies
+                                    '("standard" "tape"))))))))
     (inputs (list node-protocol-buffers-schema))
     (home-page "https://github.com/mafintosh/resolve-protobuf-schema")
     (synopsis "Resolve protobuf imports")
@@ -1012,7 +1002,7 @@  (define-public node-safe-buffer
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("tape" "standard")))))
+             (modify-json (delete-dependencies '("tape" "standard"))))))
        #:tests? #f))
     (home-page "https://github.com/feross/safe-buffer")
     (synopsis "Buffer creation with explicit semantics")
@@ -1040,20 +1030,22 @@  (define-public node-safe-stable-stringify
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("benchmark" "clone"
-                                             "fast-json-stable-stringify"
-                                             "fast-safe-stringify"
-                                             "fast-stable-stringify"
-                                             "faster-stable-stringify"
-                                             "fastest-stable-stringify"
-                                             "json-stable-stringify"
-                                             "json-stringify-deterministic"
-                                             "json-stringify-safe"
-                                             "standard"
-                                             "tap"
-                                             "typescript"
-                                             "@types/node"
-                                             "@types/json-stable-stringify")))))))
+                      (modify-json (delete-dependencies
+                                    '("benchmark"
+                                      "clone"
+                                      "fast-json-stable-stringify"
+                                      "fast-safe-stringify"
+                                      "fast-stable-stringify"
+                                      "faster-stable-stringify"
+                                      "fastest-stable-stringify"
+                                      "json-stable-stringify"
+                                      "json-stringify-deterministic"
+                                      "json-stringify-safe"
+                                      "standard"
+                                      "tap"
+                                      "typescript"
+                                      "@types/node"
+                                      "@types/json-stable-stringify"))))))))
     (home-page "https://github.com/BridgeAR/safe-stable-stringify")
     (synopsis "Serialization of javascript objects")
     (description
@@ -1111,7 +1103,7 @@  (define-public node-semver
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("tap")))))
+             (modify-json (delete-dependencies '("tap"))))))
        ;; FIXME: Tests depend on node-tap
        #:tests? #f))
     (home-page "https://github.com/npm/node-semver")
@@ -1151,7 +1143,8 @@  (define-public node-serialport
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("@serialport/binding-mock"))))
+             (modify-json (delete-dependencies
+                           `("@serialport/binding-mock")))))
          (add-after 'unpack 'chdir
            (lambda args
              (chdir "packages/serialport"))))
@@ -1210,25 +1203,7 @@  (define-public node-serialport-bindings
              (chdir "packages/bindings")))
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("prebuild-install"
-                                    ;; devDependencies
-                                    "@serialport/binding-mock"
-                                    "node-abi"))))
-         (add-after 'chdir 'avoid-prebuild-install
-           (lambda args
-             (with-atomic-json-file-replacement (lambda (pkg-meta-alist)
-               (map
-                 (match-lambda
-                   (("scripts" . scripts-alist)
-                     (cons "scripts" (filter
-                       (match-lambda
-                         (("install" . _) #f)
-                         (_ #t))
-                       scripts-alist)))
-                   (("gypfile" . _)
-                     (cons "gypfile" #f))
-                   (other other))
-                 pkg-meta-alist))))))
+             (modify-json (delete-dependencies `("node-abi"))))))
        #:tests? #f))
     (synopsis "Abstract base class for Node SerialPort bindings")
     (description "Node SerialPort is a modular suite of Node.js packages for
@@ -1409,8 +1384,8 @@  (define-public node-serialport-stream
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `(;; devDependencies
-                                    "@serialport/binding-mock"))))
+             (modify-json
+               (delete-dependencies `("@serialport/binding-mock")))))
          (add-after 'unpack 'chdir
            (lambda args
              (chdir "packages/stream"))))
@@ -1461,26 +1436,27 @@  (define-public node-sqlite3
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies
-              `(;; Normally, this is "built" using @mapbox/node-pre-gyp,
-                ;; which publishes or downloads pre-built binaries or
-                ;; falls back to building from source.  Here, we patch out
-                ;; all of that and just build directly.  It might be
-                ;; better to patch a version of @mapbox/node-pre-gyp that
-                ;; always builds from source, as Debian does, but there
-                ;; are a number of dependencies that need to be packaged
-                ;; or removed.
-                "@mapbox/node-pre-gyp"
-                "node-pre-gyp" ;; deprecated name still used in some places
-                "aws-sdk"
-                "@mapbox/cloudfriend"
-                ;; Confusingly, this is only a dependency because of
-                ;; @mapbox/node-pre-gyp: with that removed,
-                ;; npm will use its own copy:
-                "node-gyp"
-                ;; These we'd like, we just don't have them yet:
-                "eslint"
-                "mocha"))))
+             (modify-json
+              (delete-dependencies
+               `(;; Normally, this is "built" using @mapbox/node-pre-gyp,
+                 ;; which publishes or downloads pre-built binaries or
+                 ;; falls back to building from source.  Here, we patch out
+                 ;; all of that and just build directly.  It might be
+                 ;; better to patch a version of @mapbox/node-pre-gyp that
+                 ;; always builds from source, as Debian does, but there
+                 ;; are a number of dependencies that need to be packaged
+                 ;; or removed.
+                 "@mapbox/node-pre-gyp"
+                 "node-pre-gyp" ;; deprecated name still used in some places
+                 "aws-sdk"
+                 "@mapbox/cloudfriend"
+                 ;; Confusingly, this is only a dependency because of
+                 ;; @mapbox/node-pre-gyp: with that removed,
+                 ;; npm will use its own copy:
+                 "node-gyp"
+                 ;; These we'd like, we just don't have them yet:
+                 "eslint"
+                 "mocha")))))
          (add-before 'configure 'npm-config-sqlite
            ;; We need this step even if we do replace @mapbox/node-pre-gyp
            ;; because the package expects to build its bundled sqlite
@@ -1514,8 +1490,9 @@  (define-public node-sqlite3
              (substitute* ".npmignore"
                (("lib/binding")
                 "#lib/binding # <- patched for Guix"))
-             (with-atomic-json-file-replacement (lambda (pkg-meta-alist)
-               (let ((binary-alist (assoc-ref pkg-meta-alist "binary")))
+             (modify-json
+               (lambda (pkg-meta-alist)
+                 (let ((binary-alist (assoc-ref pkg-meta-alist "binary")))
                  ;; When it builds from source, node-pre-gyp supplies
                  ;; module_name and module_path based on the entries under
                  ;; "binary" from "package.json", so this package's
@@ -1528,19 +1505,11 @@  (define-public node-sqlite3
                    (assoc-ref binary-alist "module_name")
                    " module_path="
                    (assoc-ref binary-alist "module_path"))))
+                 pkg-meta-alist)
                ;; We need to remove the install script from "package.json",
                ;; as it would try to use node-pre-gyp and would block the
                ;; automatic building performed by `npm install`.
-               (map
-                 (match-lambda
-                   (("scripts" . scripts-alist)
-                     (cons "scripts" (filter
-                       (match-lambda
-                         (("install" . _) #f)
-                         (_ #t))
-                       scripts-alist)))
-                   (other other))
-                 pkg-meta-alist))))))))
+               (delete-fields `(("scripts" "install")))))))))
     (home-page "https://github.com/mapbox/node-sqlite3")
     (synopsis "Node.js bindings for SQLite3")
     (description
@@ -1623,8 +1592,9 @@  (define-public node-string-decoder
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies
-              '("tap" "core-util-is" "babel-polyfill")))))
+             (modify-json
+              (delete-dependencies
+               '("tap" "core-util-is" "babel-polyfill"))))))
        ;; FIXME: Tests depend on node-tap
        #:tests? #f))
     (inputs (list node-safe-buffer node-inherits))
@@ -1656,7 +1626,8 @@  (define-public node-tiddlywiki
                    (delete 'build)
                    (add-after 'patch-dependencies 'delete-dev-dependencies
                      (lambda _
-                       (delete-dependencies '("eslint" "@eslint/js")))))))
+                       (modify-json
+                        (delete-dependencies '("eslint" "@eslint/js"))))))))
     (home-page "https://github.com/TiddlyWiki/TiddlyWiki5#readme")
     (synopsis "Non-linear personal web notebook")
     (description "TiddlyWiki is a unique non-linear notebook for capturing,
@@ -1714,7 +1685,7 @@  (define-public node-wrappy
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("tap")))))))
+             (modify-json (delete-dependencies '("tap"))))))))
     (home-page "https://github.com/npm/wrappy")
     (synopsis "Callback wrapping utility")
     (description "@code{wrappy} is a utility for Node.js to wrap callbacks.")
@@ -1739,7 +1710,9 @@  (define-public node-yazl
        #:phases (modify-phases %standard-phases
                   (add-after 'patch-dependencies 'delete-dependencies
                     (lambda _
-                      (delete-dependencies '("airtap" "bl" "istanbul" "yauzl")))))))
+                      (modify-json
+                       (delete-dependencies
+                        '("airtap" "bl" "istanbul" "yauzl"))))))))
     (inputs (list node-buffer-crc32))
     (home-page "https://github.com/thejoshwolfe/yazl")
     (synopsis "Yet another zip library for node")
diff --git a/gnu/packages/node.scm b/gnu/packages/node.scm
index 75a1a12c53..6d6768e590 100644
--- a/gnu/packages/node.scm
+++ b/gnu/packages/node.scm
@@ -13,6 +13,7 @@ 
 ;;; Copyright © 2021, 2022 Philip McGrath <philip@philipmcgrath.com>
 ;;; Copyright © 2022 Hilton Chain <hako@ultrarare.space>
 ;;; Copyright © 2024 Efraim Flashner <efraim@flashner.co.il>
+;;; Copyright © 2024 Daniel Khodabakhsh <d.khodabakhsh@gmail.com>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -366,7 +367,7 @@  (define-public node-semver-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("tap")))))))
+             (modify-json (delete-dependencies '("tap"))))))))
     (home-page "https://github.com/npm/node-semver")
     (properties '((hidden? . #t)))
     (synopsis "Parses semantic versions strings")
@@ -397,11 +398,12 @@  (define-public node-ms-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies '("eslint"
-                                    "expect.js"
-                                    "husky"
-                                    "lint-staged"
-                                    "mocha")))))))
+             (modify-json (delete-dependencies
+                           '("eslint"
+                             "expect.js"
+                             "husky"
+                             "lint-staged"
+                             "mocha"))))))))
     (home-page "https://github.com/zeit/ms#readme")
     (properties '((hidden? . #t)))
     (synopsis "Tiny millisecond conversion utility")
@@ -431,7 +433,7 @@  (define-public node-binary-search-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("chai" "mocha")))))))
+             (modify-json (delete-dependencies `("chai" "mocha"))))))))
     (home-page "https://github.com/darkskyapp/binary-search#readme")
     (properties '((hidden? . #t)))
     (synopsis "Tiny binary search function with comparators")
@@ -460,17 +462,18 @@  (define-public node-debug-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("brfs"
-                                    "browserify"
-                                    "coveralls"
-                                    "istanbul"
-                                    "karma"
-                                    "karma-browserify"
-                                    "karma-chrome-launcher"
-                                    "karma-mocha"
-                                    "mocha"
-                                    "mocha-lcov-reporter"
-                                    "xo")))))))
+             (modify-json (delete-dependencies
+                           `("brfs"
+                             "browserify"
+                             "coveralls"
+                             "istanbul"
+                             "karma"
+                             "karma-browserify"
+                             "karma-chrome-launcher"
+                             "karma-mocha"
+                             "mocha"
+                             "mocha-lcov-reporter"
+                             "xo"))))))))
     (inputs (list node-ms-bootstrap))
     (home-page "https://github.com/visionmedia/debug#readme")
     (properties '((hidden? . #t)))
@@ -526,12 +529,13 @@  (define-public node-llparse-builder-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda _
-             (delete-dependencies `("@types/mocha"
-                                    "@types/node"
-                                    "mocha"
-                                    "ts-node"
-                                    "tslint"
-                                    "typescript"))))
+             (modify-json (delete-dependencies
+                           `("@types/mocha"
+                             "@types/node"
+                             "mocha"
+                             "ts-node"
+                             "tslint"
+                             "typescript")))))
          (replace 'build
            (lambda* (#:key inputs #:allow-other-keys)
              (let ((esbuild (search-input-file inputs "/bin/esbuild")))
@@ -587,13 +591,14 @@  (define-public node-llparse-frontend-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("@types/debug"
-                                    "@types/mocha"
-                                    "@types/node"
-                                    "mocha"
-                                    "ts-node"
-                                    "tslint"
-                                    "typescript"))))
+             (modify-json (delete-dependencies
+                           `("@types/debug"
+                             "@types/mocha"
+                             "@types/node"
+                             "mocha"
+                             "ts-node"
+                             "tslint"
+                             "typescript")))))
          (replace 'build
            (lambda* (#:key inputs #:allow-other-keys)
              (let ((esbuild (search-input-file inputs "/bin/esbuild")))
@@ -648,15 +653,16 @@  (define-public node-llparse-bootstrap
        (modify-phases %standard-phases
          (add-after 'patch-dependencies 'delete-dependencies
            (lambda args
-             (delete-dependencies `("@types/debug"
-                                    "@types/mocha"
-                                    "@types/node"
-                                    "esm"
-                                    "llparse-test-fixture"
-                                    "mocha"
-                                    "ts-node"
-                                    "tslint"
-                                    "typescript"))))
+             (modify-json (delete-dependencies
+                           `("@types/debug"
+                             "@types/mocha"
+                             "@types/node"
+                             "esm"
+                             "llparse-test-fixture"
+                             "mocha"
+                             "ts-node"
+                             "tslint"
+                             "typescript")))))
          (replace 'build
            (lambda* (#:key inputs #:allow-other-keys)
              (let ((esbuild (search-input-file inputs "/bin/esbuild")))
diff --git a/guix/build/node-build-system.scm b/guix/build/node-build-system.scm
index df7ea7774c..ea23d92a05 100644
--- a/guix/build/node-build-system.scm
+++ b/guix/build/node-build-system.scm
@@ -31,18 +31,14 @@  (define-module (guix build node-build-system)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-71)
   #:export (%standard-phases
-            with-atomic-json-file-replacement
             delete-dependencies
-            node-build))
-
-(define* (with-atomic-json-file-replacement proc
-  #:optional (file "package.json"))
-  "Like 'with-atomic-file-replacement', but PROC is called with a single
-argument---the result of parsing FILE's contents as json---and should a value
-to be written as json to the replacement FILE."
-  (with-atomic-file-replacement file
-    (lambda (in out)
-      (scm->json (proc (json->scm in)) out))))
+            delete-dev-dependencies
+            delete-fields
+            modify-json
+            modify-json-fields
+            node-build
+            replace-fields
+            with-atomic-json-file-replacement))
 
 (define* (assoc-ref* alist key #:optional default)
   "Like assoc-ref, but return DEFAULT instead of #f if no value exists."
@@ -72,6 +68,161 @@  (define* (alist-update alist key proc #:optional (= equal?))
       (acons key (proc (cdr pair)) rest)
       alist)))
 
+;;;
+;;; package.json modification procedures
+;;;
+
+(define* (with-atomic-json-file-replacement proc
+  #:optional (file "package.json"))
+  "Like 'with-atomic-file-replacement', but PROC is called with a single
+argument---the result of parsing FILE's contents as json---and should a value
+to be written as json to the replacement FILE."
+  (with-atomic-file-replacement file
+    (lambda (in out)
+      (scm->json (proc (json->scm in)) out))))
+
+(define* (modify-json #:key (file "package.json") #:rest all-arguments)
+  "Provide package.json modifying callbacks such as (delete-dependencies ...)"
+  (let
+    (
+      (modifications
+        (let loop ((arguments all-arguments))
+          (cond
+            ((null? arguments) '())
+            ((keyword? (car arguments)) (loop (cddr arguments)))
+            (else (cons (car arguments) (loop (cdr arguments))))))))
+    (with-atomic-json-file-replacement
+      (lambda (package)
+        (fold
+          (lambda (modification package)
+            (modification package))
+          package
+          modifications))
+      file)))
+
+(define (delete-dependencies dependencies-to-remove)
+  "Rewrite 'package.json' to allow the build to proceed without packages
+listed in 'dependencies-to-remove', a list of strings naming npm packages.
+
+To prevent the deleted dependencies from being reintroduced, use this function
+only after the 'patch-dependencies' phase."
+  (lambda (pkg-meta)
+    (fold
+      (lambda (dependency-key pkg-meta)
+        (alist-update
+          pkg-meta
+          dependency-key
+          (lambda (dependencies)
+            (remove
+              (lambda (dependency)
+                (member (car dependency) dependencies-to-remove))
+              dependencies))))
+      pkg-meta
+      (list
+        "devDependencies"
+        "dependencies"
+        "peerDependencies"
+        "optionalDependencies"))))
+
+(define* (modify-json-fields
+    fields
+    field-modifier
+    #:key
+      (field-path-mapper (lambda (field) field))
+      (insert? #f)
+      (strict? #t))
+  "Provides a lambda to supply to modify-json which modifies the specified
+ json file.
+- `fields` is a list procedure-specific data structures which should include
+ the definition of a `field-path` in one of two syntaxes: dot-syntax string
+ such as `\"devDependencies.esbuild\"`, or a list of strings such as
+ `(list \"devDependencies\" \"esbuild\")`.
+- `field-modifier` is a lambda which is invoked at the position of the field.
+ It is supplied with the current field definition, the association list (alist)
+ at the field location in the json file, and the field name, also called `key`.
+- `field-path-mapper` is a lambda which instructs where the field-path is
+ located within the field structure.
+- `insert?` allows the creation of the field and any missing intermediate
+ fields.
+- `strict?` causes an error to be thrown if the exact field-path is not found
+ in the data"
+  (lambda (package)
+    (fold
+      (lambda (field package)
+        (let*
+          (
+            (field-path (field-path-mapper field))
+            (
+              field-path
+              (cond
+                ((string? field-path)
+                  (string-split field-path #\.))
+                ((and (list? field-path) (every string? field-path))
+                  field-path)
+                (else
+                  (error
+                    (string-append
+                      "Invalid field value provided, expecting a string or a "
+                      "list of string but instead got: "
+                      (with-output-to-string (lambda _ (display field-path))))))
+              )))
+          (let loop
+            (
+              (data package)
+              (field-path field-path))
+            (let*
+              (
+                (key (car field-path))
+                (data
+                  (if (and (not (assoc key data)) insert?)
+                    (acons key '() data)
+                    data)))
+              (if (not (assoc key data))
+                (if strict?
+                  (error (string-append
+                    "Key '" key "' was not found in data: "
+                    (with-output-to-string (lambda _ (display data)))))
+                  data)
+                (if (= (length field-path) 1)
+                  (field-modifier field data key)
+                  (assoc-set!
+                    data
+                    key
+                    (loop (assoc-ref data key) (cdr field-path)))))))))
+      package
+      fields)))
+
+(define* (delete-fields fields #:key (strict? #t))
+  "Provides a lambda to supply to modify-json which deletes the specified
+ `fields` which is a list of field-paths as mentioned in `modify-json-fields`.
+ Examples:
+  (delete-fields '(
+    (\"path\" \"to\" \"field\")
+    \"path.to.other.field\"))"
+  (modify-json-fields
+    fields
+    (lambda (_ data key)
+      (assoc-remove! data key))
+    #:strict? strict?))
+
+(define* (replace-fields fields #:key (strict? #t))
+  "Provides a lambda to supply to modify-json which replaces the value of the
+ supplied field. `fields` is a list of pairs, where the first element is the
+ field-path and the second element is the value to replace the target with.
+ Examples:
+  (replace-fields '(
+    ((\"path\" \"to\" \"field\") \"new field value\")
+    (\"path.to.other.field\" \"new field value\")))"
+  (modify-json-fields
+    fields
+    (lambda (field data key)
+      (assoc-set! data key (cdr field)))
+    #:field-path-mapper (lambda (field) (car field))
+    #:strict? strict?))
+
+(define (delete-dev-dependencies)
+  (delete-fields (list "devDependencies") #:strict #f))
+
 ;;;
 ;;; Phases.
 ;;;
@@ -144,31 +295,6 @@  (define (resolve-dependencies dependencies)
                   (assoc-ref* pkg-meta "dependencies" '())))))))))
   #t)
 
-(define (delete-dependencies dependencies-to-remove)
-  "Rewrite 'package.json' to allow the build to proceed without packages
-listed in 'dependencies-to-remove', a list of strings naming npm packages.
-
-To prevent the deleted dependencies from being reintroduced, use this function
-only after the 'patch-dependencies' phase."
-  (with-atomic-json-file-replacement
-    (lambda (pkg-meta)
-      (fold
-        (lambda (dependency-key pkg-meta)
-          (alist-update
-            pkg-meta
-            dependency-key
-            (lambda (dependencies)
-              (remove
-                (lambda (dependency)
-                  (member (car dependency) dependencies-to-remove))
-                dependencies))))
-        pkg-meta
-        (list
-          "devDependencies"
-          "dependencies"
-          "peerDependencies"
-          "optionalDependencies")))))
-
 (define* (delete-lockfiles #:key inputs #:allow-other-keys)
   "Delete 'package-lock.json', 'yarn.lock', and 'npm-shrinkwrap.json', if they
 exist."