Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 22
node-version: 26
cache: npm
cache-dependency-path: clients/${{ matrix.app }}/package-lock.json

Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 22
node-version: 26
cache: npm
cache-dependency-path: clients/${{ matrix.app }}/package-lock.json

Expand Down
8 changes: 4 additions & 4 deletions clients/admin/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.7

# Stage 1: build the SPA bundle
FROM node:22-alpine AS build
FROM node:26-alpine AS build
WORKDIR /app

# Install deps with a cached layer
Expand All @@ -13,10 +13,10 @@ COPY . .
RUN npm run build

# Stage 2: serve via nginx
FROM nginx:alpine AS runtime
FROM nginx:1.31.8-alpine AS runtime
WORKDIR /usr/share/nginx/html

# nginx:alpine needs `envsubst` from gettext (it ships busybox without it).
# nginx alpine images need `envsubst` from gettext (it ships busybox without it).
RUN apk add --no-cache gettext

# Replace the default nginx site
Expand All @@ -30,7 +30,7 @@ COPY docker/config.json.template /etc/fsh/config.json.template
COPY docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

# Non-root: nginx:alpine includes the `nginx` user (uid 101).
# Non-root: the nginx alpine image includes the `nginx` user (uid 101).
# We do NOT switch to it here because the upstream image's master process
# needs root to bind :80; nginx will fork workers as the `nginx` user on
# its own per its default config.
Expand Down
19 changes: 16 additions & 3 deletions clients/admin/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import jsxA11y from 'eslint-plugin-jsx-a11y';
// eslint-plugin-jsx-a11y has no ESLint 10 release (jsx-eslint/eslint-plugin-jsx-a11y#1075), so this is
// the es-tooling fork: the same rules and the same recommended set, minus three long-deprecated rules.
// It is registered under the original `jsx-a11y` name so rule ids and disable comments stay unchanged,
// and moving back to upstream once it supports ESLint 10 is only this import.
import jsxA11y from 'eslint-plugin-jsx-a11y-x';
import i18next from 'eslint-plugin-i18next';
// The plugin exports no defaults object; this is 6.x's internal path, and an upgrade that moves it
// fails the lint at import rather than silently dropping the defaults.
import i18nextDefaults from 'eslint-plugin-i18next/lib/options/defaults.js';
import tseslint from 'typescript-eslint';
import { defineConfig } from 'eslint/config';

export default tseslint.config(
// The fork prefixes its rules `jsx-a11y-x/`; map them onto the `jsx-a11y` plugin name used below.
const jsxA11yRecommended = Object.fromEntries(
Object.entries(jsxA11y.configs.recommended.rules).map(([rule, level]) => [
rule.replace(/^jsx-a11y-x\//, 'jsx-a11y/'),
level,
]),
);

export default defineConfig(
{ ignores: ['dist', 'node_modules', '*.config.js'] },
{
extends: [...tseslint.configs.recommended],
Expand All @@ -24,7 +37,7 @@ export default tseslint.config(
},
rules: {
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
...jsxA11yRecommended,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
// autofocus is intentional on dialog search inputs (impersonate / add-members)
// and the login email field — the first field IS the dialog's purpose.
Expand Down
Loading
Loading