From 89c68e5438745a9d829776bba7d54fe488adf54f Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Sat, 26 Sep 2026 05:10:59 +0200 Subject: [PATCH 1/2] hatch_build.py: install the interface's and the plugin's dependencies when absent They are packages of their own, not workspace members, so nothing hoists them; after a git clean the interface resolved whatever rsbuild the workspace hoisted (1.7, with an SWC plugin mismatch: 'The version of the SWC Wasm plugin you're using might not be compatible') instead of the 2.x it declares. Installed when node_modules is missing, as CI does. Proven from a clean export with app/node_modules wiped: the wheel builds and carries both halves. --- hatch_build.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hatch_build.py b/hatch_build.py index 0f4e9d5..37d9acc 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -47,8 +47,19 @@ def initialize(self, version: str, build_data: dict) -> None: # noqa: ARG002 f"The wheel needs the built {' and '.join(missing)} under share/, and npm is not " "on PATH to build them. Run `make build` first, or set DATALAYER_DECKS_SKIP_JS=1." ) - if not (ROOT / "node_modules").is_dir() and not (ROOT.parent.parent.parent / "node_modules").is_dir(): + workspace = ROOT.parent.parent.parent / "node_modules" + if not (ROOT / "node_modules").is_dir() and not workspace.is_dir(): subprocess.run([npm, "install"], cwd=ROOT, check=True) + # The interface and the plugin it bundles are packages of their own, + # not members of the workspace: their dependencies are never hoisted, + # and the interface's bundler in particular has to be its own + # (a `git clean` leaves app/ resolving whatever rsbuild the workspace + # hoisted, which is not the one it was written against). Installed + # when absent, which is also what CI does. + for package in (ROOT / "app", ROOT / "plugins" / "ai-agents"): + if not (package / "node_modules").is_dir(): + self.app.display_info(f"Installing {package.relative_to(ROOT)}'s dependencies ...") + subprocess.run([npm, "install", "--no-audit", "--no-fund"], cwd=package, check=True) self.app.display_info(f"Building the {' and '.join(missing)} into share/ ...") subprocess.run([npm, "run", "build:all"], cwd=ROOT, check=True) for name, marker in (("interface", APP), ("container", CONTAINER)): From b330618d2f3016ca19320e34461116fa22acef6a Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Sat, 26 Sep 2026 05:15:33 +0200 Subject: [PATCH 2/2] hatch_build.py: the plugin is a workspace member when there is a workspace plugins/* is in the workspace's globs, so its dependencies are hoisted and an npm install inside it would redirect to the whole workspace; only a plain checkout installs it. The interface is never a member. --- hatch_build.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/hatch_build.py b/hatch_build.py index 37d9acc..1924711 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -50,13 +50,18 @@ def initialize(self, version: str, build_data: dict) -> None: # noqa: ARG002 workspace = ROOT.parent.parent.parent / "node_modules" if not (ROOT / "node_modules").is_dir() and not workspace.is_dir(): subprocess.run([npm, "install"], cwd=ROOT, check=True) - # The interface and the plugin it bundles are packages of their own, - # not members of the workspace: their dependencies are never hoisted, - # and the interface's bundler in particular has to be its own - # (a `git clean` leaves app/ resolving whatever rsbuild the workspace - # hoisted, which is not the one it was written against). Installed - # when absent, which is also what CI does. - for package in (ROOT / "app", ROOT / "plugins" / "ai-agents"): + # The interface (app/) is a package of its own, never a workspace + # member: nothing hoists its dependencies, and its bundler has to be + # its own -- after a `git clean`, app/ otherwise resolves whatever + # rsbuild the workspace hoisted, which is not the one it was written + # against. The plugin is a workspace member when there is a workspace + # (hoisted, nothing to install: `npm install` inside a member would + # redirect to the whole workspace) and a package of its own in a plain + # checkout, where CI installs it too. + own = [ROOT / "app"] + if not workspace.is_dir(): + own.append(ROOT / "plugins" / "ai-agents") + for package in own: if not (package / "node_modules").is_dir(): self.app.display_info(f"Installing {package.relative_to(ROOT)}'s dependencies ...") subprocess.run([npm, "install", "--no-audit", "--no-fund"], cwd=package, check=True)