new file mode 100644
@@ -0,0 +1,115 @@
+# Copyright (C) 2025 Weidmueller Interface GmbH & Co. KG
+# Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+#
+# SPDX-License-Identifier: MIT
+#
+
+# The directory of the package.json file relative to the root directory, per
+# default assume there's a file directly in the root directory
+NPM_SRC_DIR ?= ""
+
+# The path to the package.json file
+NPM_MANIFEST_FILE ?= "${NPM_SRC_PATH}/package.json"
+
+# The path to package-lock.json file
+NPM_LOCK_FILE ?= "${@os.path.join(os.path.dirname(d.getVar('NPM_MANIFEST_FILE')), 'package-lock.json')}"
+
+# The URL of the npm registry
+NPM_REGISTRY ?= "https://registry.npmjs.org"
+
+# The option to npm install development dependencies
+NPM_INSTALL_DEV ?= "0"
+
+# The option to npm prune development dependencies after install
+NPM_PRUNE_DEV ?= "${@['0', '1'][oe.types.boolean(d.getVar('NPM_INSTALL_DEV'))]}"
+
+# The nodejs architecture of the target
+NPM_ARCH ?= "${@map_nodejs_arch(d.getVar("TARGET_ARCH"), d)}"
+
+inherit nodejs-arch python3native vendor
+
+DEPENDS:append = "nodejs-native"
+RDEPENDS:${PN}:append:class-target = "${@['', ' nodejs'][bb.data.inherits_class('allarch', d)]}"
+
+NPM_SRC_PATH = "${S}/${NPM_SRC_DIR}"
+NPM_SRC_SUBDIR = "${@os.path.relpath(d.getVar('NPM_SRC_PATH'), d.getVar('WORKDIR'))}"
+NPM_SRC_URI_FILE = "${VENDOR_DIR}/npm-source-uris.txt"
+SRC_URI_FILES:append = " ${NPM_SRC_URI_FILE}"
+
+NPM = "npm"
+NPM_CACHE = "${WORKDIR}/cache/npm"
+NPM_COMMON_FLAGS = "\
+ --offline \
+ --fund=false \
+ --audit=false \
+ --cache=${NPM_CACHE} \
+ --loglevel=silly \
+ --foreground-scripts \
+ --release \
+ --nodedir='${RECIPE_SYSROOT_NATIVE}${prefix_native}' \
+ --python=${PYTHON} \
+ --build-from-source \
+"
+NPM_BUILD_FLAGS = "\
+ ${NPM_COMMON_FLAGS} \
+ --arch=${NPM_TARGET_ARCH} \
+ --target_arch=${NPM_TARGET_ARCH} \
+"
+
+export NPM_CONFIG_USERCONFIG = "/dev/null"
+export NPM_CONFIG_GLOBALCONFIG = "null"
+
+def npm_src_uri(d, name=None, version=None, registry=None, subdir=None):
+ import oe.vendor.npm
+ if not name:
+ name = d.getVar("PN")
+ if not version:
+ version = d.getVar("PV")
+ if not registry:
+ registry = d.getVar("NPM_REGISTRY")
+ if not subdir:
+ subdir = os.path.basename(d.getVar("S"))
+ return oe.vendor.npm.determine_src_uri(registry, name, version, subdir)
+
+python vendor_npm_do_vendor_resolve() {
+ import oe.vendor
+ import oe.vendor.npm
+
+ lock_file_dir = d.getVar("NPM_LOCK_FILE")
+ lock_file_dir = get_early_source_dir(d, lock_file_dir)
+ registry = d.getVar("NPM_REGISTRY")
+ src_subdir = d.getVar("NPM_SRC_SUBDIR")
+ dev = oe.types.boolean(d.getVar("NPM_INSTALL_DEV"))
+ src_uris = oe.vendor.npm.resolve_src_uris(lock_file_dir, registry,
+ src_subdir, dev)
+ with open(d.getVar("NPM_SRC_URI_FILE"), "w") as f:
+ oe.vendor.dump(f, src_uris)
+}
+
+oe_run_npm() {
+ bbnote ${NPM} "$@"
+ ${NPM} "$@"
+}
+
+vendor_npm_do_compile() {
+ oe_run_npm rebuild ${NPM_BUILD_FLAGS}
+}
+do_compile[cleandirs] += "${NPM_CACHE}"
+
+vendor_npm_do_install() {
+ oe_run_npm install --global --prefix=${D}${exec_prefix}
+ rm ${D}${exec_prefix}/lib/node_modules/${BPN}
+ install -d ${D}${exec_prefix}/lib/node_modules/${BPN}
+ tar -C . -cf - --exclude-vcs . | \
+ tar -C ${D}${exec_prefix}/lib/node_modules/${BPN} --no-same-owner -xf -
+ rm -rf ${D}${exec_prefix}/lib/node_modules/${BPN}/node_modules/.bin
+ if "${@['false', 'true'][oe.types.boolean(d.getVar('NPM_PRUNE_DEV'))]}"; then
+ (cd ${D}${exec_prefix}/lib/node_modules; oe_run_npm prune --omit=dev)
+ fi
+}
+
+FILES:${PN} += " \
+ ${nonarch_libdir}/node_modules/${BPN} \
+"
+
+EXPORT_FUNCTIONS do_vendor_resolve do_compile do_install