Replace the @astrojs/sitemap integration with two handwritten endpoints
under src/pages/ so we can attach a Google News <news:news> block to each
aviso detail URL. The integration already emitted the news namespace
but never the per-URL blocks, so the project was not eligible for
Google News indexing.
- src/pages/sitemap-index.xml.ts: single-child sitemapindex pointing at
/sitemap-0.xml.
- src/pages/sitemap-0.xml.ts: home + huella + one entry per aviso from
getAvisosAmbientales. Each aviso carries <news:news> with
publication name (fuente), language ca, publication_date (ISO),
title and keywords (temas joined).
- astro.config.mjs: drop the @astrojs/sitemap integration; remove the
dependency from package.json and pnpm-lock.yaml.
- e2e: add a test asserting the news namespace and at least one
<news:news> block with publication_date and language. 22/22 e2e
pass; check and eslint clean.
Coolify builds were failing with ERR_PNPM_OUTDATED_LOCKFILE because
the devDependencies added in recent commits (eslint, playwright,
@astrojs/check, tsx, sharp, etc.) had been installed with npm locally
and the pnpm-lock.yaml in the repo was stale.
- Regenerate pnpm-lock.yaml from current package.json.
- Promote sharp to a direct devDependency (scripts/build-og-image.ts
imports it; pnpm cannot resolve transitive packages outside the
importer graph).
- Pin esbuild and sharp as allowed build scripts in pnpm-workspace.yaml
so pnpm 11's default deny policy does not skip their native postinstall
on a clean install. Both are required: esbuild for the Vite/Astro
binary, sharp for the OG image rasterizer.
- Add a pnpm engine pin (>=9) so installs that target the new schema
fail fast instead of silently downgrading.
Verified locally: pnpm install (clean), pnpm run prebuild (regenerates
OG images, astro check, eslint), pnpm run build and pnpm run test:e2e
all pass (20/20).
scripts/build-og-image.ts (renamed from .mjs, run via tsx) now also
queries getAvisosAmbientales and rasterizes a 1200x630 PNG per aviso at
public/og/avis/[origen]-[id].png. The SVG is built inline from the aviso
fields: title (truncated to 110 chars), summary (200), source and
municipio as meta line. [slug].astro passes /og/avis/<slug>.png + the
title as alt to BaseLayout, which emits og:image, og:image:alt,
twitter:image and twitter:image:alt automatically.
e2e: detail page test now also asserts og:image points at the per-aviso
PNG and that the PNG returns 200. 20/20 e2e pass; check and eslint
clean.
Pin eslint@9 (jsx-a11y peer) and add eslint-plugin-astro + eslint-plugin-jsx-a11y + typescript-eslint as devDeps.
eslint.config.js wires:
- @eslint/js recommended and typescript-eslint recommended for general .ts/.mjs.
- astro flat/jsx-a11y-recommended for .astro files, registering the jsx-a11y plugin explicitly.
- project-specific tweaks: drop jsx-a11y/anchor-is-valid (noisy on Astro links), warn on click-events-have-key-events and no-static-element-interactions, allow any in scripts/tests, ignore env.d.ts triple-slash ref (Astro convention).
Scripts: lint and lint:fix. prebuild now also runs lint, so the same chain guards a11y regressions as type and build errors.
CI workflow gains a lint step between check and build. 0 errors, 16/16 e2e still pass.
Make typecheck part of the local guard rails without new tooling:
- prebuild now runs the OG image rasterizer and astro check before astro build.
- pretest:e2e runs astro check and astro build so playwright always exercises a fresh, type-checked dist.
> ibiza-environmental-watch@0.1.0 prebuild
> node scripts/build-og-image.mjs && astro check
generated /home/izg/REPOS/EivissaEnvirnmentalWatch/public/og-default.png (34393 bytes)
21:39:47 [types] Generated 169ms
21:39:47 [check] Getting diagnostics for Astro files in /home/izg/REPOS/EivissaEnvirnmentalWatch...
Result (18 files):
- 0 errors
- 0 warnings
- 0 hints
> ibiza-environmental-watch@0.1.0 build
> astro build
21:40:03 [types] Generated 151ms
21:40:03 [build] output: "static"
21:40:03 [build] mode: "static"
21:40:03 [build] directory: /home/izg/REPOS/EivissaEnvirnmentalWatch/dist/
21:40:03 [build] Collecting build info...
21:40:03 [build] ✓ Completed in 331ms.
21:40:03 [build] Building static entrypoints...
21:40:04 [vite] ✓ built in 1.00s
21:40:04 [vite] ✓ built in 356ms
21:40:04 [build] Rearranging server assets...
generating static routes
21:40:04 ├─ /404.html (+43ms)
21:40:04 ├─ /huella/index.html (+6ms)
21:40:04 ├─ /robots.txt (+4ms)
21:40:04 ├─ /rss.xml (+114ms)
21:40:04 ├─ /index.html (+32ms)
21:40:04 ✓ Completed in 247ms.
21:40:04 [build] ✓ Completed in 1.74s.
21:40:04 [@astrojs/sitemap] `sitemap-index.xml` created at `dist`
21:40:04 [build] 3 page(s) built in 2.11s
21:40:04 [build] Complete! and
> ibiza-environmental-watch@0.1.0 pretest:e2e
> astro check && astro build
21:40:09 [types] Generated 149ms
21:40:09 [check] Getting diagnostics for Astro files in /home/izg/REPOS/EivissaEnvirnmentalWatch...
Result (18 files):
- 0 errors
- 0 warnings
- 0 hints
21:40:24 [types] Generated 101ms
21:40:24 [build] output: "static"
21:40:24 [build] mode: "static"
21:40:24 [build] directory: /home/izg/REPOS/EivissaEnvirnmentalWatch/dist/
21:40:24 [build] Collecting build info...
21:40:24 [build] ✓ Completed in 330ms.
21:40:24 [build] Building static entrypoints...
21:40:26 [vite] ✓ built in 1.48s
21:40:26 [vite] ✓ built in 380ms
21:40:26 [build] Rearranging server assets...
generating static routes
21:40:26 ├─ /404.html (+81ms)
21:40:26 ├─ /huella/index.html (+14ms)
21:40:26 ├─ /robots.txt (+9ms)
21:40:26 ├─ /rss.xml (+134ms)
21:40:27 ├─ /index.html (+32ms)
21:40:27 ✓ Completed in 322ms.
21:40:27 [build] ✓ Completed in 2.30s.
21:40:27 [@astrojs/sitemap] `sitemap-index.xml` created at `dist`
21:40:27 [build] 3 page(s) built in 2.66s
21:40:27 [build] Complete!
> ibiza-environmental-watch@0.1.0 test:e2e
> playwright test
Running 16 tests using 6 workers
✓ 6 [desktop] › tests/e2e/site.spec.ts:71:3 › site smoke › robots.txt built from Astro.site and points at the sitemap (359ms)
✓ 7 [desktop] › tests/e2e/site.spec.ts:79:3 › site smoke › sitemap lists the two real pages and not the 404 (619ms)
✓ 8 [desktop] › tests/e2e/site.spec.ts:92:3 › build artifacts › og PNG exists with the right dimensions (73ms)
✓ 2 [desktop] › tests/e2e/site.spec.ts:54:3 › site smoke › 404 page is noindex and reachable (2.1s)
✓ 1 [desktop] › tests/e2e/site.spec.ts:62:3 › site smoke › JSON-LD on the home parses and exposes Organization + ItemList (3.3s)
✓ 4 [desktop] › tests/e2e/site.spec.ts:24:3 › site smoke › skip-link is the first interactive element and jumps to main (4.2s)
✓ 5 [desktop] › tests/e2e/site.spec.ts:33:3 › site smoke › filter pills toggle aria-pressed and update the live counter (4.4s)
✓ 10 [mobile] › tests/e2e/site.spec.ts:24:3 › site smoke › skip-link is the first interactive element and jumps to main (2.8s)
✓ 14 [mobile] › tests/e2e/site.spec.ts:71:3 › site smoke › robots.txt built from Astro.site and points at the sitemap (274ms)
✓ 15 [mobile] › tests/e2e/site.spec.ts:79:3 › site smoke › sitemap lists the two real pages and not the 404 (538ms)
✓ 16 [mobile] › tests/e2e/site.spec.ts:92:3 › build artifacts › og PNG exists with the right dimensions (50ms)
✓ 11 [mobile] › tests/e2e/site.spec.ts:33:3 › site smoke › filter pills toggle aria-pressed and update the live counter (3.1s)
✓ 13 [mobile] › tests/e2e/site.spec.ts:62:3 › site smoke › JSON-LD on the home parses and exposes Organization + ItemList (1.9s)
✓ 12 [mobile] › tests/e2e/site.spec.ts:54:3 › site smoke › 404 page is noindex and reachable (1.9s)
✓ 3 [desktop] › tests/e2e/site.spec.ts:9:3 › site smoke › home renders and reports no axe violations @ desktop (12.7s)
✓ 9 [mobile] › tests/e2e/site.spec.ts:9:3 › site smoke › home renders and reports no axe violations @ desktop (7.5s)
16 passed (23.4s) therefore catch type and a11y regressions before code ships, mirroring what a precommit hook would do.
Add @playwright/test, axe-core and @axe-core/playwright as devDependencies.
- playwright.config.ts with desktop+mobile projects and a webServer that boots astro preview on 127.0.0.1:4321.
- tests/e2e/site.spec.ts covers: home renders with H1 and textual map summary; skip-link is the first focusable and jumps to main; filter pills toggle aria-pressed and update the live counter; 404 is noindex; JSON-LD parses and exposes Organization+WebSite+ItemList; robots.txt and sitemap are wired to Astro.site; the 404 is excluded from the sitemap; the generated og-default.png is 1200x630.
- Scripts: test:e2e runs the suite, test:e2e:install pulls the chromium headless shell.
- .gitignore ignores test-results/, playwright-report/ and the playwright cache.
All 16 tests pass (8 desktop, 8 mobile).
Add scripts/build-og-image.mjs (sharp) hooked as npm prebuild to materialize a 1200x630 PNG alongside the SVG. BaseLayout now points og:image/twitter:image at the PNG, which the major crawlers preview reliably (SVG support is patchy). The SVG stays in the repo as the source of truth.
Add npm run check and check:watch and pin @astrojs/check + typescript in devDependencies so the existing strict tsconfig actually runs. Current build is clean (0 errors).