-
Notifications
You must be signed in to change notification settings - Fork 25
Description
Is your feature request related to a problem? Please describe.
SmartForms currently relies on Vite build-time environment variables (import.meta.env). This means any change to server URLs, launch parameters, or feature flags requires a full rebuild of the application. In Kubernetes or containerised deployments where the same image is promoted across multiple environments (dev → test → prod), this approach is inefficient and complicates CI/CD workflows.
Describe the solution you'd like
Add support for runtime dynamic configuration loading:
- Provide a mechanism to fetch a
config.jsonfile at application start-up before rendering the UI. - Merge these runtime values with
import.meta.envand sensible defaults. - Document a Kubernetes deployment pattern where
config.jsonis mounted or generated from a ConfigMap. - Include a TypeScript helper (
loadRuntimeConfig()) to load and validate configuration before bootstrapping the app.
This allows environment-specific values to be applied without rebuilding the SPA.
Describe alternatives you've considered
-
Option 2B: Inject
window.__RUNTIME_CONFIG__via script
Works well but requires modifyingindex.htmland container entrypoints. Option 2A is simpler for static hosting (S3, CloudFront). -
Rebuilding per environment
Current approach but increases build complexity and slows deployments. -
Reverse proxy routing only
Useful for API host changes but does not cover other variables like OAuth scope or feature flags.
Additional context
Variables to support
VITE\_ONTOSERVER\_URL
VITE\_FORMS\_SERVER\_URL
VITE\_LAUNCH\_SCOPE
VITE\_LAUNCH\_CLIENT\_ID
VITE\_IN\_APP\_POPULATE
VITE\_PRESERVE\_SYM\_LINKS
MODE
1) Deployment Pattern (Kubernetes)
Mount or generate config.json from a ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: smartforms-runtime-config
data:
config.json: |
{
"VITE_ONTOSERVER_URL": "https://ontoserver.prod.example.com",
"VITE_FORMS_SERVER_URL": "https://forms.prod.example.com",
"VITE_LAUNCH_SCOPE": "openid profile",
"VITE_LAUNCH_CLIENT_ID": "smartforms-client",
"VITE_IN_APP_POPULATE": true,
"VITE_PRESERVE_SYM_LINKS": false,
"MODE": "production"
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: smartforms
spec:
template:
spec:
volumes:
- name: config-volume
configMap:
name: smartforms-runtime-config
containers:
- name: nginx
image: aehrc/smart-forms:latest
volumeMounts:
- name: config-volume
mountPath: /usr/share/nginx/html/config.json
subPath: config.json2) TypeScript Helper (src/config/runtime.ts)
export interface SmartFormsConfig {
VITE_ONTOSERVER_URL: string;
VITE_FORMS_SERVER_URL: string;
VITE_LAUNCH_SCOPE: string;
VITE_LAUNCH_CLIENT_ID: string;
VITE_IN_APP_POPULATE: boolean;
VITE_PRESERVE_SYM_LINKS: boolean;
MODE: string;
}
let cfg: SmartFormsConfig | null = null;
export async function loadRuntimeConfig(): Promise<SmartFormsConfig> {
if (cfg) return cfg;
const res = await fetch(`${import.meta.env.BASE_URL}config.json`, { cache: 'no-cache' });
if (!res.ok) throw new Error('Failed to load runtime config');
const json = await res.json();
cfg = {
VITE_ONTOSERVER_URL: json.VITE_ONTOSERVER_URL ?? import.meta.env.VITE_ONTOSERVER_URL,
VITE_FORMS_SERVER_URL: json.VITE_FORMS_SERVER_URL ?? import.meta.env.VITE_FORMS_SERVER_URL,
VITE_LAUNCH_SCOPE: json.VITE_LAUNCH_SCOPE ?? import.meta.env.VITE_LAUNCH_SCOPE,
VITE_LAUNCH_CLIENT_ID: json.VITE_LAUNCH_CLIENT_ID ?? import.meta.env.VITE_LAUNCH_CLIENT_ID,
VITE_IN_APP_POPULATE: json.VITE_IN_APP_POPULATE ?? (import.meta.env.VITE_IN_APP_POPULATE === 'true'),
VITE_PRESERVE_SYM_LINKS: json.VITE_PRESERVE_SYM_LINKS ?? (import.meta.env.VITE_PRESERVE_SYM_LINKS === 'true'),
MODE: json.MODE ?? import.meta.env.MODE ?? 'development'
};
return cfg;
}Usage in main.tsx:
import ReactDOM from 'react-dom/client';
import App from './App';
import { loadRuntimeConfig } from './config/runtime';
loadRuntimeConfig()
.then(() => ReactDOM.createRoot(document.getElementById('root')!).render(<App />))
.catch(err => console.error('Config load failed', err));3) Example config.json
{
"VITE_ONTOSERVER_URL": "https://ontoserver.prod.example.com",
"VITE_FORMS_SERVER_URL": "https://forms.prod.example.com",
"VITE_LAUNCH_SCOPE": "openid profile",
"VITE_LAUNCH_CLIENT_ID": "smartforms-client",
"VITE_IN_APP_POPULATE": true,
"VITE_PRESERVE_SYM_LINKS": false,
"MODE": "production"
}4) Unit Tests (Vitest)
import { describe, it, expect, vi } from 'vitest';
import { loadRuntimeConfig } from './runtime';
describe('loadRuntimeConfig()', () => {
it('loads config.json and merges with env', async () => {
const mockJson = {
VITE_ONTOSERVER_URL: 'https://rt.onto',
VITE_FORMS_SERVER_URL: 'https://rt.forms',
VITE_IN_APP_POPULATE: true
};
global.fetch = vi.fn().mockResolvedValue({ ok: true, json: () => mockJson });
const cfg = await loadRuntimeConfig();
expect(cfg.VITE_ONTOSERVER_URL).toBe('https://rt.onto');
expect(cfg.VITE_FORMS_SERVER_URL).toBe('https://rt.forms');
expect(cfg.VITE_IN_APP_POPULATE).toBe(true);
});
it('throws if fetch fails', async () => {
global.fetch = vi.fn().mockResolvedValue({ ok: false });
await expect(loadRuntimeConfig()).rejects.toThrow();
});
});Acceptance Criteria
- App fetches
config.jsonbefore rendering. - Fallback order works:
config.json→import.meta.env→ defaults. - Kubernetes manifest and helper documented.
- Unit tests pass in CI.
✅ This enhancement makes SmartForms Kubernetes-friendly, reduces rebuild overhead, and supports dynamic configuration for Ontoserver, Forms server, and OAuth parameters using a simple config.json pattern.