diff --git a/.dockerignore b/.dockerignore
index 983bc03b7..a62025d0c 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,17 +4,24 @@
!.prettierrc.json
!.prod
!.release-please-manifest.json
+!.env.example
+!.env.runtime.template
+!.env.playwright.local.example
+!.env.development.local.example
!codegen.yml
!commitlint.config.js
!eslint.config.mjs
!index.html
!package.json
+!pnpm-lock.yaml
+!pnpm-workspace.yaml
!printVersion.js
!public
!release-please-config.json
+!scripts
+!scripts/**
!src
!tsconfig.json
!tsconfig.node.json
!vite.config.ts
!vitest.config.ts
-!yarn.lock
diff --git a/.env.development.local.example b/.env.development.local.example
index e46d70182..7787b96f6 100644
--- a/.env.development.local.example
+++ b/.env.development.local.example
@@ -1,10 +1,7 @@
-# Local environment variable overrides:
-# See https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used
+# Local overrides for Vite development mode.
#
-# Files on the left have more priority than files on the right:
-# - npm start: .env.development.local, .env.local, .env.development, .env
-# - npm run build: .env.production.local, .env.local, .env.production, .env
-# - npm test: .env.test.local, .env.test, .env (note .env.local is missing)
+# File naming follows Vite conventions. This file is intended for local-only
+# development overrides and should not be committed.
# Endpoint URI overrides for the fully local development environment..
# When running a local API, then also the LinkedEvents needs to be local.
diff --git a/.env.example b/.env.example
index 949678dd6..9fb331c11 100644
--- a/.env.example
+++ b/.env.example
@@ -1,17 +1,21 @@
-# Default environment variables
-# See https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used
+# Default environment variables for Vite-based tooling.
#
-# Files on the left have more priority than files on the right:
-# - npm start: .env.development.local, .env.local, .env.development, .env
-# - npm run build: .env.production.local, .env.local, .env.production, .env
-# - npm test: .env.test.local, .env.test, .env (note .env.local is missing)
+# Vite file priority (highest to lowest):
+# 1) Existing process env
+# 2) .env.[mode].local
+# 3) .env.[mode]
+# 4) .env.local
+# 5) .env
+#
+# In this project:
+# - pnpm dev uses mode "development"
+# - pnpm build uses mode "production"
+# - pnpm test:coverage loads .env and .env.test in src/test/vitest-setup.ts
VITE_APP_ORIGIN=http://localhost:3000
# Staging CMS URI:
VITE_APP_CMS_URI=https://kultus.app-staging.hkih.hion.dev/graphql
# Production CMS URI:
# VITE_APP_CMS_URI=https://kultus.content.api.hel.fi/graphql
-VITE_APP_APPLICATION_NAME=$npm_package_name
-VITE_APP_VERSION=$npm_package_version
# Sentry
VITE_APP_SENTRY_ENVIRONMENT=local
diff --git a/.env.runtime.template b/.env.runtime.template
new file mode 100644
index 000000000..0c063c787
--- /dev/null
+++ b/.env.runtime.template
@@ -0,0 +1,27 @@
+VITE_APP_ORIGIN=
+VITE_APP_CMS_URI=
+VITE_APP_SENTRY_ENVIRONMENT=
+VITE_APP_SENTRY_DSN=
+VITE_APP_SENTRY_TRACES_SAMPLE_RATE=
+VITE_APP_SENTRY_TRACE_PROPAGATION_TARGETS=
+VITE_APP_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=
+VITE_APP_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=
+VITE_APP_CSP_REPORT_URI=
+VITE_APP_API_URI=
+VITE_APP_API_REPORT_URI=
+VITE_APP_LINKEDEVENTS_API_URI=
+VITE_APP_OIDC_AUDIENCES=
+VITE_APP_OIDC_RETURN_TYPE=
+VITE_APP_OIDC_SERVER_TYPE=
+VITE_APP_OIDC_API_CLIENT_ID=
+VITE_APP_OIDC_AUTHORITY=
+VITE_APP_OIDC_CLIENT_ID=
+VITE_APP_OIDC_SCOPE=
+VITE_APP_OIDC_AUTOMATIC_SILENT_RENEW_ENABLED=
+VITE_APP_HELSINKI_PROFILE_URL=
+VITE_APP_IDLE_TIMEOUT_IN_MS=
+VITE_APP_APPLICATION_NAME=
+VITE_APP_VERSION=
+VITE_APP_COMMITHASH=
+VITE_APP_BUILDTIME=
+VITE_APP_SENTRY_RELEASE=
diff --git a/.env.test b/.env.test
new file mode 100644
index 000000000..d1022fcc9
--- /dev/null
+++ b/.env.test
@@ -0,0 +1,36 @@
+# Test environment variables (used by Vitest + update-runtime-env in test mode)
+
+VITE_APP_ORIGIN=http://localhost:3000
+VITE_APP_CMS_URI=https://kultus.app-staging.hkih.hion.dev/graphql
+VITE_APP_APPLICATION_NAME=$npm_package_name
+VITE_APP_VERSION=$npm_package_version
+
+# Sentry
+VITE_APP_SENTRY_ENVIRONMENT=test
+VITE_APP_SENTRY_DSN=
+VITE_APP_SENTRY_TRACES_SAMPLE_RATE=1.0
+VITE_APP_SENTRY_TRACE_PROPAGATION_TARGETS=http://localhost:8081/graphql
+VITE_APP_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=0
+VITE_APP_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=0
+VITE_APP_CSP_REPORT_URI=
+
+# API / backend services
+VITE_APP_API_URI=https://kultus.api.test.hel.ninja/graphql
+VITE_APP_API_REPORT_URI=https://kultus.api.test.hel.ninja/reports
+VITE_APP_LINKEDEVENTS_API_URI=https://linkedevents.api.test.hel.ninja/v1
+
+# OIDC client configuration
+VITE_APP_OIDC_AUDIENCES=kultus-api-test,profile-api-test
+VITE_APP_OIDC_RETURN_TYPE=code
+VITE_APP_OIDC_SERVER_TYPE=KEYCLOAK
+VITE_APP_OIDC_API_CLIENT_ID=kultus-api-test
+VITE_APP_OIDC_AUTHORITY=https://tunnistus.test.hel.ninja/auth/realms/helsinki-tunnistus
+VITE_APP_OIDC_CLIENT_ID=kultus-admin-ui-test
+VITE_APP_OIDC_SCOPE="openid profile email"
+VITE_APP_OIDC_AUTOMATIC_SILENT_RENEW_ENABLED=1
+
+# Helsinki profile UI URI (for link)
+VITE_APP_HELSINKI_PROFILE_URL=https://profiili.test.hel.ninja/loginsso
+
+# Client session timeout in ms
+VITE_APP_IDLE_TIMEOUT_IN_MS=3600000
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b6843aed9..85e99cfc4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,16 +2,15 @@ name: CI
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
pull_request:
- branches: [ main, master ]
+ branches: [main, master]
workflow_dispatch:
jobs:
common:
- uses: City-of-Helsinki/.github/.github/workflows/ci-node.yml@main
- secrets: inherit
+ uses: City-of-Helsinki/.github/.github/workflows/ci-pnpm-node.yml@main
+ secrets:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
- node-version: 22
- extra-commands: |
- cp .env.example .env
+ node-version: 24.x
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 810e51f2a..8eb7e3531 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@ sonar-report.*
# production
/build
+/public/env-config.js
+/public/test-env-config.js
# misc
.DS_Store
@@ -24,7 +26,6 @@ sonar-report.*
.env.playwright.local
.env.production
.env.production.local
-.env.test
.env.test.local
npm-debug.log*
diff --git a/.husky/pre-commit b/.husky/pre-commit
index e80c56d23..668e2fc70 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,2 +1,2 @@
-yarn doctoc . -u
-yarn lint-staged --relative
\ No newline at end of file
+pnpm doctoc . -u
+pnpm lint-staged --relative
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
index cc7ce7f49..f3c88209a 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v22.13.1
+v24.15.0
diff --git a/Dockerfile b/Dockerfile
index 9754c63ee..5ef64fce5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,41 +1,31 @@
-# ===============================================
-FROM registry.access.redhat.com/ubi9/nodejs-22 AS appbase
-# ===============================================
-
-# install yarn
-USER root
-RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo
-RUN yum -y install yarn
-
+# ============================================================
+# STAGE 1: Build the Static Assets
+# ============================================================
+FROM helsinki.azurecr.io/ubi9/nodejs-24-pnpm-builder-base AS appbase
WORKDIR /app
-# Offical image has npm log verbosity as info. More info - https://github.com/nodejs/docker-node#verbosity
-ENV NPM_CONFIG_LOGLEVEL warn
-
-# set our node environment, either development or production
-# defaults to production, compose overrides this to development on build and run
-ARG NODE_ENV=${NODE_ENV:-"production"}
-ENV NODE_ENV $NODE_ENV
-
-# Global npm deps in a non-root user directory
-ENV NPM_CONFIG_PREFIX=/app/.npm-global
-ENV PATH=$PATH:/app/.npm-global/bin
-
-# Yarn
-ENV YARN_VERSION 1.22.22
-RUN yarn policies set-version ${YARN_VERSION}
+# 1. Copy needed files for build
+COPY --chown=default:root package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json tsconfig.node.json ./
+COPY --chown=default:root ./public ./public
+COPY --chown=default:root ./scripts ./scripts
+COPY --chown=default:root .env.runtime.template ./
-# Copy package.json and package-lock.json/yarn.lock files
-COPY --chown=default:root package*.json *yarn* ./
+# 2. Run the install
+# corepack in the base image will automatically use the version of pnpm
+# defined in your package.json 'packageManager' field if present.
+RUN pnpm install --frozen-lockfile --ignore-scripts && pnpm store prune
+RUN pnpm update-runtime-env
-# Install npm dependencies
-ENV PATH /app/node_modules/.bin:$PATH
+# 3. Copy remaining source files
+COPY --chown=default:root index.html vite.config.ts eslint.config.mjs .prettierrc.json .env* ./
+COPY --chown=default:root ./src ./src
-RUN yarn install --frozen-lockfile --ignore-scripts && yarn cache clean --force
-# =============================
+# ============================================================
+# STAGE 2: Development
+# ============================================================
FROM appbase AS development
-# =============================
+WORKDIR /app
# Set NODE_ENV to development in the development container
ARG NODE_ENV=development
@@ -48,99 +38,40 @@ ENV NODE_ENV $NODE_ENV
ARG CHOKIDAR_USEPOLLING=true
ENV CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING}
-# copy in our source code last, as it changes the most
-COPY --chown=default:root . .
+# Expose port and start development server
+EXPOSE 8080
+CMD pnpm exec vite --port 8080 --no-open --host
-# Bake package.json start command into the image
-CMD ["yarn", "dev", "--no-open", "--host"]
-# ===================================
+# ============================================================
+# STAGE 3: Static builder for production
+# ============================================================
FROM appbase AS staticbuilder
-# ===================================
-
-# Oidc authority
-ARG VITE_APP_OIDC_AUDIENCES
-ARG VITE_APP_OIDC_RETURN_TYPE
-ARG VITE_APP_OIDC_SERVER_TYPE
-ARG VITE_APP_OIDC_API_CLIENT_ID
-ARG VITE_APP_OIDC_AUTHORITY
-ARG VITE_APP_OIDC_CLIENT_ID
-ARG VITE_APP_OIDC_SCOPE
-ARG VITE_APP_OIDC_AUTOMATIC_SILENT_RENEW_ENABLED
-
-# Sentry variables
-ARG VITE_APP_SENTRY_ENVIRONMENT
-ARG VITE_APP_SENTRY_DSN
-ARG VITE_APP_SENTRY_TRACES_SAMPLE_RATE
-ARG VITE_APP_SENTRY_TRACE_PROPAGATION_TARGETS
-ARG VITE_APP_SENTRY_REPLAYS_SESSION_SAMPLE_RATE
-ARG VITE_APP_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE
-ARG VITE_APP_CSP_REPORT_URI
-
-# Api url
-ARG VITE_APP_API_URI
-ARG VITE_APP_CMS_URI
-ARG VITE_APP_API_REPORT_URI
-
-# Linkedevents api url
-ARG VITE_APP_LINKEDEVENTS_API_URI
-
-# Application's origin (i.e. where this application is hosted)
-ARG VITE_APP_ORIGIN
-
-# Helsinki profile URL
-ARG VITE_APP_HELSINKI_PROFILE_URL
-
-# Time before user logout if idle
-ARG VITE_APP_IDLE_TIMEOUT_IN_MS
-
-ARG NODE_OPTIONS
-
-# Fix FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed -
-# JavaScript heap out of memory: https://github.com/vitejs/vite/issues/2433.
-ENV NODE_OPTIONS=${NODE_OPTIONS}
-
-# Release information
-ARG VITE_APP_SENTRY_RELEASE
-ARG VITE_APP_COMMITHASH
-ARG VITE_APP_BUILDTIME
-
-# Use template and inject the environment variables into .prod/nginx.conf
-ENV VITE_APP_BUILDTIME=${VITE_APP_BUILDTIME:-""}
-ENV VITE_APP_RELEASE=${VITE_APP_SENTRY_RELEASE:-""}
-ENV VITE_APP_COMMITHASH=${VITE_APP_COMMITHASH:-""}
-COPY .prod/nginx.conf.template /tmp/.prod/nginx.conf.template
-RUN export APP_VERSION=$(yarn --silent app:version) && \
- envsubst '${APP_VERSION},${VITE_APP_BUILDTIME},${VITE_APP_RELEASE},${VITE_APP_COMMITHASH}' < \
- "/tmp/.prod/nginx.conf.template" > \
- "/tmp/.prod/nginx.conf"
-
-# Copy all files
-COPY . /app
-
-# Build
-RUN yarn build
-
-# =============================
-FROM registry.access.redhat.com/ubi9/nginx-122 AS production
-# =============================
-# Add application sources to a directory that the assemble script expects them
-# and set permissions so that the container runs without root access
-ARG PORT
-
-USER root
-
-RUN chgrp -R 0 /usr/share/nginx/html && \
- chmod -R g=u /usr/share/nginx/html
-
-# Copy static build
+
+# Perform the build
+ARG VITE_SENTRY_RELEASE
+RUN pnpm build
+
+
+# ============================================================
+# STAGE 4: Production Runtime
+# ============================================================
+FROM helsinki.azurecr.io/ubi10/nginx-126-spa-standard AS production
+
+ARG VITE_SENTRY_RELEASE
+ENV APP_RELEASE=${VITE_SENTRY_RELEASE:-""}
+# 1. Copy the compiled assets
COPY --from=staticbuilder /app/build /usr/share/nginx/html
-# Copy nginx config
-COPY --from=staticbuilder /tmp/.prod/nginx.conf /etc/nginx/nginx.conf
-USER 1001
+# 2. Setup Runtime Env Injection
+# env.sh is provided by the base image
+WORKDIR /usr/share/nginx/html
+COPY --from=staticbuilder /app/.env.runtime.template ./.env
-# Run script uses standard ways to run the application
-CMD ["/bin/bash", "-c", "nginx -g \"daemon off;\""]
+# 3. Inject Versioning for the /readiness endpoint from package.json using base image
+COPY package.json .
-EXPOSE ${PORT:-8080}
+# - env.sh (Inherited from base image at /usr/share/nginx/html/env.sh)
+# - USER 1001 (Inherited from base image)
+# - EXPOSE 8080 (Inherited from base image)
+# - ENTRYPOINT/CMD (Inherited from base image)
diff --git a/README.md b/README.md
index ebe6276fc..1e9121755 100644
--- a/README.md
+++ b/README.md
@@ -22,15 +22,15 @@ Providers' UI - A restricted administration client for Kultus event providers (f
- [Pre-commit Hook](#pre-commit-hook)
- [Commit-msg Hook](#commit-msg-hook)
- [Available Scripts](#available-scripts)
- - [`yarn dev`](#yarn-dev)
- - [`yarn test:watch`](#yarn-testwatch)
- - [`yarn test:changed`](#yarn-testchanged)
- - [`yarn test:staged`](#yarn-teststaged)
- - [`yarn test:coverage`](#yarn-testcoverage)
- - [`yarn ci`](#yarn-ci)
- - [`yarn test:debug`](#yarn-testdebug)
- - [`yarn build`](#yarn-build)
- - [`yarn codegen`](#yarn-codegen)
+ - [`pnpm dev`](#pnpm-dev)
+ - [`pnpm test:watch`](#pnpm-testwatch)
+ - [`pnpm test:changed`](#pnpm-testchanged)
+ - [`pnpm test:staged`](#pnpm-teststaged)
+ - [`pnpm test:coverage`](#pnpm-testcoverage)
+ - [`pnpm ci`](#pnpm-ci)
+ - [`pnpm test:debug`](#pnpm-testdebug)
+ - [`pnpm build`](#pnpm-build)
+ - [`pnpm codegen`](#pnpm-codegen)
- [Headless CMS](#headless-cms)
- [Headless CMS React Components -lib](#headless-cms-react-components--lib)
- [Releases, changelogs and deployments](#releases-changelogs-and-deployments)
@@ -207,8 +207,8 @@ This project is built using the following key frameworks and libraries:
Compatibility defined by [Dockerfile](./Dockerfile):
- Docker
-- Node.js 22.x
-- Yarn
+- Node.js 24.x
+- pnpm
#### About Kultus data models (and relations to LinkedEvents)
@@ -307,7 +307,7 @@ Using the following instructions you should be able to:
>
> If you connect to a Docker container from your local computer, you can usually use https://localhost:8081/ to reach the API.
-5. Run the Node application in development mode with `yarn dev`. At this point you should be also running the local APIs if you have selected to use them.
+5. Run the Node application in development mode with `pnpm dev`. At this point you should be also running the local APIs if you have selected to use them.
##### Running using Docker
@@ -378,12 +378,12 @@ This project uses [Husky](https://typicode.github.io/husky/#/) to manage Git hoo
The pre-commit hook is configured to run the following commands:
```sh
-yarn doctoc .
-yarn lint-staged --relative
+pnpm doctoc .
+pnpm lint-staged --relative
```
-- `yarn doctoc .`: This command updates the table of contents in your markdown files.
-- `yarn lint-staged --relative`: This command runs linting on staged files to ensure they meet the project's coding standards. The lint-staged configuration can be found from [lint-staged.config.js](./lint-staged.config.js).
+- `pnpm doctoc .`: This command updates the table of contents in your markdown files.
+- `pnpm lint-staged --relative`: This command runs linting on staged files to ensure they meet the project's coding standards. The lint-staged configuration can be found from [lint-staged.config.js](./lint-staged.config.js).
- Using `--relative` flag to reduce command line length,
as the combined length of all the absolute paths for a large commit can get quite long
@@ -404,7 +404,7 @@ npx --no-install commitlint --edit "$1"
In the project directory, you can run:
-#### `yarn dev`
+#### `pnpm dev`
Runs the app in the development mode.
Open http://localhost:3000/ to view it in the browser.
@@ -412,33 +412,33 @@ Open http://localhost:3000/ to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
-#### `yarn test:watch`
+#### `pnpm test:watch`
Runs the tests and watches for changes in interactive watch mode.
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more
information.
-#### `yarn test:changed`
+#### `pnpm test:changed`
Run tests against uncommitted changes (including staged and unstaged).
-#### `yarn test:staged`
+#### `pnpm test:staged`
Run tests against staged changes, used in husky git commit hook.
-#### `yarn test:coverage`
+#### `pnpm test:coverage`
Run tests and generate coverage report
-#### `yarn ci`
+#### `pnpm ci`
Run tests and generate coverage report, meant for Continuous Integration (CI) pipeline use.
-#### `yarn test:debug`
+#### `pnpm test:debug`
Debug tests
-#### `yarn build`
+#### `pnpm build`
Builds the app for production to the `build` folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
@@ -448,7 +448,7 @@ Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-#### `yarn codegen`
+#### `pnpm codegen`
Codegen settings in codegen.yml
diff --git a/compose.yml b/compose.yml
index 723770440..cefd066c3 100644
--- a/compose.yml
+++ b/compose.yml
@@ -7,7 +7,7 @@ services:
environment:
NODE_ENV: ${DOCKER_TARGET:-development} # which node env the build uses. NOTE: Usually same as the ${DOCKER_TARGET}.
VITE_ENVIRONMENT: ${DOCKER_TARGET:-development}
- PORT: ${PORT:-3000}
+ PORT: ${PORT:-8080}
env_file:
- ".env"
- ".env.development.local"
@@ -19,7 +19,7 @@ services:
# to avoid ownership and/or platform issues:
- /app/node_modules
ports:
- - 3000:${PORT:-3000}
+ - 3000:${PORT:-8080}
networks:
default:
name: helsinki
diff --git a/index.html b/index.html
index 53d74329c..64cb002d9 100644
--- a/index.html
+++ b/index.html
@@ -48,9 +48,10 @@
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the