From 7ce1deaf226ec2699e87d96ce40562f861627ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C5=ABdolfs=20O=C5=A1i=C5=86=C5=A1?= Date: Mon, 26 Oct 2026 18:26:00 +0100 Subject: [PATCH] Build Docker e2e and desktop releases from the workspace root --- desktop/scripts/build-artifacts | 43 ++++++++++++++++------- explorer/.dockerignore | 13 ------- explorer/Dockerfile.e2e | 16 ++++++--- explorer/Dockerfile.e2e.dockerignore | 9 +++++ explorer/docker-compose.e2e.yml | 42 +++++++++++++++------- explorer/scripts/docker-e2e-entrypoint.sh | 11 +++--- 6 files changed, 86 insertions(+), 48 deletions(-) delete mode 100644 explorer/.dockerignore create mode 100644 explorer/Dockerfile.e2e.dockerignore diff --git a/desktop/scripts/build-artifacts b/desktop/scripts/build-artifacts index 81f5aed31..1f9913539 100755 --- a/desktop/scripts/build-artifacts +++ b/desktop/scripts/build-artifacts @@ -16,6 +16,8 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +# The npm workspace that holds desktop and the shared UI code. +REPO_DIR="$(cd "$PROJECT_DIR/.." && pwd)" # ---- Flags ---- @@ -65,8 +67,9 @@ ensure_image() { VERSION="$(jq -r '.version' "$PROJECT_DIR/crates/radicle-tauri/tauri.conf.json")" RELEASE_SHA="$(git -C "$PROJECT_DIR" rev-parse HEAD)" SHORT_SHA="$(git -C "$PROJECT_DIR" rev-parse --short=8 HEAD)" -# The Linux containers only mount desktop/, which has no .git of its own, so -# build.rs can't ask git for the commit. Pass it in the way build.rs would get it. +# git in the Linux containers may refuse the bind-mounted checkout, which the +# container's root doesn't own, so build.rs can't rely on asking it for the +# commit. Pass it in the way build.rs would get it. GIT_HEAD="$(git -C "$PROJECT_DIR" rev-parse --short HEAD)" RELEASE_DIR="$PROJECT_DIR/releases/v$VERSION" @@ -93,8 +96,10 @@ fi if [[ "$CLEAN" == true ]]; then step "Cleaning build artifacts" rm -rf "$PROJECT_DIR/target-release" - rm -rf "$PROJECT_DIR/node_modules" - podman volume rm radicle-desktop-linux-target radicle-desktop-linux-nm radicle-desktop-linux-cargo 2>/dev/null || true + rm -rf "$REPO_DIR/node_modules" "$REPO_DIR"/{explorer,desktop,shared/ui}/node_modules + podman volume rm radicle-desktop-linux-target radicle-desktop-linux-cargo \ + radicle-desktop-linux-nm radicle-desktop-linux-nm-explorer \ + radicle-desktop-linux-nm-desktop radicle-desktop-linux-nm-shared 2>/dev/null || true podman image rm radicle-desktop-release-builder 2>/dev/null || true echo " Cleaned" fi @@ -103,8 +108,9 @@ fi if [[ "$BUILD_DMG" == true ]]; then step "Building macOS aarch64 DMG" + cd "$REPO_DIR" + npm install -w desktop cd "$PROJECT_DIR" - npm install CARGO_TARGET_DIR="$PROJECT_DIR/target-release" \ npm exec -- tauri build --bundles dmg cp "target-release/release/bundle/dmg/Radicle_${VERSION}_aarch64.dmg" \ @@ -122,24 +128,32 @@ fi # After a Rust toolchain bump or dependency change that confuses incremental # compilation: podman volume rm radicle-desktop-linux-target radicle-desktop-linux-cargo # After a major npm dependency change: podman volume rm radicle-desktop-linux-nm +# radicle-desktop-linux-nm-explorer radicle-desktop-linux-nm-desktop +# radicle-desktop-linux-nm-shared +# +# Every workspace package's node_modules is a volume, so that npm inside the +# container never writes Linux builds into the host's checkout. if [[ "$BUILD_DEB" == true ]]; then step "Building Linux amd64 deb" ensure_image "radicle-desktop-release-builder" "Dockerfile.release" "linux/arm64" podman run --rm \ - -v "$PROJECT_DIR:/workspace:z" \ - -v radicle-desktop-linux-target:/workspace/target:z \ + -v "$REPO_DIR:/workspace:z" \ + -v radicle-desktop-linux-target:/workspace/desktop/target:z \ -v radicle-desktop-linux-nm:/workspace/node_modules:z \ + -v radicle-desktop-linux-nm-explorer:/workspace/explorer/node_modules:z \ + -v radicle-desktop-linux-nm-desktop:/workspace/desktop/node_modules:z \ + -v radicle-desktop-linux-nm-shared:/workspace/shared/ui/node_modules:z \ -v radicle-desktop-linux-cargo:/root/.cargo/registry:z \ -v "$RELEASE_DIR:/output:z" \ - -w /workspace \ + -w /workspace/desktop \ -e CI=true \ -e GIT_HEAD="$GIT_HEAD" \ radicle-desktop-release-builder \ bash -c " set -euo pipefail - npm ci + (cd /workspace && npm ci -w desktop) npm exec -- tauri build --target x86_64-unknown-linux-gnu --bundles deb cp \"target/x86_64-unknown-linux-gnu/release/bundle/deb/radicle-desktop_${VERSION}_amd64.deb\" /output/ " @@ -162,16 +176,19 @@ if [[ "$BUILD_APPIMAGE" == true ]]; then podman run --rm \ --privileged \ - -v "$PROJECT_DIR:/workspace:z" \ - -v radicle-desktop-linux-target:/workspace/target:z \ + -v "$REPO_DIR:/workspace:z" \ + -v radicle-desktop-linux-target:/workspace/desktop/target:z \ -v radicle-desktop-linux-nm:/workspace/node_modules:z \ + -v radicle-desktop-linux-nm-explorer:/workspace/explorer/node_modules:z \ + -v radicle-desktop-linux-nm-desktop:/workspace/desktop/node_modules:z \ + -v radicle-desktop-linux-nm-shared:/workspace/shared/ui/node_modules:z \ -v radicle-desktop-linux-cargo:/root/.cargo/registry:z \ -v "$RELEASE_DIR:/output:z" \ - -w /workspace \ + -w /workspace/desktop \ -e CI=true \ -e GIT_HEAD="$GIT_HEAD" \ radicle-desktop-release-builder \ - bash /workspace/scripts/appimage-build + bash /workspace/desktop/scripts/appimage-build # Strip the version so the filename matches what the website serves # (the DMG above is renamed the same way). diff --git a/explorer/.dockerignore b/explorer/.dockerignore deleted file mode 100644 index a0daf0291..000000000 --- a/explorer/.dockerignore +++ /dev/null @@ -1,13 +0,0 @@ -node_modules -build -.svelte-kit -.git -tests/tmp -tests/artifacts -tests/visual/snapshots -data.ms -dumps -radicle-httpd/target -radicle-search/target -target -.wrangler diff --git a/explorer/Dockerfile.e2e b/explorer/Dockerfile.e2e index c2e5830be..12113db94 100644 --- a/explorer/Dockerfile.e2e +++ b/explorer/Dockerfile.e2e @@ -26,15 +26,21 @@ WORKDIR /work # Install Chromium plus its apt-level runtime dependencies, pinned to the # Playwright version in package-lock.json so the baked browser matches the # @playwright/test that npm ci installs into the volume at runtime. Copying -# only the lockfiles keeps this layer cached across source changes. +# only the npm manifests keeps this layer cached across source changes. The +# build context is the repository root, which holds the npm workspace: npm ci +# checks the lockfile against every workspace's package.json, even when it +# installs only explorer. # --ignore-scripts: this layer only warms the browser cache, so dependency # install scripts are not necessary. -COPY package.json package-lock.json ./ -RUN npm ci --ignore-scripts \ +COPY package.json package-lock.json .npmrc ./ +COPY shared/ui/package.json shared/ui/ +COPY explorer/package.json explorer/ +COPY desktop/package.json desktop/ +RUN npm ci -w explorer --ignore-scripts \ && npx playwright install --with-deps chromium \ - && rm -rf node_modules + && rm -rf node_modules explorer/node_modules -ENTRYPOINT ["/work/scripts/docker-e2e-entrypoint.sh"] +ENTRYPOINT ["/work/explorer/scripts/docker-e2e-entrypoint.sh"] # --- local stage: adds Rust so the entrypoint can build radicle-httpd --- FROM base AS local diff --git a/explorer/Dockerfile.e2e.dockerignore b/explorer/Dockerfile.e2e.dockerignore new file mode 100644 index 000000000..49464e8d8 --- /dev/null +++ b/explorer/Dockerfile.e2e.dockerignore @@ -0,0 +1,9 @@ +# The build context is the repository root, and the image only copies the npm +# manifests: the repository itself is bind-mounted at runtime. +* +!package.json +!package-lock.json +!.npmrc +!shared/ui/package.json +!explorer/package.json +!desktop/package.json diff --git a/explorer/docker-compose.e2e.yml b/explorer/docker-compose.e2e.yml index b26bb1dea..73ccf2bfc 100644 --- a/explorer/docker-compose.e2e.yml +++ b/explorer/docker-compose.e2e.yml @@ -1,6 +1,6 @@ x-e2e-common: &e2e-common init: true - working_dir: /work + working_dir: /work/explorer environment: PLAYWRIGHT_BROWSERS_PATH: /ms-playwright @@ -9,35 +9,53 @@ services: e2e: <<: *e2e-common build: - context: . - dockerfile: Dockerfile.e2e + # The repository root, which holds the npm workspace. + context: .. + dockerfile: explorer/Dockerfile.e2e target: base volumes: - # Repo bind-mounted so code edits, test artifacts, and the - # binary/fixture cache (tests/tmp) are shared with the host. - - .:/work - # Container-private node_modules: keeps the host's untouched and - # avoids a glibc mismatch with host-built native modules. + # Repo bind-mounted so code edits and test artifacts are shared with the + # host. + - ..:/work + # Container-private node_modules for every workspace package: keeps the + # host's untouched and avoids a glibc mismatch with host-built native + # modules. npm hoists most packages to the root and nests the rest under + # the package that needs them. - e2e-node-modules:/work/node_modules + - e2e-explorer-node-modules:/work/explorer/node_modules + - e2e-desktop-node-modules:/work/desktop/node_modules + - e2e-shared-node-modules:/work/shared/ui/node_modules + # Container-private binaries and fixtures: the host's binaries may be + # built for another OS, and Radicle storage has to live on the + # container's own filesystem rather than a bind mount from a macOS host. + - e2e-tmp:/work/explorer/tests/tmp # Compiles radicle-httpd from source and runs the suite against it. # Used via `scripts/test-e2e-docker --local` when testing backend changes. e2e-local: <<: *e2e-common build: - context: . - dockerfile: Dockerfile.e2e + context: .. + dockerfile: explorer/Dockerfile.e2e target: local volumes: - - .:/work + - ..:/work - e2e-node-modules:/work/node_modules + - e2e-explorer-node-modules:/work/explorer/node_modules + - e2e-desktop-node-modules:/work/desktop/node_modules + - e2e-shared-node-modules:/work/shared/ui/node_modules + - e2e-tmp:/work/explorer/tests/tmp # Container-private cargo crate cache and workspace build dir, so the # Rust build is incremental across runs and never mixes with the # host's (different glibc/rustc) target artifacts. - e2e-cargo-registry:/usr/local/cargo/registry - - e2e-httpd-target:/work/target + - e2e-httpd-target:/work/explorer/target volumes: e2e-node-modules: + e2e-explorer-node-modules: + e2e-desktop-node-modules: + e2e-shared-node-modules: + e2e-tmp: e2e-cargo-registry: e2e-httpd-target: diff --git a/explorer/scripts/docker-e2e-entrypoint.sh b/explorer/scripts/docker-e2e-entrypoint.sh index 09ee8b9eb..6b63ee530 100755 --- a/explorer/scripts/docker-e2e-entrypoint.sh +++ b/explorer/scripts/docker-e2e-entrypoint.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Entrypoint for the e2e Docker container. Installs npm deps into the -# container-private node_modules volume (only when package-lock.json changes), +# container-private node_modules volumes (only when the workspace's +# package-lock.json changes), # ensures the Radicle test binaries are present, then runs Playwright. Any # arguments are forwarded to the test command. # @@ -8,7 +9,7 @@ # source and tests the frontend against it) instead of test:e2e. set -euo pipefail -cd /work +cd /work/explorer # The container runs as root, so anything it writes into the bind-mounted repo # would otherwise be root-owned on the host. Hand those paths back to the @@ -23,11 +24,11 @@ cleanup() { } trap cleanup EXIT -LOCK_HASH=$(md5sum package-lock.json | cut -d' ' -f1) -STAMP=node_modules/.lockhash +LOCK_HASH=$(md5sum ../package-lock.json | cut -d' ' -f1) +STAMP=../node_modules/.lockhash if [ ! -f "$STAMP" ] || [ "$(cat "$STAMP")" != "$LOCK_HASH" ]; then echo "📦 Installing npm dependencies..." - npm ci + (cd .. && npm ci -w explorer) echo "$LOCK_HASH" >"$STAMP" else echo "📦 npm dependencies up to date."