Skip to content

Commit 4f9034c

Browse files
jmclaude
andcommitted
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 <noreply@anthropic.com> (cherry picked from commit e156c97623327e2aa7bd7aec65ecb6d1ad01ffdc)
1 parent ecd9f9f commit 4f9034c

1 file changed

Lines changed: 109 additions & 88 deletions

File tree

AGENTS.md

Lines changed: 109 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -3,184 +3,205 @@
33
Goal: spin up the **backend in Docker** and the **frontend natively** (live-editable
44
`ng serve`) and confirm they talk to each other, with the fewest moving parts.
55

6-
This recipe was validated end-to-end on `internal/unify-docker-compose` (Windows + Docker
7-
Desktop, Compose v2.40): BE up from one `.env.local`, FE on `ng serve` rendering in a real
8-
browser against the BE (200s, CORS OK). It builds on PR #1289, which makes
9-
`docker/docker-compose-rest.yml` drivable from a single `--env-file`.
6+
Validated end-to-end on `internal/unify-docker-compose` (Windows + Docker Desktop, Compose
7+
v2.40): BE up from one `.env.local`, FE on `ng serve` rendering the LINDAT/CLARIAH-CZ home in
8+
a real browser with demo content (6 communities, 471 indexed items), 200s, no CORS errors.
9+
Builds on PR #1289, which makes `docker/docker-compose-rest.yml` drivable from one `--env-file`.
1010

1111
> Several non-obvious traps below cost real time to find — read **Gotchas** before starting.
12+
> **Match the BE to the FE version:** this FE reports **7.6.5** (see the browser console
13+
> startup banner), so use a **7.6.5** backend image. A 7.5 BE (`dtq-dev-7.5`) parses most
14+
> things but errors on browse definitions.
1215
1316
---
1417

1518
## Prerequisites
1619

