From 163899f3e9079f1ffac6fd08b4d9f220a017f211 Mon Sep 17 00:00:00 2001 From: jm Date: Tue, 19 May 2026 22:27:51 +0200 Subject: [PATCH 1/8] Unify docker compose so local dev needs only an .env, no override yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five small changes that make `docker/docker-compose.yml` + `docker-compose-rest.yml` the same artifact for the pyinfra-driven production deploy AND for a developer running the stack on their own machine. The prior gap was that compose's defaults encoded production-only assumptions; local-dev had to override them in a separate file, which was the friction this fixes. Defaults preserve every existing production behaviour bit-for-bit (verified with `docker compose -f ... -f ... config` against an INSTANCE-only env: same subnet, same proxies trusted range, same host_ip 127.0.0.1, same dspace.ui.url default, same CORS default that already matched dspace.ui.url implicitly). Changes: 1. Dockerfile: also copy `docker/dspace-ui.json` to `/app/dspace-ui.json` so the locally-built image matches Dockerfile.dist's layout. Compose's hardcoded `pm2-runtime start dspace-ui.json` (no `docker/` prefix) was ENOENT-looping on locally-built containers. The Dockerfile CMD now matches compose's entrypoint too. 2. docker-compose.yml: add `extra_hosts: host.docker.internal:host-gateway` to the FE service. No-op on production (BE is reached via dev-5.pc DNS, not host.docker.internal). On local dev it lets the FE container's SSR reach a BE port published on the host machine. 3. docker-compose-rest.yml: parameterise the dspacenet subnet via `${DSPACE_SUBNET_PREFIX:-172.2${INSTANCE}}.0.0/16`. Default unchanged. Local dev can move off the crowded 172.2X range with a one-line env override. `proxies.trusted.ipranges` follows the same prefix automatically. 4. docker-compose-rest.yml: add explicit `rest__P__cors__P__allowed__D__origins: ${REST_CORS_ALLOWED_ORIGINS:-${UI_URL:-...}}` to the dspace service env. Default value matches the implicit `${dspace.ui.url}` fallback that `dspace/config/modules/rest.cfg` already computes, so production CORS is byte-identical. Local dev can extend with multiple browser origins (localhost + host.docker.internal + LAN IP) so the preflight succeeds regardless of which hostname resolves the BE first. 5. New `docker/.env.local.example`: documented starter for local-dev. Sets the four env vars above (HOST_IP=0.0.0.0, DSPACE_HOST=host.docker.internal, DSPACE_SUBNET_PREFIX, REST_CORS_ALLOWED_ORIGINS) plus image set and INSTANCE. Production deploys do not use this file — pyinfra still templates its own .env from devops/infra/pyinfra/assets/dspace/dspace-envs/v7/.env.j2. Background: I wrote the diagnosis up in detail when investigating the home-page flicker (separate PR #1287). The death-by-a-thousand-cuts of unstated assumptions in the compose files is what made starting the stack locally a slog. This PR removes the slog without touching the production deploy path. --- Dockerfile | 10 +++++- docker/.env.local.example | 60 ++++++++++++++++++++++++++++++++++ docker/docker-compose-rest.yml | 17 +++++++--- docker/docker-compose.yml | 9 +++++ 4 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 docker/.env.local.example diff --git a/Dockerfile b/Dockerfile index e1e72cbf43f..206540f1a97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,5 +28,13 @@ ENV NODE_ENV=development RUN apk add tzdata RUN yarn build:prod RUN npm install pm2 -g -CMD /bin/sh -c "pm2-runtime start docker/dspace-ui.json > /dev/null 2> /dev/null" + +# Mirror Dockerfile.dist's layout so docker-compose.yml's entrypoint +# (`pm2-runtime start dspace-ui.json`, no `docker/` prefix) works for both +# the locally-built dev image and the published dist image. Before this, +# locally-built containers ENOENT-looped because compose's entrypoint +# pointed at the dist path while the file sat at /app/docker/dspace-ui.json. +RUN cp docker/dspace-ui.json /app/dspace-ui.json + +CMD /bin/sh -c "pm2-runtime start dspace-ui.json > /dev/null 2> /dev/null" diff --git a/docker/.env.local.example b/docker/.env.local.example new file mode 100644 index 00000000000..32c4a10c74e --- /dev/null +++ b/docker/.env.local.example @@ -0,0 +1,60 @@ +# Example local-dev .env for docker/docker-compose.yml + docker/docker-compose-rest.yml. +# +# Production / pyinfra deploys do NOT use this file — they template their own .env from +# devops/infra/pyinfra/assets/dspace/dspace-envs/v7/.env.j2 and rely on every var here +# being unset or matching the production default. +# +# Usage: +# cp docker/.env.local.example docker/.env.local +# docker compose --env-file docker/.env.local -p dspace-7 \ +# -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d +# +# Tear down: +# docker compose --env-file docker/.env.local -p dspace-7 \ +# -f docker/docker-compose.yml -f docker/docker-compose-rest.yml down -v +# +# All variables below are documented; uncomment / change only what you need. + +# Single-digit suffix used by every port / container name / subnet. 5 and 8 are reserved +# by .github/workflows/deploy.yml; pick anything 1-9 that isn't already on your machine. +INSTANCE=7 + +# Hostname that resolves both inside the FE container (via the extra_hosts entry added +# in docker-compose.yml) AND from the browser on the host machine. Docker Desktop on +# Mac/Win automatically maps host.docker.internal in the host's hosts file; on Linux +# add it yourself or replace with your LAN IP. +DSPACE_HOST=host.docker.internal +DSPACE_REST_PORT=8087 +REST_URL=http://host.docker.internal:8087/server +UI_URL=http://host.docker.internal:4007 + +# Bind published ports on all interfaces (default 127.0.0.1 is right for production +# behind nginx but blocks the browser from reaching host.docker.internal:PORT, since +# host.docker.internal resolves to the host's LAN IP, not 127.0.0.1). +HOST_IP=0.0.0.0 + +# Move the dspacenet subnet off the 172.2X/16 production default. The 172.16.0.0/12 +# range is commonly already occupied by unrelated docker-compose projects on dev +# machines; 10.10X.0.0/16 is in the private range and rarely collides. +DSPACE_SUBNET_PREFIX=10.10${INSTANCE} + +# Allow the obvious local browser origins. The BE default is just ${UI_URL}, which on +# its own isn't enough when the browser uses localhost and the SSR FE uses +# host.docker.internal — both originate CORS preflights that need to pass. +REST_CORS_ALLOWED_ORIGINS=http://localhost:4007,http://host.docker.internal:4007,http://127.0.0.1:4007 + +# Image set. Use the upstream `dspace/*` images when seeding the DB with the standard +# entities SQL (docker/db.entities.yml) — the dataquest BE has CLARIN-specific tables +# that the upstream SQL dump doesn't create. Switch to the dataquest set when running +# against a dataquest-compatible database dump. +DSPACE_UI_IMAGE=dataquest/dspace-angular:dspace-7_x +DSPACE_REST_IMAGE=dspace/dspace:dspace-7_x +DSPACE_DB_IMAGE=dspace/dspace-postgres-pgcrypto:dspace-7_x +DSPACE_SOLR_IMAGE=dspace/dspace-solr:dspace-7_x + +# Required by docker/db.entities.yml's image expansion if you layer that file in to +# preload the entities demo data (the loadsql variant downloads + imports the SQL +# dump on first start). +DOCKER_REGISTRY=docker.io +DOCKER_OWNER=dspace +DSPACE_VER=dspace-7_x diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index bade0ecf24a..e39082ee7dc 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -17,8 +17,10 @@ networks: ipam: config: # Define a custom subnet for our DSpace network, so that we can easily trust requests from host to container. - # If you customize this value, be sure to customize the 'proxies.trusted.ipranges' env variable below. - - subnet: 172.2${INSTANCE}.0.0/16 + # The 172.2X.0.0/16 default is the historical pyinfra-deployed layout and stays unchanged for + # production. Local-dev hosts often already have the 172.2X range occupied by unrelated compose + # projects; override with DSPACE_SUBNET_PREFIX (e.g. `10.10${INSTANCE}`) in the .env file. + - subnet: ${DSPACE_SUBNET_PREFIX:-172.2${INSTANCE}}.0.0/16 services: # DSpace (backend) webapp container dspace: @@ -40,8 +42,15 @@ services: # solr.server: Ensure we are using the 'dspacesolr' image for Solr solr__P__server: http://dspacesolr:8983/solr # proxies.trusted.ipranges: This setting is required for a REST API running in Docker to trust requests - # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above. - proxies__P__trusted__P__ipranges: '172.2${INSTANCE}.0' + # from the host machine. This IP range MUST correspond to the 'dspacenet' subnet defined above — + # follows DSPACE_SUBNET_PREFIX so an override there propagates here automatically. + proxies__P__trusted__P__ipranges: '${DSPACE_SUBNET_PREFIX:-172.2${INSTANCE}}.0' + # rest.cors.allowed-origins: comma-separated origin list the BE will respond to for CORS preflight. + # Default matches dspace.ui.url (i.e. UI_URL) — identical to the implicit default in + # dspace/config/modules/rest.cfg, so production / pyinfra behaviour is unchanged. + # Local-dev sets REST_CORS_ALLOWED_ORIGINS to also include host.docker.internal and localhost + # so the browser preflight succeeds regardless of which name resolves the BE. + rest__P__cors__P__allowed__D__origins: ${REST_CORS_ALLOWED_ORIGINS:-${UI_URL:-http://127.0.0.1:4000}} #S3 config assetstore__P__index__P__primary: ${S3_STORAGE:-0} assetstore__P__s3__P__enabled: ${S3_ENABLED:-false} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index ea0e5f30f1b..37fd7f446dc 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -34,6 +34,15 @@ services: build: context: .. dockerfile: Dockerfile + # host.docker.internal lets the FE container reach the BE through the host + # gateway when both are not on the same docker network (typical local-dev + # case where the FE is just the one container started from this file and + # the BE is reached at a host port). Docker Desktop on Mac/Windows already + # injects this; on Linux Docker (>= 20.10) `host-gateway` makes it resolve + # to the host. In production / pyinfra deploys this is a no-op because the + # BE is reached via its public DNS name (dev-5.pc etc.), not host.docker.internal. + extra_hosts: + - host.docker.internal:host-gateway networks: dspacenet: entrypoint: ${FE_CMD:-/bin/sh -c "pm2-runtime start dspace-ui.json > /dev/null 2> /dev/null"} From f3b44667daac7566b2ae45c3c66d5858c6b050d8 Mon Sep 17 00:00:00 2001 From: jm Date: Tue, 19 May 2026 23:03:13 +0200 Subject: [PATCH 2/8] Address Copilot review - docker/.env.local.example: make every port follow INSTANCE via nested ${VAR} substitution. Previously hardcoded 4007/8087 silently drifted from the compose-published ports when a developer changed INSTANCE. Verified: changing INSTANCE=7 -> 6 now retargets the entire stack (REST 8086, FE 4006, CORS origins all 400N) with no other edits. - .gitignore: ignore docker/.env.local so the per-developer copy of the example doesn't get committed by accident. The .example template stays committed. --- .gitignore | 4 ++++ docker/.env.local.example | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index bdab34cb367..e77268e6010 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,7 @@ python_data_import/debug.log.txt python_data_import/logs.txt python_data_import/date.txt */__pycache__/ + +# Local-dev compose env created by copying docker/.env.local.example. Per-developer, +# never committed. The .example template IS committed. +/docker/.env.local diff --git a/docker/.env.local.example b/docker/.env.local.example index 32c4a10c74e..ddecb9081a6 100644 --- a/docker/.env.local.example +++ b/docker/.env.local.example @@ -17,6 +17,8 @@ # Single-digit suffix used by every port / container name / subnet. 5 and 8 are reserved # by .github/workflows/deploy.yml; pick anything 1-9 that isn't already on your machine. +# All ports below follow this digit via compose's nested ${VAR} substitution, so changing +# INSTANCE alone re-targets the whole stack — no other edits required here. INSTANCE=7 # Hostname that resolves both inside the FE container (via the extra_hosts entry added @@ -24,9 +26,9 @@ INSTANCE=7 # Mac/Win automatically maps host.docker.internal in the host's hosts file; on Linux # add it yourself or replace with your LAN IP. DSPACE_HOST=host.docker.internal -DSPACE_REST_PORT=8087 -REST_URL=http://host.docker.internal:8087/server -UI_URL=http://host.docker.internal:4007 +DSPACE_REST_PORT=808${INSTANCE} +REST_URL=http://host.docker.internal:808${INSTANCE}/server +UI_URL=http://host.docker.internal:400${INSTANCE} # Bind published ports on all interfaces (default 127.0.0.1 is right for production # behind nginx but blocks the browser from reaching host.docker.internal:PORT, since @@ -41,7 +43,7 @@ DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # Allow the obvious local browser origins. The BE default is just ${UI_URL}, which on # its own isn't enough when the browser uses localhost and the SSR FE uses # host.docker.internal — both originate CORS preflights that need to pass. -REST_CORS_ALLOWED_ORIGINS=http://localhost:4007,http://host.docker.internal:4007,http://127.0.0.1:4007 +REST_CORS_ALLOWED_ORIGINS=http://localhost:400${INSTANCE},http://host.docker.internal:400${INSTANCE},http://127.0.0.1:400${INSTANCE} # Image set. Use the upstream `dspace/*` images when seeding the DB with the standard # entities SQL (docker/db.entities.yml) — the dataquest BE has CLARIN-specific tables From 0e179c0cd0267ff3195a96a3cb95539426303148 Mon Sep 17 00:00:00 2001 From: jm Date: Wed, 3 Jun 2026 19:24:38 +0200 Subject: [PATCH 3/8] docker: tighten .env.local.example docs + fix stale paths (review follow-up) - Derive COMPOSE_PROJECT_NAME from INSTANCE so changing INSTANCE alone really re-targets the whole stack (drop the hardcoded -p dspace-7 from the usage block). - DRY REST_URL/UI_URL onto ${DSPACE_HOST}; note that a LAN-IP switch must also be added to REST_CORS_ALLOWED_ORIGINS. - HOST_IP=0.0.0.0: spell out the LAN-exposure risk (BE, JVM debug, Postgres, Solr); native-FE users keep 127.0.0.1. - Document the Compose-v2 requirement and the top-down (backward-only) env-file interpolation ordering. - rest.cors.allowed-origins comment: reframe as a behavior-preserving always-on env override (value equals rest.cfg's ${dspace.ui.url} default) rather than "byte-identical". - Fix the stale commented dspace-ui.json volume path (/app/docker -> /app) to match the post-cp layout. Co-Authored-By: Claude Opus 4.8 --- docker/.env.local.example | 34 ++++++++++++++++++++++++++-------- docker/docker-compose-rest.yml | 9 +++++++-- docker/docker-compose.yml | 2 +- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/docker/.env.local.example b/docker/.env.local.example index ddecb9081a6..3e02c40e781 100644 --- a/docker/.env.local.example +++ b/docker/.env.local.example @@ -4,13 +4,17 @@ # devops/infra/pyinfra/assets/dspace/dspace-envs/v7/.env.j2 and rely on every var here # being unset or matching the production default. # -# Usage: +# Requires Docker Compose v2 (`docker compose`, not the legacy `docker-compose` v1): the +# nested ${INSTANCE} substitution inside this env file is a v2 feature. +# +# Usage (the project name follows INSTANCE via COMPOSE_PROJECT_NAME below, so --env-file +# is the only thing you need to pass): # cp docker/.env.local.example docker/.env.local -# docker compose --env-file docker/.env.local -p dspace-7 \ +# docker compose --env-file docker/.env.local \ # -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d # -# Tear down: -# docker compose --env-file docker/.env.local -p dspace-7 \ +# Tear down (-v also wipes the DB/Solr volumes — omit it to keep your data): +# docker compose --env-file docker/.env.local \ # -f docker/docker-compose.yml -f docker/docker-compose-rest.yml down -v # # All variables below are documented; uncomment / change only what you need. @@ -19,20 +23,33 @@ # by .github/workflows/deploy.yml; pick anything 1-9 that isn't already on your machine. # All ports below follow this digit via compose's nested ${VAR} substitution, so changing # INSTANCE alone re-targets the whole stack — no other edits required here. +# NOTE: keep INSTANCE defined ABOVE every line that references it. Env-file interpolation +# is top-down: a forward reference silently expands to an empty string, with no error. INSTANCE=7 +# Project name (the `-p` value / container-name prefix). Deriving it from INSTANCE keeps +# `up`, `down`, and `docker compose ps` consistent when you change INSTANCE, without +# passing -p on the command line. +COMPOSE_PROJECT_NAME=dspace-${INSTANCE} + # Hostname that resolves both inside the FE container (via the extra_hosts entry added # in docker-compose.yml) AND from the browser on the host machine. Docker Desktop on # Mac/Win automatically maps host.docker.internal in the host's hosts file; on Linux -# add it yourself or replace with your LAN IP. +# add it yourself or replace with your LAN IP. REST_URL/UI_URL below follow ${DSPACE_HOST} +# so changing this one value is enough — but if you switch to a LAN IP, also add that +# origin to REST_CORS_ALLOWED_ORIGINS (it is not derived from DSPACE_HOST). DSPACE_HOST=host.docker.internal DSPACE_REST_PORT=808${INSTANCE} -REST_URL=http://host.docker.internal:808${INSTANCE}/server -UI_URL=http://host.docker.internal:400${INSTANCE} +REST_URL=http://${DSPACE_HOST}:808${INSTANCE}/server +UI_URL=http://${DSPACE_HOST}:400${INSTANCE} # Bind published ports on all interfaces (default 127.0.0.1 is right for production # behind nginx but blocks the browser from reaching host.docker.internal:PORT, since # host.docker.internal resolves to the host's LAN IP, not 127.0.0.1). +# SECURITY: 0.0.0.0 exposes EVERY published port to your LAN — the BE, the JVM debug port +# (800X), Postgres (543X, password 'dspace'), and Solr (898X). Dev machines only; never on +# an untrusted network. If you run the FE natively on the host instead of in a container, +# you don't need this — keep HOST_IP=127.0.0.1 (see AGENTS.md). HOST_IP=0.0.0.0 # Move the dspacenet subnet off the 172.2X/16 production default. The 172.16.0.0/12 @@ -42,7 +59,8 @@ DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # Allow the obvious local browser origins. The BE default is just ${UI_URL}, which on # its own isn't enough when the browser uses localhost and the SSR FE uses -# host.docker.internal — both originate CORS preflights that need to pass. +# host.docker.internal — both originate CORS preflights that need to pass. If you set +# DSPACE_HOST to a LAN IP, add http://:400${INSTANCE} here too. REST_CORS_ALLOWED_ORIGINS=http://localhost:400${INSTANCE},http://host.docker.internal:400${INSTANCE},http://127.0.0.1:400${INSTANCE} # Image set. Use the upstream `dspace/*` images when seeding the DB with the standard diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index e39082ee7dc..74553f773ae 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -46,8 +46,13 @@ services: # follows DSPACE_SUBNET_PREFIX so an override there propagates here automatically. proxies__P__trusted__P__ipranges: '${DSPACE_SUBNET_PREFIX:-172.2${INSTANCE}}.0' # rest.cors.allowed-origins: comma-separated origin list the BE will respond to for CORS preflight. - # Default matches dspace.ui.url (i.e. UI_URL) — identical to the implicit default in - # dspace/config/modules/rest.cfg, so production / pyinfra behaviour is unchanged. + # When REST_CORS_ALLOWED_ORIGINS is unset this defaults to ${UI_URL} (same literal as + # dspace.ui.url above), which equals the implicit ${dspace.ui.url} default that + # dspace/config/modules/rest.cfg already computes — so the effective CORS allow-list is + # unchanged for production / pyinfra. NOTE: this now sends rest.cors.allowed-origins as an + # explicit env override on every deploy (previously the BE derived it internally from + # rest.cfg); the value is the same, but it will shadow rest.cfg if that ever ships a + # non-default cors list. # Local-dev sets REST_CORS_ALLOWED_ORIGINS to also include host.docker.internal and localhost # so the browser preflight succeeds regardless of which name resolves the BE. rest__P__cors__P__allowed__D__origins: ${REST_CORS_ALLOWED_ORIGINS:-${UI_URL:-http://127.0.0.1:4000}} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 37fd7f446dc..d760791eb42 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -30,7 +30,7 @@ services: volumes: - ./config.prod.yml:/app/config/config.prod.yml # - ./aai.js:/app/dist/browser/aai.js -# - ./dspace-ui.json:/app/docker/dspace-ui.json:rw +# - ./dspace-ui.json:/app/dspace-ui.json:rw build: context: .. dockerfile: Dockerfile From c3ca865e7d9a82c14f841e2af1571a634ab65a55 Mon Sep 17 00:00:00 2001 From: jm Date: Wed, 3 Jun 2026 19:24:46 +0200 Subject: [PATCH 4/8] =?UTF-8?q?docs:=20add=20AGENTS.md=20=E2=80=94=20teste?= =?UTF-8?q?d=20recipe=20for=20native=20FE=20+=20dockerized=20BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single-command BE spin-up via docker-compose-rest.yml + one .env.local, native live-editable FE pointed at it via DSPACE_REST_* env vars, integration/CORS checks, and the gotchas hit while validating (Git-Bash path mangling of /server, Node 18/20 requirement, stale node_modules, stale pgdata volume version mismatch, orphaned ng serve). Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..d253b06e47f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,164 @@ +# AGENTS.md — testing this branch (dataquest dspace-angular) + +Goal: spin up the **backend in Docker** and the **frontend natively** (so the FE stays +live-editable) and confirm they talk to each other, with the fewest moving parts. + +This recipe was validated on this branch (`internal/unify-docker-compose`) on Windows + +Docker Desktop (Compose v2.40.x). It leans on PR #1289, which makes +`docker/docker-compose-rest.yml` drivable from a single `--env-file` (parameterised +subnet + CORS), so **no `docker-compose.local.yml` override is needed**. + +--- + +## TL;DR + +```bash +# 0) prerequisites (see "Gotchas" for why): Docker running; Node 18 or 20 for the FE. + +# 1) BACKEND in Docker (fresh DB, migrate-on-boot, ~90s to healthy) +cp docker/.env.local.example docker/.env.local # then edit per "Backend" below +docker compose --env-file docker/.env.local -p dspace-7 \ + -f docker/docker-compose-rest.yml up -d +# wait until: curl -s -o /dev/null -w '%{http_code}' http://localhost:8087/server/api => 200 + +# 2) FRONTEND natively, pointed at the dockerized BE (live-reload on http://localhost:4000) +yarn install # first run / after pulling deps +DSPACE_REST_SSL=false DSPACE_REST_HOST=localhost DSPACE_REST_PORT=8087 \ + DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ + yarn start:dev + +# 3) teardown (the -v wipes the DB/Solr volumes — see gotcha #4) +docker compose --env-file docker/.env.local -p dspace-7 \ + -f docker/docker-compose-rest.yml down -v --remove-orphans +``` + +Why this works: the FE config maps env vars onto `config/config.yml` +(`rest.host`→`DSPACE_REST_HOST`, `rest.port`→`DSPACE_REST_PORT`, …; env wins last — see +`src/config/config.server.ts`). The BE trusts the host and answers the browser because +PR #1289 lets `REST_CORS_ALLOWED_ORIGINS` list the FE origin (`http://localhost:4000`). + +--- + +## Backend (Docker) + +The BE network is fully defined in `docker/docker-compose-rest.yml`, so the BE comes up +from that file **alone** (you do not need `docker-compose.yml`, which is the FE container). + +`docker/.env.local` used for this recipe (created from `docker/.env.local.example`, then +tuned for a **native** FE — i.e. CORS/UI on `localhost:4000`, loopback bind only): + +```ini +INSTANCE=7 +DSPACE_HOST=localhost +DSPACE_REST_PORT=808${INSTANCE} # -> 8087 +DSPACE_REST_NAMESPACE=/server # silences a harmless compose warning (defaulted only in docker-compose.yml) +REST_URL=http://localhost:808${INSTANCE}/server +UI_URL=http://localhost:4000 # native FE dev server port, NOT 400${INSTANCE} +HOST_IP=127.0.0.1 # loopback is enough for native FE; avoids LAN exposure +DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # dodge 172.2x collisions with other compose projects +REST_CORS_ALLOWED_ORIGINS=http://localhost:4000,http://127.0.0.1:4000 +DSPACE_REST_IMAGE=dataquest/dspace:dspace-7_x +DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x +DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x +``` + +- **Ports** (INSTANCE=7): REST `8087`, JVM debug `8007`, Postgres `5437`, Solr `8987`, + handle `2647`, handle-http `8017`. Change `INSTANCE` to re-target all of them at once. + `INSTANCE` 5 and 8 are reserved by `.github/workflows/deploy.yml`. +- **Images** — overridable; if unset, the compose defaults apply. The `dataquest/*` set + above is self-consistent with a **fresh, migrated** DB (no SQL dump needed). To preload + the entities demo data instead, use the upstream `dspace/*` set and add + `-f docker/db.entities.yml` (downloads + imports a SQL dump on first boot). +- First boot runs `dspace database migrate force` on an empty DB and deploys the `server` + webapp (~90s here). No admin user exists yet; create one if you need to log in: + ```bash + docker compose --env-file docker/.env.local -p dspace-7 \ + -f docker/docker-compose-rest.yml -f docker/cli.yml \ + run --rm dspace-cli create-administrator \ + -e admin@test.dev -f admin -l user -p admin -c en -o dataquest + ``` + +### Verify the BE +```bash +curl -s http://localhost:8087/server/api # JSON root: dspaceName/dspaceVersion/dspaceUI/dspaceServer +# CORS preflight must be allowed for the FE origin: +curl -s -i -X OPTIONS http://localhost:8087/server/api/core/items \ + -H 'Origin: http://localhost:4000' -H 'Access-Control-Request-Method: GET' \ + | grep -i access-control-allow-origin # => http://localhost:4000 +``` +(Confirmed: allowed origin → `200` + `Access-Control-Allow-Origin: http://localhost:4000`; +a non-listed origin → `403`.) + +--- + +## Frontend (native, live-editable) + +```bash +yarn install # REQUIRED if node_modules is missing/partial (see gotcha #3) +DSPACE_REST_SSL=false DSPACE_REST_HOST=localhost DSPACE_REST_PORT=8087 \ + DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ + yarn start:dev # nodemon -> ng serve, live-reload on http://localhost:4000 +``` + +- `yarn start:dev` runs `ng serve` (CSR dev server, fast rebuilds). `yarn start` instead + does a full SSR production build (`build:prod && serve:ssr`) — slower, only for an + SSR-accurate check. +- The browser calls the BE **directly** at `http://localhost:8087/server`, so the BE CORS + list (above) must include `http://localhost:4000`. +- Do **not** set `DSPACE_REST_NAMESPACE` from a Git-Bash shell — see gotcha #1. The default + `/server` from `config/config.yml` is already correct. + +--- + +## Gotchas (all hit while validating this) + +1. **Git Bash mangles `DSPACE_REST_NAMESPACE=/server`** into `C:/Program Files/Git/server` + (MSYS path conversion of a leading-slash value), which corrupts the REST baseUrl. Fix: + don't pass it (config default `/server` applies), or `export MSYS_NO_PATHCONV=1 + MSYS2_ARG_CONV_EXCL='*'` first. (Inside the `.env.local` file it is **not** mangled — only + shell exports are.) + +2. **Node 24 breaks the FE build.** This branch targets Node **18** (`Dockerfile`) / 18–20 + (CI). On Node 24 `ng serve`/`ng build` dies with + `Copy Plugin … unknown property 'priority'` (Angular 15's bundled copy-webpack-plugin v11 + gets shadowed by the top-level v6.4.1 under Node 24's resolution). Use Node 18 or 20 + (e.g. via `nvm`). The dependency tree itself is correct — this is purely the runtime Node + version. + +3. **Stale / partial `node_modules`.** If you see `Can't resolve 'd3'` / + `'ngx-skeleton-loader'` and a cascade of `NG6002`/`NG8004` errors, `node_modules` is + incomplete — run a full `yarn install` (it takes a few minutes). + +4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` volume that + a *different* Postgres major initialised gives + `FATAL: database files are incompatible with server (… initialized by PostgreSQL version 15 … not compatible with version 13)`; + the BE then hangs forever in its DB-wait loop and REST never comes up. Fix: start clean + with `down -v` (wipes the project's volumes), then `up -d`. A leftover orphan FE container + (`dspace-angular`) from a prior run is cleared by `--remove-orphans`. + +5. **Orphaned `ng serve` keeps port 4000.** Stopping the `nodemon` parent does **not** kill + its spawned `ng serve` child, so the next start crashes on `Port 4000 is already in use` + (the interactive "use another port?" prompt then throws on a non-TTY). Kill the listener + first: + ```bash + pid=$(netstat -ano | grep LISTENING | grep ':4000' | awk '{print $NF}' | head -1) + taskkill //F //T //PID $pid # Git Bash needs the // flag form + ``` + +--- + +## Alternative: fully containerized FE (PR #1289's documented path) + +If you want the FE in Docker too (no native Node), use `docker/.env.local.example` as-is +(it sets `DSPACE_HOST=host.docker.internal`, `HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) +and bring up **both** files with a local build: + +```bash +docker compose --env-file docker/.env.local -p dspace-7 \ + -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build +# FE on http://localhost:4007 ; that build path exercises the Dockerfile cp fix from PR #1289. +``` +Note `HOST_IP=0.0.0.0` publishes every BE port (incl. Postgres pwd `dspace`, JVM debug) on +all interfaces — dev-only, do not use on an untrusted network. The native-FE recipe above +avoids this by binding loopback only. +``` From cf6d1f0b703e721284f76df87316853051b950c7 Mon Sep 17 00:00:00 2001 From: jm Date: Wed, 3 Jun 2026 22:32:53 +0200 Subject: [PATCH 5/8] docs(AGENTS): correct to the end-to-end validated native-FE recipe Replaces the unvalidated first cut. Native FE now verified rendering in a browser against the dockerized BE. Adds the hard-won prerequisites/gotchas: Node 18 (not 24), copy-webpack-plugin ^11 to unbreak `ng serve`, DSPACE_REST_HOST=127.0.0.1 (Node IPv6 localhost trap), and pairing the dtq-dev FE with the dtq-dev-7.5 BE (HAL parser crash on a mismatched BE). Co-Authored-By: Claude Opus 4.8 (cherry picked from commit e84bb29e97a57eb083ab55a3178af70f2a773427) --- AGENTS.md | 224 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 123 insertions(+), 101 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d253b06e47f..ce2ed6fed16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,145 +1,170 @@ # AGENTS.md — testing this branch (dataquest dspace-angular) -Goal: spin up the **backend in Docker** and the **frontend natively** (so the FE stays -live-editable) and confirm they talk to each other, with the fewest moving parts. +Goal: spin up the **backend in Docker** and the **frontend natively** (live-editable +`ng serve`) and confirm they talk to each other, with the fewest moving parts. -This recipe was validated on this branch (`internal/unify-docker-compose`) on Windows + -Docker Desktop (Compose v2.40.x). It leans on PR #1289, which makes -`docker/docker-compose-rest.yml` drivable from a single `--env-file` (parameterised -subnet + CORS), so **no `docker-compose.local.yml` override is needed**. +This recipe was validated end-to-end on `internal/unify-docker-compose` (Windows + Docker +Desktop, Compose v2.40): BE up from one `.env.local`, FE on `ng serve` rendering in a real +browser against the BE (200s, CORS OK). It builds on PR #1289, which makes +`docker/docker-compose-rest.yml` drivable from a single `--env-file`. + +> Several non-obvious traps below cost real time to find — read **Gotchas** before starting. + +--- + +## Prerequisites + +- **Docker** running. +- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node + in ways CI doesn't catch (CI only runs `build:prod`, not `ng serve`). Quick portable + install, no global change: + ```bash + D=/c/Users/$USER/AppData/Local/Temp/node18setup; mkdir -p "$D" && cd "$D" + curl -fsSL -o n18.zip https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip + /c/Windows/System32/tar.exe -xf n18.zip # MSYS tar can't unzip; use Windows bsdtar + ./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19 # yarn into the portable prefix + export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for the FE commands + ``` +- **The `ng serve` fix** (see Gotcha #2): this branch pins `copy-webpack-plugin@^6.4.1`, + which breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`. --- ## TL;DR ```bash -# 0) prerequisites (see "Gotchas" for why): Docker running; Node 18 or 20 for the FE. - -# 1) BACKEND in Docker (fresh DB, migrate-on-boot, ~90s to healthy) -cp docker/.env.local.example docker/.env.local # then edit per "Backend" below -docker compose --env-file docker/.env.local -p dspace-7 \ - -f docker/docker-compose-rest.yml up -d -# wait until: curl -s -o /dev/null -w '%{http_code}' http://localhost:8087/server/api => 200 +# 1) BACKEND in Docker — fresh DB, migrate-on-boot, ~40-90s to healthy +cp docker/.env.local.example docker/.env.local # then edit per "Backend" below +docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml up -d +# wait: curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8087/server/api => 200 -# 2) FRONTEND natively, pointed at the dockerized BE (live-reload on http://localhost:4000) -yarn install # first run / after pulling deps -DSPACE_REST_SSL=false DSPACE_REST_HOST=localhost DSPACE_REST_PORT=8087 \ +# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already bumped + installed) +DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ - yarn start:dev + yarn start:dev # live-reload on http://localhost:4000 -# 3) teardown (the -v wipes the DB/Solr volumes — see gotcha #4) -docker compose --env-file docker/.env.local -p dspace-7 \ - -f docker/docker-compose-rest.yml down -v --remove-orphans +# 3) teardown (-v wipes DB/Solr volumes — see Gotcha #4) +docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml down -v --remove-orphans ``` -Why this works: the FE config maps env vars onto `config/config.yml` -(`rest.host`→`DSPACE_REST_HOST`, `rest.port`→`DSPACE_REST_PORT`, …; env wins last — see -`src/config/config.server.ts`). The BE trusts the host and answers the browser because -PR #1289 lets `REST_CORS_ALLOWED_ORIGINS` list the FE origin (`http://localhost:4000`). +The FE config maps env vars onto `config/config.yml` (`rest.host`→`DSPACE_REST_HOST`, +`rest.port`→`DSPACE_REST_PORT`, …; env wins last — see `src/config/config.server.ts`), so +no file edits are needed to point the FE at the dockerized BE. --- ## Backend (Docker) -The BE network is fully defined in `docker/docker-compose-rest.yml`, so the BE comes up -from that file **alone** (you do not need `docker-compose.yml`, which is the FE container). +The BE network is fully defined in `docker/docker-compose-rest.yml`, so the BE comes up from +that file **alone** (no `docker-compose.yml` — that's the FE container, which we don't use). -`docker/.env.local` used for this recipe (created from `docker/.env.local.example`, then -tuned for a **native** FE — i.e. CORS/UI on `localhost:4000`, loopback bind only): +`docker/.env.local` for this recipe (tuned for a **native** FE): ```ini INSTANCE=7 -DSPACE_HOST=localhost +# 127.0.0.1, NOT localhost (Gotcha #5). BE self-links use REST_URL, so keep it on 127.0.0.1. +DSPACE_HOST=127.0.0.1 DSPACE_REST_PORT=808${INSTANCE} # -> 8087 -DSPACE_REST_NAMESPACE=/server # silences a harmless compose warning (defaulted only in docker-compose.yml) -REST_URL=http://localhost:808${INSTANCE}/server -UI_URL=http://localhost:4000 # native FE dev server port, NOT 400${INSTANCE} -HOST_IP=127.0.0.1 # loopback is enough for native FE; avoids LAN exposure -DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # dodge 172.2x collisions with other compose projects +DSPACE_REST_NAMESPACE=/server # silences a harmless compose warning +REST_URL=http://127.0.0.1:808${INSTANCE}/server +UI_URL=http://localhost:4000 # native FE dev-server port, NOT 400${INSTANCE} +HOST_IP=127.0.0.1 # loopback is enough for native FE; no LAN exposure +DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # dodge 172.2x collisions REST_CORS_ALLOWED_ORIGINS=http://localhost:4000,http://127.0.0.1:4000 -DSPACE_REST_IMAGE=dataquest/dspace:dspace-7_x +# Use the BE that MATCHES this FE branch (Gotcha #6). dtq-dev FE + dtq-dev-7.5 BE = DSpace 7.5. +DSPACE_REST_IMAGE=dataquest/dspace:dtq-dev-7.5 DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x ``` -- **Ports** (INSTANCE=7): REST `8087`, JVM debug `8007`, Postgres `5437`, Solr `8987`, - handle `2647`, handle-http `8017`. Change `INSTANCE` to re-target all of them at once. - `INSTANCE` 5 and 8 are reserved by `.github/workflows/deploy.yml`. -- **Images** — overridable; if unset, the compose defaults apply. The `dataquest/*` set - above is self-consistent with a **fresh, migrated** DB (no SQL dump needed). To preload - the entities demo data instead, use the upstream `dspace/*` set and add - `-f docker/db.entities.yml` (downloads + imports a SQL dump on first boot). -- First boot runs `dspace database migrate force` on an empty DB and deploys the `server` - webapp (~90s here). No admin user exists yet; create one if you need to log in: +- **Ports** (INSTANCE=7): REST `8087`, JVM debug `8007`, Postgres `5437`, Solr `8987`. Change + `INSTANCE` to re-target all at once. 5 and 8 are reserved by `.github/workflows/deploy.yml`. +- First boot runs `dspace database migrate force` on an empty DB. No content, no admin user + (the homepage is intentionally near-empty — see "What success looks like"). To create an + admin: ```bash - docker compose --env-file docker/.env.local -p dspace-7 \ - -f docker/docker-compose-rest.yml -f docker/cli.yml \ - run --rm dspace-cli create-administrator \ - -e admin@test.dev -f admin -l user -p admin -c en -o dataquest + docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml -f docker/cli.yml \ + run --rm dspace-cli create-administrator -e admin@test.dev -f admin -l user -p admin -c en -o dataquest ``` ### Verify the BE ```bash -curl -s http://localhost:8087/server/api # JSON root: dspaceName/dspaceVersion/dspaceUI/dspaceServer -# CORS preflight must be allowed for the FE origin: -curl -s -i -X OPTIONS http://localhost:8087/server/api/core/items \ +curl -s http://127.0.0.1:8087/server/api # dspaceName/dspaceVersion (=> DSpace 7.5) +curl -s -i -X OPTIONS http://127.0.0.1:8087/server/api/core/items \ -H 'Origin: http://localhost:4000' -H 'Access-Control-Request-Method: GET' \ | grep -i access-control-allow-origin # => http://localhost:4000 ``` -(Confirmed: allowed origin → `200` + `Access-Control-Allow-Origin: http://localhost:4000`; -a non-listed origin → `403`.) +(Confirmed: allowed origin → `200` + matching `Access-Control-Allow-Origin`; other origin → `403`.) --- ## Frontend (native, live-editable) ```bash -yarn install # REQUIRED if node_modules is missing/partial (see gotcha #3) -DSPACE_REST_SSL=false DSPACE_REST_HOST=localhost DSPACE_REST_PORT=8087 \ +# one-time: Node 18 on PATH, copy-webpack-plugin bumped to ^11, deps installed +export PATH="/c/Users/$USER/AppData/Local/Temp/node18setup/node-v18.20.5-win-x64:$PATH" +yarn install # after the copy-webpack-plugin bump, or if node_modules is partial + +DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ - yarn start:dev # nodemon -> ng serve, live-reload on http://localhost:4000 + yarn start:dev # ng serve, live-reload, http://localhost:4000 ``` -- `yarn start:dev` runs `ng serve` (CSR dev server, fast rebuilds). `yarn start` instead - does a full SSR production build (`build:prod && serve:ssr`) — slower, only for an - SSR-accurate check. -- The browser calls the BE **directly** at `http://localhost:8087/server`, so the BE CORS - list (above) must include `http://localhost:4000`. -- Do **not** set `DSPACE_REST_NAMESPACE` from a Git-Bash shell — see gotcha #1. The default - `/server` from `config/config.yml` is already correct. +- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds, what you want for editing). + `yarn start` does a full SSR production build instead (slower; for an SSR-accurate check). +- The browser calls the BE **directly** at `http://127.0.0.1:8087/server`, so the BE CORS list + must include `http://localhost:4000` (it does, above). +- Do **not** set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); the config default + `/server` is correct. + +### What success looks like +`http://localhost:4000` renders the DSpace shell + the cookie-consent (Klaro) banner; the main +content area is empty because the repo is fresh. Browser DevTools → Network shows +`GET http://127.0.0.1:8087/server/api → 200`, `.../authn/status → 200`, +`.../discover/browses → 200`, `.../core/sites → 200`, with **no CORS errors**. A few benign +console errors are normal here: `favicon.ico` 404, Matomo refused (no analytics container), +`google.analytics.key` 404, and two DSpace-7.5 nuances (`/api/security/csrf` 404 and a +browse-definitions parse warning) — none block rendering. --- -## Gotchas (all hit while validating this) +## Gotchas (each one cost time to find) 1. **Git Bash mangles `DSPACE_REST_NAMESPACE=/server`** into `C:/Program Files/Git/server` - (MSYS path conversion of a leading-slash value), which corrupts the REST baseUrl. Fix: - don't pass it (config default `/server` applies), or `export MSYS_NO_PATHCONV=1 - MSYS2_ARG_CONV_EXCL='*'` first. (Inside the `.env.local` file it is **not** mangled — only - shell exports are.) - -2. **Node 24 breaks the FE build.** This branch targets Node **18** (`Dockerfile`) / 18–20 - (CI). On Node 24 `ng serve`/`ng build` dies with - `Copy Plugin … unknown property 'priority'` (Angular 15's bundled copy-webpack-plugin v11 - gets shadowed by the top-level v6.4.1 under Node 24's resolution). Use Node 18 or 20 - (e.g. via `nvm`). The dependency tree itself is correct — this is purely the runtime Node - version. - -3. **Stale / partial `node_modules`.** If you see `Can't resolve 'd3'` / - `'ngx-skeleton-loader'` and a cascade of `NG6002`/`NG8004` errors, `node_modules` is - incomplete — run a full `yarn install` (it takes a few minutes). - -4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` volume that - a *different* Postgres major initialised gives - `FATAL: database files are incompatible with server (… initialized by PostgreSQL version 15 … not compatible with version 13)`; - the BE then hangs forever in its DB-wait loop and REST never comes up. Fix: start clean - with `down -v` (wipes the project's volumes), then `up -d`. A leftover orphan FE container - (`dspace-angular`) from a prior run is cleared by `--remove-orphans`. - -5. **Orphaned `ng serve` keeps port 4000.** Stopping the `nodemon` parent does **not** kill - its spawned `ng serve` child, so the next start crashes on `Port 4000 is already in use` - (the interactive "use another port?" prompt then throws on a non-TTY). Kill the listener - first: + (MSYS path conversion of a leading-slash value), corrupting the REST baseUrl. Fix: don't + pass it (config default `/server` applies), or `export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`. + Inside the `.env.local` file it's NOT mangled — only shell exports are. + +2. **`ng serve` is broken on this branch out of the box.** `package.json` pins + `copy-webpack-plugin@^6.4.1`, but `@angular-builders/custom-webpack` (the serve builder) + resolves it for build-angular@15, which passes the `priority` option (v9+ only) → + `An unhandled exception occurred: Copy Plugin … unknown property 'priority'`. CI misses it + (it runs `build:prod`, not `ng serve`). Fix: set `"copy-webpack-plugin": "^11.0.0"` in + `package.json` (matches build-angular's pin) and `yarn install`. Validated: `ng serve` then + `✓ Compiled successfully`. (If pushing this fix, let CI re-run `build:prod` + tests.) + +3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a + cascade of `NG6002`/`NG8004` → run a full `yarn install`. + +4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by + a different Postgres major gives `FATAL: database files are incompatible with server`; the BE + then hangs forever in its DB-wait loop and REST never comes up. Fix: `down -v` for a clean + start. `--remove-orphans` clears a leftover `dspace-angular` container. + +5. **`localhost` ≠ `127.0.0.1` for the FE→BE hop.** Node 18 resolves `localhost` to IPv6 `::1` + with no IPv4 fallback, but the BE publishes on `127.0.0.1` only (`host_ip`), so SSR/Node + fetches `ECONNREFUSED` and you get `undefined doesn't contain the link sites`. Use + `DSPACE_REST_HOST=127.0.0.1` (and `REST_URL=http://127.0.0.1:...` so the BE's HAL self-links + match). `curl` hides this because it falls back to IPv4. + +6. **Pair the FE with the matching BE version.** The `dtq-dev` FE branch against + `dataquest/dspace:dspace-7_x` (DSpace 7.6.5) makes the FE's HAL parser recurse → + `RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Use the + branch's matching BE, `dataquest/dspace:dtq-dev-7.5` (DSpace 7.5). + +7. **Orphaned `ng serve` keeps port 4000.** Stopping the `nodemon` parent doesn't kill its + spawned `ng serve` child, so the next start crashes on `Port 4000 is already in use` (the + interactive prompt throws on a non-TTY). Kill the listener first: ```bash pid=$(netstat -ano | grep LISTENING | grep ':4000' | awk '{print $NF}' | head -1) taskkill //F //T //PID $pid # Git Bash needs the // flag form @@ -149,16 +174,13 @@ DSPACE_REST_SSL=false DSPACE_REST_HOST=localhost DSPACE_REST_PORT=8087 \ ## Alternative: fully containerized FE (PR #1289's documented path) -If you want the FE in Docker too (no native Node), use `docker/.env.local.example` as-is -(it sets `DSPACE_HOST=host.docker.internal`, `HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) -and bring up **both** files with a local build: - +To run the FE in Docker too (no native Node), use `docker/.env.local.example` as-is +(`DSPACE_HOST=host.docker.internal`, `HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up +**both** files with a local build: ```bash -docker compose --env-file docker/.env.local -p dspace-7 \ +docker compose --env-file docker/.env.local \ -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build -# FE on http://localhost:4007 ; that build path exercises the Dockerfile cp fix from PR #1289. -``` -Note `HOST_IP=0.0.0.0` publishes every BE port (incl. Postgres pwd `dspace`, JVM debug) on -all interfaces — dev-only, do not use on an untrusted network. The native-FE recipe above -avoids this by binding loopback only. +# FE on http://localhost:4007 ; this build path exercises the Dockerfile cp fix from PR #1289. ``` +`HOST_IP=0.0.0.0` publishes every BE port (incl. Postgres pwd `dspace`, JVM debug) on all +interfaces — dev-only, not for an untrusted network. The native-FE recipe above avoids this. From ecd9f9fe8ddb87cb98b6281ba715beea5cb87f16 Mon Sep 17 00:00:00 2001 From: jm Date: Wed, 3 Jun 2026 22:36:37 +0200 Subject: [PATCH 6/8] build(deps): bump copy-webpack-plugin ^6.4.1 -> ^11.0.0 to fix `ng serve` webpack/webpack.common.ts resolves the project's pinned copy-webpack-plugin, but @angular-devkit/build-angular@15 emits asset-copy patterns using the `priority` option (copy-webpack-plugin v9+). With the old ^6.4.1 pin, `ng serve` (via @angular-builders/custom-webpack) dies with "Copy Plugin ... unknown property 'priority'". ^11.0.0 matches build-angular@15's own dependency and dedupes to a single resolution; the project's copy options (from/to/force/transform/noErrorOnMissing) stay v11-compatible. CI didn't catch this because it runs build:prod, not ng serve. Co-Authored-By: Claude Opus 4.8 (cherry picked from commit 3a2264a3367f16124609fa65cd0f3687be6dd6c0) --- package.json | 2 +- yarn.lock | 145 ++++----------------------------------------------- 2 files changed, 12 insertions(+), 135 deletions(-) diff --git a/package.json b/package.json index 565c0510999..74fc3655f8a 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "@typescript-eslint/parser": "^5.62.0", "axe-core": "^4.10.3", "compression-webpack-plugin": "^9.2.0", - "copy-webpack-plugin": "^6.4.1", + "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", "csstype": "^3.1.3", "cypress": "^13.17.0", diff --git a/yarn.lock b/yarn.lock index 665ca9bbde2..9583ed80b33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1669,7 +1669,7 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz#8249de9b7e22fcb3ceb5e66090c30a1d5492b81a" integrity sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA== -"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": +"@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== @@ -2018,14 +2018,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - "@npmcli/fs@^2.1.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" @@ -2063,14 +2055,6 @@ npm-bundled "^3.0.0" npm-normalize-package-bin "^3.0.0" -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - "@npmcli/move-file@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" @@ -4043,30 +4027,6 @@ cacache@17.0.4: tar "^6.1.11" unique-filename "^3.0.0" -cacache@^15.0.5: - version "15.3.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - cacache@^16.1.0: version "16.1.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" @@ -4587,7 +4547,7 @@ copy-to-clipboard@^3.3.1: dependencies: toggle-selection "^1.0.6" -copy-webpack-plugin@11.0.0: +copy-webpack-plugin@11.0.0, copy-webpack-plugin@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== @@ -4599,23 +4559,6 @@ copy-webpack-plugin@11.0.0: schema-utils "^4.0.0" serialize-javascript "^6.0.0" -copy-webpack-plugin@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz#138cd9b436dbca0a6d071720d5414848992ec47e" - integrity sha512-MXyPCjdPVx5iiWyl40Va3JGh27bKzOTNY3NjUTrosD2q7dR/cLD0013uqJ3BpFbUjyONINjb6qI7nDIJujrMbA== - dependencies: - cacache "^15.0.5" - fast-glob "^3.2.4" - find-cache-dir "^3.3.1" - glob-parent "^5.1.1" - globby "^11.0.1" - loader-utils "^2.0.0" - normalize-path "^3.0.0" - p-limit "^3.0.2" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - webpack-sources "^1.4.3" - core-js-compat@^3.25.1: version "3.47.0" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.47.0.tgz#698224bbdbb6f2e3f39decdda4147b161e3772a3" @@ -6332,7 +6275,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.11, fast-glob@^3.2.4, fast-glob@^3.2.9, fast-glob@^3.3.0: +fast-glob@^3.2.11, fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.3" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== @@ -6466,7 +6409,7 @@ finalhandler@~1.3.1: statuses "~2.0.2" unpipe "~1.0.0" -find-cache-dir@^3.3.1, find-cache-dir@^3.3.2: +find-cache-dir@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== @@ -6754,7 +6697,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -6830,7 +6773,7 @@ globalthis@^1.0.4: define-properties "^1.2.1" gopd "^1.0.1" -globby@^11.0.1, globby@^11.1.0: +globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -8826,7 +8769,7 @@ minipass-json-stream@^1.0.1: jsonparse "^1.3.1" minipass "^3.0.0" -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: +minipass-pipeline@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== @@ -11123,13 +11066,6 @@ send@~0.19.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - serialize-javascript@^6.0.0, serialize-javascript@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -11426,11 +11362,6 @@ sortablejs@1.15.6: resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.6.tgz#ff93699493f5b8ab8d828f933227b4988df1d393" integrity sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A== -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - "source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" @@ -11558,13 +11489,6 @@ ssri@^10.0.0: dependencies: minipass "^7.0.3" -ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - ssri@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" @@ -11622,16 +11546,7 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11695,7 +11610,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11709,13 +11624,6 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.2" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" @@ -11786,7 +11694,7 @@ tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== -tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: +tar@^6.1.11, tar@^6.1.2: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== @@ -12246,13 +12154,6 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz#301d4f8a43d2b75c97adfad87c9dd5350c9475d1" integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" @@ -12267,13 +12168,6 @@ unique-filename@^3.0.0: dependencies: unique-slug "^4.0.0" -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - unique-slug@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" @@ -12611,14 +12505,6 @@ webpack-merge@^5.10.0, webpack-merge@^5.7.3: flat "^5.0.2" wildcard "^2.0.0" -webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - webpack-sources@^3.0.0, webpack-sources@^3.2.3: version "3.3.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" @@ -12820,7 +12706,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -12838,15 +12724,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 4f9034ce3a397853724d59594d2d6d906ddad8ed Mon Sep 17 00:00:00 2001 From: jm Date: Thu, 4 Jun 2026 10:39:48 +0200 Subject: [PATCH 7/8] docs(AGENTS): fix BE version (7.6.5, not 7.5) + add demo-content recipe Validated the native FE rendering the LINDAT/CLARIAH-CZ home with demo content. Key corrections vs the first cut: - Match the BE to the FE version (7.6.5). dtq-dev-7.5 is DSpace 7.5 -> browse-definitions parse error in the console. - The earlier "Maximum call stack" was the localhost/127.0.0.1 host mismatch (HAL self-links resolved under two hostnames), not a BE-version issue; keep REST_HOST and REST_URL on 127.0.0.1. - Add Option A (upstream 7.6.5 + db.entities.yml + index-discovery -b) for sample content, with the caveat it's upstream (not CLARIN) data; Option B for CLARIN fidelity via a dataquest dump. - Note the MSYS path-mangling also bites `docker exec /dspace/...` (use MSYS_NO_PATHCONV=1). Co-Authored-By: Claude Opus 4.8 (cherry picked from commit e156c97623327e2aa7bd7aec65ecb6d1ad01ffdc) --- AGENTS.md | 197 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 109 insertions(+), 88 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ce2ed6fed16..44cf7f52ac9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,184 +3,205 @@ Goal: spin up the **backend in Docker** and the **frontend natively** (live-editable `ng serve`) and confirm they talk to each other, with the fewest moving parts. -This recipe was validated end-to-end on `internal/unify-docker-compose` (Windows + Docker -Desktop, Compose v2.40): BE up from one `.env.local`, FE on `ng serve` rendering in a real -browser against the BE (200s, CORS OK). It builds on PR #1289, which makes -`docker/docker-compose-rest.yml` drivable from a single `--env-file`. +Validated end-to-end on `internal/unify-docker-compose` (Windows + Docker Desktop, Compose +v2.40): BE up from one `.env.local`, FE on `ng serve` rendering the LINDAT/CLARIAH-CZ home in +a real browser with demo content (6 communities, 471 indexed items), 200s, no CORS errors. +Builds on PR #1289, which makes `docker/docker-compose-rest.yml` drivable from one `--env-file`. > Several non-obvious traps below cost real time to find — read **Gotchas** before starting. +> **Match the BE to the FE version:** this FE reports **7.6.5** (see the browser console +> startup banner), so use a **7.6.5** backend image. A 7.5 BE (`dtq-dev-7.5`) parses most +> things but errors on browse definitions. --- ## Prerequisites - **Docker** running. -- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node - in ways CI doesn't catch (CI only runs `build:prod`, not `ng serve`). Quick portable - install, no global change: +- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node in + ways CI doesn't catch (CI runs `build:prod`, never `ng serve`). Portable install, no global + change: ```bash D=/c/Users/$USER/AppData/Local/Temp/node18setup; mkdir -p "$D" && cd "$D" curl -fsSL -o n18.zip https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip /c/Windows/System32/tar.exe -xf n18.zip # MSYS tar can't unzip; use Windows bsdtar - ./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19 # yarn into the portable prefix - export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for the FE commands + ./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19 + export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for FE commands ``` -- **The `ng serve` fix** (see Gotcha #2): this branch pins `copy-webpack-plugin@^6.4.1`, - which breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`. +- **The `ng serve` fix** (Gotcha #2): the branch pins `copy-webpack-plugin@^6.4.1`, which + breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`. (This is now + committed on PR #1289.) --- ## TL;DR ```bash -# 1) BACKEND in Docker — fresh DB, migrate-on-boot, ~40-90s to healthy -cp docker/.env.local.example docker/.env.local # then edit per "Backend" below -docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml up -d +# 1) BACKEND in Docker, WITH demo content (7.6.5 to match the FE) — ~2-4 min first boot +cp docker/.env.local.example docker/.env.local # then edit per "Backend" +docker compose --env-file docker/.env.local \ + -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d # wait: curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8087/server/api => 200 +MSYS_NO_PATHCONV=1 docker exec dspace7 /dspace/bin/dspace index-discovery -b # populate Solr -# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already bumped + installed) +# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already ^11 + installed) DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ - yarn start:dev # live-reload on http://localhost:4000 + yarn start:dev # live-reload on http://localhost:4000 # 3) teardown (-v wipes DB/Solr volumes — see Gotcha #4) -docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml down -v --remove-orphans +docker compose --env-file docker/.env.local \ + -f docker/docker-compose-rest.yml -f docker/db.entities.yml down -v --remove-orphans ``` -The FE config maps env vars onto `config/config.yml` (`rest.host`→`DSPACE_REST_HOST`, -`rest.port`→`DSPACE_REST_PORT`, …; env wins last — see `src/config/config.server.ts`), so -no file edits are needed to point the FE at the dockerized BE. +The FE maps env vars onto `config/config.yml` (`rest.host`→`DSPACE_REST_HOST`, … ; env wins +last — see `src/config/config.server.ts`), so no file edits are needed to point it at the BE. --- ## Backend (Docker) -The BE network is fully defined in `docker/docker-compose-rest.yml`, so the BE comes up from -that file **alone** (no `docker-compose.yml` — that's the FE container, which we don't use). +`docker/docker-compose-rest.yml` fully defines the network, so the BE comes up from that file +(plus `db.entities.yml` for demo data) — no `docker-compose.yml` (that's the FE container). -`docker/.env.local` for this recipe (tuned for a **native** FE): +`docker/.env.local` for this recipe: ```ini INSTANCE=7 # 127.0.0.1, NOT localhost (Gotcha #5). BE self-links use REST_URL, so keep it on 127.0.0.1. DSPACE_HOST=127.0.0.1 DSPACE_REST_PORT=808${INSTANCE} # -> 8087 -DSPACE_REST_NAMESPACE=/server # silences a harmless compose warning +DSPACE_REST_NAMESPACE=/server REST_URL=http://127.0.0.1:808${INSTANCE}/server UI_URL=http://localhost:4000 # native FE dev-server port, NOT 400${INSTANCE} -HOST_IP=127.0.0.1 # loopback is enough for native FE; no LAN exposure -DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # dodge 172.2x collisions +HOST_IP=127.0.0.1 +DSPACE_SUBNET_PREFIX=10.10${INSTANCE} REST_CORS_ALLOWED_ORIGINS=http://localhost:4000,http://127.0.0.1:4000 -# Use the BE that MATCHES this FE branch (Gotcha #6). dtq-dev FE + dtq-dev-7.5 BE = DSpace 7.5. -DSPACE_REST_IMAGE=dataquest/dspace:dtq-dev-7.5 -DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x -DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x + +# --- Option A: demo content (VERIFIED) — upstream 7.6.5 + db.entities.yml ------------------- +# Matches the FE's 7.6.5 and loads the official DSpace demo entities dataset. NOTE: this is +# UPSTREAM DSpace data on an UPSTREAM BE, so CLARIN-specific FE calls 404 (harmless to render). +DSPACE_REST_IMAGE=dspace/dspace:dspace-7_x +DSPACE_DB_IMAGE=dspace/dspace-postgres-pgcrypto:dspace-7_x # db.entities.yml overrides to -loadsql +DSPACE_SOLR_IMAGE=dspace/dspace-solr:dspace-7_x +DOCKER_REGISTRY=docker.io # consumed by db.entities.yml to resolve the -loadsql image +DOCKER_OWNER=dspace +DSPACE_VER=dspace-7_x + +# --- Option B: CLARIN fidelity (version-matched, but no content unless you have a dump) ------ +# DSPACE_REST_IMAGE=dataquest/dspace:dspace-7_x # DSpace 7.6.5, CLARIN tables/endpoints +# DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x +# DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x +# Fresh DB = empty homepage. For real CLARIN content, restore a dataquest/LINDAT DB dump into +# dspacedb7 (pg_restore/psql) instead of layering db.entities.yml, then reindex. ``` +- Bring up Option A with **both** files: `-f docker/docker-compose-rest.yml -f docker/db.entities.yml`. + The `-loadsql` Postgres downloads + imports `dspace7-entities-data.sql` on first boot; the BE + then runs `database migrate ignored`. **Then reindex Solr** (command above) or the homepage's + browse/search/"What's New" stay empty even though the DB has data. +- For Option B (no `db.entities.yml`), use just `-f docker/docker-compose-rest.yml`. - **Ports** (INSTANCE=7): REST `8087`, JVM debug `8007`, Postgres `5437`, Solr `8987`. Change `INSTANCE` to re-target all at once. 5 and 8 are reserved by `.github/workflows/deploy.yml`. -- First boot runs `dspace database migrate force` on an empty DB. No content, no admin user - (the homepage is intentionally near-empty — see "What success looks like"). To create an - admin: - ```bash - docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml -f docker/cli.yml \ - run --rm dspace-cli create-administrator -e admin@test.dev -f admin -l user -p admin -c en -o dataquest - ``` +- Admin user (optional): `… -f docker/cli.yml run --rm dspace-cli create-administrator -e admin@test.dev -f admin -l user -p admin -c en -o dataquest` ### Verify the BE ```bash -curl -s http://127.0.0.1:8087/server/api # dspaceName/dspaceVersion (=> DSpace 7.5) +curl -s http://127.0.0.1:8087/server/api # dspaceVersion => DSpace 7.6.5 +curl -s http://127.0.0.1:8087/server/api/core/communities/search/top # totalElements: 6 (demo) curl -s -i -X OPTIONS http://127.0.0.1:8087/server/api/core/items \ -H 'Origin: http://localhost:4000' -H 'Access-Control-Request-Method: GET' \ | grep -i access-control-allow-origin # => http://localhost:4000 ``` -(Confirmed: allowed origin → `200` + matching `Access-Control-Allow-Origin`; other origin → `403`.) --- ## Frontend (native, live-editable) ```bash -# one-time: Node 18 on PATH, copy-webpack-plugin bumped to ^11, deps installed export PATH="/c/Users/$USER/AppData/Local/Temp/node18setup/node-v18.20.5-win-x64:$PATH" yarn install # after the copy-webpack-plugin bump, or if node_modules is partial - DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ yarn start:dev # ng serve, live-reload, http://localhost:4000 ``` -- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds, what you want for editing). - `yarn start` does a full SSR production build instead (slower; for an SSR-accurate check). -- The browser calls the BE **directly** at `http://127.0.0.1:8087/server`, so the BE CORS list - must include `http://localhost:4000` (it does, above). -- Do **not** set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); the config default - `/server` is correct. +- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds — what you want for editing). + `yarn start` does a full SSR production build instead. +- The browser calls the BE directly at `http://127.0.0.1:8087/server`; the CORS list must include + `http://localhost:4000` (it does). +- Don't set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); config default `/server` + is correct. ### What success looks like -`http://localhost:4000` renders the DSpace shell + the cookie-consent (Klaro) banner; the main -content area is empty because the repo is fresh. Browser DevTools → Network shows -`GET http://127.0.0.1:8087/server/api → 200`, `.../authn/status → 200`, -`.../discover/browses → 200`, `.../core/sites → 200`, with **no CORS errors**. A few benign -console errors are normal here: `favicon.ico` 404, Matomo refused (no analytics container), -`google.analytics.key` 404, and two DSpace-7.5 nuances (`/api/security/csrf` 404 and a -browse-definitions parse warning) — none block rendering. +`http://localhost:4000` redirects to `/home` and renders the **LINDAT/CLARIAH-CZ Repository +Home**: search + facets (Author/Subject/Language), a **"What's New"** list of items, and the +footer. DevTools → Network shows `…8087/server/api/*` 200s with no CORS errors. Benign console +errors are normal: `favicon.ico` 404, Matomo refused, `google.analytics.key` 404, `/security/csrf` +404, and (with Option A's upstream BE) some CLARIN-specific 404s. --- ## Gotchas (each one cost time to find) -1. **Git Bash mangles `DSPACE_REST_NAMESPACE=/server`** into `C:/Program Files/Git/server` - (MSYS path conversion of a leading-slash value), corrupting the REST baseUrl. Fix: don't - pass it (config default `/server` applies), or `export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`. - Inside the `.env.local` file it's NOT mangled — only shell exports are. +1. **Git Bash mangles leading-slash paths.** `DSPACE_REST_NAMESPACE=/server` becomes + `C:/Program Files/Git/server`, and `docker exec dspace7 /dspace/bin/dspace …` becomes + `…/Git/dspace/bin/dspace` (no such file). Fix: prefix the command with + `MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`. (Values inside the `.env.local` file are NOT + mangled — only shell args.) 2. **`ng serve` is broken on this branch out of the box.** `package.json` pins - `copy-webpack-plugin@^6.4.1`, but `@angular-builders/custom-webpack` (the serve builder) - resolves it for build-angular@15, which passes the `priority` option (v9+ only) → - `An unhandled exception occurred: Copy Plugin … unknown property 'priority'`. CI misses it - (it runs `build:prod`, not `ng serve`). Fix: set `"copy-webpack-plugin": "^11.0.0"` in - `package.json` (matches build-angular's pin) and `yarn install`. Validated: `ng serve` then - `✓ Compiled successfully`. (If pushing this fix, let CI re-run `build:prod` + tests.) + `copy-webpack-plugin@^6.4.1`, but build-angular@15 (via `@angular-builders/custom-webpack`) + emits asset-copy patterns using the `priority` option (v9+) → `Copy Plugin … unknown property + 'priority'`. CI misses it (it runs `build:prod`, not `ng serve`). Fix: `^11.0.0` (matches + build-angular) + `yarn install`. (Committed on PR #1289.) -3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a - cascade of `NG6002`/`NG8004` → run a full `yarn install`. +3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a cascade + of `NG6002`/`NG8004` → run a full `yarn install`. -4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by - a different Postgres major gives `FATAL: database files are incompatible with server`; the BE - then hangs forever in its DB-wait loop and REST never comes up. Fix: `down -v` for a clean - start. `--remove-orphans` clears a leftover `dspace-angular` container. +4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by a + different Postgres major gives `FATAL: database files are incompatible with server`; the BE + then hangs forever in its DB-wait loop. Fix: `down -v` for a clean start (`--remove-orphans` + clears a leftover `dspace-angular` container). 5. **`localhost` ≠ `127.0.0.1` for the FE→BE hop.** Node 18 resolves `localhost` to IPv6 `::1` - with no IPv4 fallback, but the BE publishes on `127.0.0.1` only (`host_ip`), so SSR/Node - fetches `ECONNREFUSED` and you get `undefined doesn't contain the link sites`. Use - `DSPACE_REST_HOST=127.0.0.1` (and `REST_URL=http://127.0.0.1:...` so the BE's HAL self-links - match). `curl` hides this because it falls back to IPv4. - -6. **Pair the FE with the matching BE version.** The `dtq-dev` FE branch against - `dataquest/dspace:dspace-7_x` (DSpace 7.6.5) makes the FE's HAL parser recurse → - `RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Use the - branch's matching BE, `dataquest/dspace:dtq-dev-7.5` (DSpace 7.5). - -7. **Orphaned `ng serve` keeps port 4000.** Stopping the `nodemon` parent doesn't kill its - spawned `ng serve` child, so the next start crashes on `Port 4000 is already in use` (the - interactive prompt throws on a non-TTY). Kill the listener first: + with no IPv4 fallback, but the BE publishes on `127.0.0.1` only, so Node fetches `ECONNREFUSED` + → `undefined doesn't contain the link sites`. Worse, a host MISMATCH (FE on `127.0.0.1` but BE + self-links on `localhost`) made the HAL parser resolve objects under two hostnames and recurse + → `RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Keep + **both** `DSPACE_REST_HOST` and `REST_URL` on `127.0.0.1`. (`curl` hides this — it falls back + to IPv4.) + +6. **Match the BE to the FE version.** The FE reports **7.6.5**, so use a **7.6.5** BE + (`dspace/dspace:dspace-7_x` or `dataquest/dspace:dspace-7_x`). The `dataquest/dspace:dtq-dev-7.5` + image is **DSpace 7.5** and causes `An error occurred while retrieving the browse definitions` + in the console. + +7. **Demo data ≠ empty repo, and Solr needs reindexing.** A fresh DB shows an empty homepage — + that's expected, not a bug. Layer `db.entities.yml` (Option A) for sample content, then run + `index-discovery -b` so browse/search/"What's New" populate. (The public + `dspace7-entities-data.sql` now ships future 8.0/9.0/10.0 flyway entries; a 7.6.5 BE still + reads the data fine via `migrate ignored`.) + +8. **Orphaned `ng serve` keeps port 4000.** Stopping `nodemon` doesn't kill its `ng serve` child, + so the next start crashes on `Port 4000 is already in use`. Kill the listener first: ```bash pid=$(netstat -ano | grep LISTENING | grep ':4000' | awk '{print $NF}' | head -1) - taskkill //F //T //PID $pid # Git Bash needs the // flag form + taskkill //F //T //PID $pid ``` + And do NOT switch git branches while `ng serve` runs — it watches the working tree and will + recompile against the wrong files. Stop it first (or use a separate `git worktree`). --- ## Alternative: fully containerized FE (PR #1289's documented path) -To run the FE in Docker too (no native Node), use `docker/.env.local.example` as-is -(`DSPACE_HOST=host.docker.internal`, `HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up -**both** files with a local build: +To run the FE in Docker too, use `docker/.env.local.example` as-is (`host.docker.internal`, +`HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up **both** compose files with `--build`: ```bash docker compose --env-file docker/.env.local \ -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build # FE on http://localhost:4007 ; this build path exercises the Dockerfile cp fix from PR #1289. ``` `HOST_IP=0.0.0.0` publishes every BE port (incl. Postgres pwd `dspace`, JVM debug) on all -interfaces — dev-only, not for an untrusted network. The native-FE recipe above avoids this. +interfaces — dev-only, not for an untrusted network. From b3fca1e6dee6471df1aeeb7b883b451d622a82c0 Mon Sep 17 00:00:00 2001 From: jm Date: Fri, 5 Jun 2026 13:44:31 +0200 Subject: [PATCH 8/8] feat(dev): one-command native-FE local dev (dev.backend.sh + start:dev:local + .nvmrc) Makes "debuggable/changeable FE against a real backend" two commands: build-scripts/run/dev.backend.sh # DSpace 7.6.5 + demo content at 127.0.0.1:8087, reindexed yarn start:dev:local # ng serve, live-reload on :4000, REST_* env baked in Removes the traps that made this painful: the script pins the 7.6.5 images (FE-matched), uses 127.0.0.1 (Node sends localhost->IPv6; BE is IPv4-only), sets CORS, layers db.entities.yml for sample data, and runs index-discovery with MSYS_NO_PATHCONV. .nvmrc pins Node 18 (the Angular 15 toolchain breaks on newer Node; pairs with the copy-webpack-plugin fix). Co-Authored-By: Claude Opus 4.8 (cherry picked from commit c686c3ce0545387763a02b85212892cbaa2c6b16) --- .nvmrc | 1 + AGENTS.md | 31 +++++++------ build-scripts/run/dev.backend.sh | 76 ++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 .nvmrc create mode 100644 build-scripts/run/dev.backend.sh diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..1117d417c6a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18.20.5 diff --git a/AGENTS.md b/AGENTS.md index 44cf7f52ac9..19e7914de4d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,28 +34,27 @@ Builds on PR #1289, which makes `docker/docker-compose-rest.yml` drivable from o --- -## TL;DR +## TL;DR — two commands ```bash -# 1) BACKEND in Docker, WITH demo content (7.6.5 to match the FE) — ~2-4 min first boot -cp docker/.env.local.example docker/.env.local # then edit per "Backend" -docker compose --env-file docker/.env.local \ - -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d -# wait: curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8087/server/api => 200 -MSYS_NO_PATHCONV=1 docker exec dspace7 /dspace/bin/dspace index-discovery -b # populate Solr +# 0) one-time: use Node 18 (see .nvmrc) and install deps +nvm use # or: see Prerequisites for a portable Node 18 +yarn install -# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already ^11 + installed) -DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \ - DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \ - yarn start:dev # live-reload on http://localhost:4000 +# 1) BACKEND: DSpace 7.6.5 + demo content at http://127.0.0.1:8087/server (~2-4 min first run) +build-scripts/run/dev.backend.sh # add 'fresh' to wipe volumes + reload from scratch -# 3) teardown (-v wipes DB/Solr volumes — see Gotcha #4) -docker compose --env-file docker/.env.local \ - -f docker/docker-compose-rest.yml -f docker/db.entities.yml down -v --remove-orphans +# 2) FRONTEND: live-reload dev server on http://localhost:4000 +yarn start:dev:local ``` -The FE maps env vars onto `config/config.yml` (`rest.host`→`DSPACE_REST_HOST`, … ; env wins -last — see `src/config/config.server.ts`), so no file edits are needed to point it at the BE. +`dev.backend.sh` brings the backend up (correct 7.6.5 images, IPv4 host, CORS, demo dataset) +and reindexes Solr; `start:dev:local` is `ng serve` with the right `DSPACE_REST_*` env baked in. +Stop/wipe the backend with `docker compose -f docker/docker-compose-rest.yml -f docker/db.entities.yml down -v`. + +Everything below is the manual/explained version of those two commands (for customizing the +instance, image set, or running the FE in Docker). The FE maps env vars onto `config/config.yml` +(`rest.host`→`DSPACE_REST_HOST`, … ; env wins last — see `src/config/config.server.ts`). --- diff --git a/build-scripts/run/dev.backend.sh b/build-scripts/run/dev.backend.sh new file mode 100644 index 00000000000..15d42a8273b --- /dev/null +++ b/build-scripts/run/dev.backend.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# +# One-command local-dev BACKEND for working on the native frontend. +# +# Brings up a DSpace 7.6.5 backend (matching this FE's version) in Docker, loaded with the +# official demo entities dataset, reachable at http://127.0.0.1:8087/server, then indexes Solr +# so browse/search/"What's New" are populated. After it prints "Backend ready", start the FE: +# +# yarn start:dev:local # ng serve, live-reload, http://localhost:4000 (needs Node 18 — see .nvmrc) +# +# Usage: +# build-scripts/run/dev.backend.sh # up (reuses existing containers/data) +# build-scripts/run/dev.backend.sh fresh # wipe DB/Solr volumes first, then up (clean slate) +# +# Notes: +# - 127.0.0.1 (not localhost): Node resolves localhost to IPv6 ::1, but the BE binds IPv4 only. +# - Demo data is UPSTREAM DSpace (not CLARIN), so some CLARIN-specific FE calls 404 — harmless. +# For CLARIN content, point DSPACE_REST_IMAGE at dataquest/dspace:dspace-7_x and restore a +# dataquest/LINDAT DB dump instead of using db.entities.yml. +# +set -uo pipefail +cd "$(dirname "$0")/../.." || exit 1 + +export INSTANCE="${INSTANCE:-7}" +export COMPOSE_PROJECT_NAME="dspace-${INSTANCE}" +export DSPACE_HOST=127.0.0.1 +export DSPACE_REST_NAMESPACE=/server +export REST_URL="http://127.0.0.1:808${INSTANCE}/server" +export UI_URL="http://localhost:4000" +export HOST_IP=127.0.0.1 +export DSPACE_SUBNET_PREFIX="10.10${INSTANCE}" +export REST_CORS_ALLOWED_ORIGINS="http://localhost:4000,http://127.0.0.1:4000" +# DSpace 7.6.5 to match the FE; upstream images + the demo entities dataset (db.entities.yml). +export DSPACE_REST_IMAGE=dspace/dspace:dspace-7_x +export DSPACE_DB_IMAGE=dspace/dspace-postgres-pgcrypto:dspace-7_x +export DSPACE_SOLR_IMAGE=dspace/dspace-solr:dspace-7_x +export DOCKER_REGISTRY=docker.io DOCKER_OWNER=dspace DSPACE_VER=dspace-7_x + +COMPOSE=(docker compose -f docker/docker-compose-rest.yml -f docker/db.entities.yml) +REST="http://127.0.0.1:808${INSTANCE}/server/api" + +if [ "${1:-}" = "fresh" ]; then + echo ">> wiping previous dev backend (down -v)" + "${COMPOSE[@]}" down -v --remove-orphans || true +fi + +echo ">> starting backend: DSpace 7.6.5 + demo entities (project ${COMPOSE_PROJECT_NAME})" +if ! "${COMPOSE[@]}" up -d; then + echo "!! 'up' failed. If it's a Postgres version/volume mismatch, run: $0 fresh" >&2 + exit 1 +fi + +echo -n ">> waiting for REST API (${REST}) " +for _ in $(seq 1 90); do + [ "$(curl -s -o /dev/null -w '%{http_code}' "$REST" 2>/dev/null)" = "200" ] && { echo " ready"; break; } + printf '.'; sleep 5 +done +if [ "$(curl -s -o /dev/null -w '%{http_code}' "$REST" 2>/dev/null)" != "200" ]; then + echo " timed out. Check: ${COMPOSE[*]} logs dspace${INSTANCE}" >&2 + exit 1 +fi + +echo ">> indexing Solr discovery (so browse/search/What's New populate)" +# MSYS_NO_PATHCONV stops Git Bash from rewriting /dspace/... into a Windows path. +MSYS_NO_PATHCONV=1 docker exec "dspace${INSTANCE}" /dspace/bin/dspace index-discovery -b \ + || echo " (reindex failed — rerun: MSYS_NO_PATHCONV=1 docker exec dspace${INSTANCE} /dspace/bin/dspace index-discovery -b)" + +cat <