1720
- **Docker** running.
18-
- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node
19-
in ways CI doesn't catch (CI only runs `build:prod`, not `ng serve`). Quick portable
20-
install, no global change:
21+
- **Node 18** for the frontend. NOT 20+/24 — the Angular 15 toolchain breaks on newer Node in
22+
ways CI doesn't catch (CI runs `build:prod`, never `ng serve`). Portable install, no global
23+
change:
2124
```bash
2225
D=/c/Users/$USER/AppData/Local/Temp/node18setup; mkdir -p "$D" && cd "$D"
2326
curl -fsSL -o n18.zip https://nodejs.org/dist/v18.20.5/node-v18.20.5-win-x64.zip
2427
/c/Windows/System32/tar.exe -xf n18.zip # MSYS tar can't unzip; use Windows bsdtar
25-
./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19 # yarn into the portable prefix
26-
export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for the FE commands
28+
./node-v18.20.5-win-x64/npm.cmd install -g yarn@1.22.19
29+
export PATH="$D/node-v18.20.5-win-x64:$PATH" # prepend for FE commands
2730
```
28-
- **The `ng serve` fix** (see Gotcha #2): this branch pins `copy-webpack-plugin@^6.4.1`,
29-
which breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`.
31+
- **The `ng serve` fix** (Gotcha #2): the branch pins `copy-webpack-plugin@^6.4.1`, which
32+
breaks `ng serve`. Bump it to `^11.0.0` in `package.json` and `yarn install`. (This is now
33+
committed on PR #1289.)
3034

3135
---
3236

3337
## TL;DR
3438

3539
```bash
36-
# 1) BACKEND in Docker — fresh DB, migrate-on-boot, ~40-90s to healthy
37-
cp docker/.env.local.example docker/.env.local # then edit per "Backend" below
38-
docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml up -d
40+
# 1) BACKEND in Docker, WITH demo content (7.6.5 to match the FE) — ~2-4 min first boot
41+
cp docker/.env.local.example docker/.env.local # then edit per "Backend"
42+
docker compose --env-file docker/.env.local \
43+
-f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d
3944
# wait: curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8087/server/api => 200
45+
MSYS_NO_PATHCONV=1 docker exec dspace7 /dspace/bin/dspace index-discovery -b # populate Solr
4046

41-
# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already bumped + installed)
47+
# 2) FRONTEND natively (Node 18 on PATH, copy-webpack-plugin already ^11 + installed)
4248
DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \
4349
DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \
44-
yarn start:dev # live-reload on http://localhost:4000
50+
yarn start:dev # live-reload on http://localhost:4000
4551

4652
# 3) teardown (-v wipes DB/Solr volumes — see Gotcha #4)
47-
docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml down -v --remove-orphans
53+
docker compose --env-file docker/.env.local \
54+
-f docker/docker-compose-rest.yml -f docker/db.entities.yml down -v --remove-orphans
4855
```
4956

50-
The FE config maps env vars onto `config/config.yml` (`rest.host``DSPACE_REST_HOST`,
51-
`rest.port``DSPACE_REST_PORT`, …; env wins last — see `src/config/config.server.ts`), so
52-
no file edits are needed to point the FE at the dockerized BE.
57+
The FE maps env vars onto `config/config.yml` (`rest.host``DSPACE_REST_HOST`, … ; env wins
58+
last — see `src/config/config.server.ts`), so no file edits are needed to point it at the BE.
5359

5460
---
5561

5662
## Backend (Docker)
5763

58-
The BE network is fully defined in `docker/docker-compose-rest.yml`, so the BE comes up from
59-
that file **alone** (no `docker-compose.yml` that's the FE container, which we don't use).
64+
`docker/docker-compose-rest.yml` fully defines the network, so the BE comes up from that file
65+
(plus `db.entities.yml` for demo data) — no `docker-compose.yml` (that's the FE container).
6066

61-
`docker/.env.local` for this recipe (tuned for a **native** FE):
67+
`docker/.env.local` for this recipe:
6268

6369
```ini
6470
INSTANCE=7
6571
# 127.0.0.1, NOT localhost (Gotcha #5). BE self-links use REST_URL, so keep it on 127.0.0.1.
6672
DSPACE_HOST=127.0.0.1
6773
DSPACE_REST_PORT=808${INSTANCE} # -> 8087
68-
DSPACE_REST_NAMESPACE=/server # silences a harmless compose warning
74+
DSPACE_REST_NAMESPACE=/server
6975
REST_URL=http://127.0.0.1:808${INSTANCE}/server
7076
UI_URL=http://localhost:4000 # native FE dev-server port, NOT 400${INSTANCE}
71-
HOST_IP=127.0.0.1 # loopback is enough for native FE; no LAN exposure
72-
DSPACE_SUBNET_PREFIX=10.10${INSTANCE} # dodge 172.2x collisions
77+
HOST_IP=127.0.0.1
78+
DSPACE_SUBNET_PREFIX=10.10${INSTANCE}
7379
REST_CORS_ALLOWED_ORIGINS=http://localhost:4000,http://127.0.0.1:4000
74-
# Use the BE that MATCHES this FE branch (Gotcha #6). dtq-dev FE + dtq-dev-7.5 BE = DSpace 7.5.
75-
DSPACE_REST_IMAGE=dataquest/dspace:dtq-dev-7.5
76-
DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x
77-
DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x
80+
81+
# --- Option A: demo content (VERIFIED) — upstream 7.6.5 + db.entities.yml -------------------
82+
# Matches the FE's 7.6.5 and loads the official DSpace demo entities dataset. NOTE: this is
83+
# UPSTREAM DSpace data on an UPSTREAM BE, so CLARIN-specific FE calls 404 (harmless to render).
84+
DSPACE_REST_IMAGE=dspace/dspace:dspace-7_x
85+
DSPACE_DB_IMAGE=dspace/dspace-postgres-pgcrypto:dspace-7_x # db.entities.yml overrides to -loadsql
86+
DSPACE_SOLR_IMAGE=dspace/dspace-solr:dspace-7_x
87+
DOCKER_REGISTRY=docker.io # consumed by db.entities.yml to resolve the -loadsql image
88+
DOCKER_OWNER=dspace
89+
DSPACE_VER=dspace-7_x
90+
91+
# --- Option B: CLARIN fidelity (version-matched, but no content unless you have a dump) ------
92+
# DSPACE_REST_IMAGE=dataquest/dspace:dspace-7_x # DSpace 7.6.5, CLARIN tables/endpoints
93+
# DSPACE_DB_IMAGE=dataquest/dspace-postgres-pgcrypto:dspace-7_x
94+
# DSPACE_SOLR_IMAGE=dataquest/dspace-solr:dspace-7_x
95+
# Fresh DB = empty homepage. For real CLARIN content, restore a dataquest/LINDAT DB dump into
96+
# dspacedb7 (pg_restore/psql) instead of layering db.entities.yml, then reindex.
7897
```
7998

99+
- Bring up Option A with **both** files: `-f docker/docker-compose-rest.yml -f docker/db.entities.yml`.
100+
The `-loadsql` Postgres downloads + imports `dspace7-entities-data.sql` on first boot; the BE
101+
then runs `database migrate ignored`. **Then reindex Solr** (command above) or the homepage's
102+
browse/search/"What's New" stay empty even though the DB has data.
103+
- For Option B (no `db.entities.yml`), use just `-f docker/docker-compose-rest.yml`.
80104
- **Ports** (INSTANCE=7): REST `8087`, JVM debug `8007`, Postgres `5437`, Solr `8987`. Change
81105
`INSTANCE` to re-target all at once. 5 and 8 are reserved by `.github/workflows/deploy.yml`.
82-
- First boot runs `dspace database migrate force` on an empty DB. No content, no admin user
83-
(the homepage is intentionally near-empty — see "What success looks like"). To create an
84-
admin:
85-
```bash
86-
docker compose --env-file docker/.env.local -f docker/docker-compose-rest.yml -f docker/cli.yml \
87-
run --rm dspace-cli create-administrator -e admin@test.dev -f admin -l user -p admin -c en -o dataquest
88-
```
106+
- 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`
89107

90108
### Verify the BE
91109
```bash
92-
curl -s http://127.0.0.1:8087/server/api # dspaceName/dspaceVersion (=> DSpace 7.5)
110+
curl -s http://127.0.0.1:8087/server/api # dspaceVersion => DSpace 7.6.5
111+
curl -s http://127.0.0.1:8087/server/api/core/communities/search/top # totalElements: 6 (demo)
93112
curl -s -i -X OPTIONS http://127.0.0.1:8087/server/api/core/items \
94113
-H 'Origin: http://localhost:4000' -H 'Access-Control-Request-Method: GET' \
95114
| grep -i access-control-allow-origin # => http://localhost:4000
96115
```
97-
(Confirmed: allowed origin → `200` + matching `Access-Control-Allow-Origin`; other origin → `403`.)
98116

99117
---
100118

101119
## Frontend (native, live-editable)
102120

103121
```bash
104-
# one-time: Node 18 on PATH, copy-webpack-plugin bumped to ^11, deps installed
105122
export PATH="/c/Users/$USER/AppData/Local/Temp/node18setup/node-v18.20.5-win-x64:$PATH"
106123
yarn install # after the copy-webpack-plugin bump, or if node_modules is partial
107-
108124
DSPACE_REST_SSL=false DSPACE_REST_HOST=127.0.0.1 DSPACE_REST_PORT=8087 \
109125
DSPACE_UI_HOST=localhost DSPACE_UI_PORT=4000 \
110126
yarn start:dev # ng serve, live-reload, http://localhost:4000
111127
```
112128

113-
- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds, what you want for editing).
114-
`yarn start` does a full SSR production build instead (slower; for an SSR-accurate check).
115-
- The browser calls the BE **directly** at `http://127.0.0.1:8087/server`, so the BE CORS list
116-
must include `http://localhost:4000` (it does, above).
117-
- Do **not** set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); the config default
118-
`/server` is correct.
129+
- `yarn start:dev` = `ng serve` (CSR dev server, fast rebuilds what you want for editing).
130+
`yarn start` does a full SSR production build instead.
131+
- The browser calls the BE directly at `http://127.0.0.1:8087/server`; the CORS list must include
132+
`http://localhost:4000` (it does).
133+
- Don't set `DSPACE_REST_NAMESPACE` from a Git-Bash shell (Gotcha #1); config default `/server`
134+
is correct.
119135

120136
### What success looks like
121-
`http://localhost:4000` renders the DSpace shell + the cookie-consent (Klaro) banner; the main
122-
content area is empty because the repo is fresh. Browser DevTools → Network shows
123-
`GET http://127.0.0.1:8087/server/api → 200`, `.../authn/status → 200`,
124-
`.../discover/browses → 200`, `.../core/sites → 200`, with **no CORS errors**. A few benign
125-
console errors are normal here: `favicon.ico` 404, Matomo refused (no analytics container),
126-
`google.analytics.key` 404, and two DSpace-7.5 nuances (`/api/security/csrf` 404 and a
127-
browse-definitions parse warning) — none block rendering.
137+
`http://localhost:4000` redirects to `/home` and renders the **LINDAT/CLARIAH-CZ Repository
138+
Home**: search + facets (Author/Subject/Language), a **"What's New"** list of items, and the
139+
footer. DevTools → Network shows `…8087/server/api/*` 200s with no CORS errors. Benign console
140+
errors are normal: `favicon.ico` 404, Matomo refused, `google.analytics.key` 404, `/security/csrf`
141+
404, and (with Option A's upstream BE) some CLARIN-specific 404s.
128142

129143
---
130144

131145
## Gotchas (each one cost time to find)
132146

133-
1. **Git Bash mangles `DSPACE_REST_NAMESPACE=/server`** into `C:/Program Files/Git/server`
134-
(MSYS path conversion of a leading-slash value), corrupting the REST baseUrl. Fix: don't
135-
pass it (config default `/server` applies), or `export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`.
136-
Inside the `.env.local` file it's NOT mangled — only shell exports are.
147+
1. **Git Bash mangles leading-slash paths.** `DSPACE_REST_NAMESPACE=/server` becomes
148+
`C:/Program Files/Git/server`, and `docker exec dspace7 /dspace/bin/dspace …` becomes
149+
`…/Git/dspace/bin/dspace` (no such file). Fix: prefix the command with
150+
`MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'`. (Values inside the `.env.local` file are NOT
151+
mangled — only shell args.)
137152

138153
2. **`ng serve` is broken on this branch out of the box.** `package.json` pins
139-
`copy-webpack-plugin@^6.4.1`, but `@angular-builders/custom-webpack` (the serve builder)
140-
resolves it for build-angular@15, which passes the `priority` option (v9+ only) →
141-
`An unhandled exception occurred: Copy Plugin … unknown property 'priority'`. CI misses it
142-
(it runs `build:prod`, not `ng serve`). Fix: set `"copy-webpack-plugin": "^11.0.0"` in
143-
`package.json` (matches build-angular's pin) and `yarn install`. Validated: `ng serve` then
144-
`✓ Compiled successfully`. (If pushing this fix, let CI re-run `build:prod` + tests.)
154+
`copy-webpack-plugin@^6.4.1`, but build-angular@15 (via `@angular-builders/custom-webpack`)
155+
emits asset-copy patterns using the `priority` option (v9+) → `Copy Plugin … unknown property
156+
'priority'`. CI misses it (it runs `build:prod`, not `ng serve`). Fix: `^11.0.0` (matches
157+
build-angular) + `yarn install`. (Committed on PR #1289.)
145158

146-
3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a
147-
cascade of `NG6002`/`NG8004` → run a full `yarn install`.
159+
3. **Stale / partial `node_modules`.** `Can't resolve 'd3'` / `'ngx-skeleton-loader'` + a cascade
160+
of `NG6002`/`NG8004` → run a full `yarn install`.
148161

149-
4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by
150-
a different Postgres major gives `FATAL: database files are incompatible with server`; the BE
151-
then hangs forever in its DB-wait loop and REST never comes up. Fix: `down -v` for a clean
152-
start. `--remove-orphans` clears a leftover `dspace-angular<INSTANCE>` container.
162+
4. **Stale Postgres volume version mismatch.** Re-using an old `dspace-7_pgdata` initialised by a
163+
different Postgres major gives `FATAL: database files are incompatible with server`; the BE
164+
then hangs forever in its DB-wait loop. Fix: `down -v` for a clean start (`--remove-orphans`
165+
clears a leftover `dspace-angular<INSTANCE>` container).
153166

154167
5. **`localhost``127.0.0.1` for the FE→BE hop.** Node 18 resolves `localhost` to IPv6 `::1`
155-
with no IPv4 fallback, but the BE publishes on `127.0.0.1` only (`host_ip`), so SSR/Node
156-
fetches `ECONNREFUSED` and you get `undefined doesn't contain the link sites`. Use
157-
`DSPACE_REST_HOST=127.0.0.1` (and `REST_URL=http://127.0.0.1:...` so the BE's HAL self-links
158-
match). `curl` hides this because it falls back to IPv4.
159-
160-
6. **Pair the FE with the matching BE version.** The `dtq-dev` FE branch against
161-
`dataquest/dspace:dspace-7_x` (DSpace 7.6.5) makes the FE's HAL parser recurse →
162-
`RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Use the
163-
branch's matching BE, `dataquest/dspace:dtq-dev-7.5` (DSpace 7.5).
164-
165-
7. **Orphaned `ng serve` keeps port 4000.** Stopping the `nodemon` parent doesn't kill its
166-
spawned `ng serve` child, so the next start crashes on `Port 4000 is already in use` (the
167-
interactive prompt throws on a non-TTY). Kill the listener first:
168+
with no IPv4 fallback, but the BE publishes on `127.0.0.1` only, so Node fetches `ECONNREFUSED`
169+
`undefined doesn't contain the link sites`. Worse, a host MISMATCH (FE on `127.0.0.1` but BE
170+
self-links on `localhost`) made the HAL parser resolve objects under two hostnames and recurse
171+
`RangeError: Maximum call stack size exceeded` in `DspaceRestResponseParsingService`. Keep
172+
**both** `DSPACE_REST_HOST` and `REST_URL` on `127.0.0.1`. (`curl` hides this — it falls back
173+
to IPv4.)
174+
175+
6. **Match the BE to the FE version.** The FE reports **7.6.5**, so use a **7.6.5** BE
176+
(`dspace/dspace:dspace-7_x` or `dataquest/dspace:dspace-7_x`). The `dataquest/dspace:dtq-dev-7.5`
177+
image is **DSpace 7.5** and causes `An error occurred while retrieving the browse definitions`
178+
in the console.
179+
180+
7. **Demo data ≠ empty repo, and Solr needs reindexing.** A fresh DB shows an empty homepage —
181+
that's expected, not a bug. Layer `db.entities.yml` (Option A) for sample content, then run
182+
`index-discovery -b` so browse/search/"What's New" populate. (The public
183+
`dspace7-entities-data.sql` now ships future 8.0/9.0/10.0 flyway entries; a 7.6.5 BE still
184+
reads the data fine via `migrate ignored`.)
185+
186+
8. **Orphaned `ng serve` keeps port 4000.** Stopping `nodemon` doesn't kill its `ng serve` child,
187+
so the next start crashes on `Port 4000 is already in use`. Kill the listener first:
168188
```bash
169189
pid=$(netstat -ano | grep LISTENING | grep ':4000' | awk '{print $NF}' | head -1)
170-
taskkill //F //T //PID $pid # Git Bash needs the // flag form
190+
taskkill //F //T //PID $pid
171191
```
192+
And do NOT switch git branches while `ng serve` runs — it watches the working tree and will
193+
recompile against the wrong files. Stop it first (or use a separate `git worktree`).
172194

173195
---
174196

175197
## Alternative: fully containerized FE (PR #1289's documented path)
176198

177-
To run the FE in Docker too (no native Node), use `docker/.env.local.example` as-is
178-
(`DSPACE_HOST=host.docker.internal`, `HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up
179-
**both** files with a local build:
199+
To run the FE in Docker too, use `docker/.env.local.example` as-is (`host.docker.internal`,
200+
`HOST_IP=0.0.0.0`, CORS on `400${INSTANCE}`) and bring up **both** compose files with `--build`:
180201
```bash
181202
docker compose --env-file docker/.env.local \
182203
-f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build
183204
# FE on http://localhost:4007 ; this build path exercises the Dockerfile cp fix from PR #1289.
184205
```
185206
`HOST_IP=0.0.0.0` publishes every BE port (incl. Postgres pwd `dspace`, JVM debug) on all
186-
interfaces — dev-only, not for an untrusted network. The native-FE recipe above avoids this.
207+
interfaces — dev-only, not for an untrusted network.

0 commit comments

Comments
 (0)