feat: environment variables (to support fhircast)#5988
Conversation
✅ Deploy Preview for ohif-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
d0ccfa1 to
8140baf
Compare
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
FHIRCast support for MedPlum staged as draft pull request. |
c67f366 to
9841432
Compare
…lient id plumbing Platform enablers for FHIRcast v3.0.0 / SMART on FHIR integrations that live in out-of-tree plugins (e.g. github.com/node-on-fhir/nof-ohif-viewer): - EXTRA_EXTENSIONS / EXTRA_MODES env vars append plugins to pluginConfig at build time, including out-of-tree packages via name=directory entries, so deployments can add plugins without editing tracked files - /fhir-proxy dev-server proxy (FHIR_SERVER env var, WebSocket-enabled for FHIRcast subscriptions); env-configured PROXY_* entries now prepend to the proxy list instead of replacing it - SMART_CLIENT_ID added to the DefinePlugin whitelist with a documented .env placeholder - ModeRoute guards against the active data source not yet being registered when extension dependencies finish loading (SMART launch flows) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9841432 to
a5802bd
Compare
|
Looks like we have a stale cache, due to the pnpm migration (#6031). If somebody with Netlify access could click the "Clear cache and retry" button, that will unjam this.
|
554b16c to
a5802bd
Compare
The chore(version) release-bot commits rewrote internal @ohif/* deps from workspace:* to concrete 3.13.0-beta.92 across the workspace package.json files but never regenerated the lockfile, so the lockfile specifiers still read workspace:*. CI's default --frozen-lockfile install aborted with ERR_PNPM_OUTDATED_LOCKFILE. Regenerate so specifiers match the manifests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…IR extension SMART_CLIENT_ID was a build-time DefinePlugin value and FHIR_SERVER / the /fhir-proxy dev-server route were FHIR-specific hooks that don't belong in upstream OHIF. Build-time bake-in also can't be changed without a rebuild, which is wrong for a deployment-specific client ID. These now live in the ohif-fhir-viewer extension as runtime config (window.config / Preferences / URL params), with the dev proxy replaced by server-side CORS. PR OHIF#5988 now carries only the generic core improvements: EXTRA_EXTENSIONS / EXTRA_MODES injection and the Mode.tsx late-dataSource guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tension When an EXTRA_EXTENSIONS package contains a `mode/` subdirectory with a package.json, register that mode automatically — so `EXTRA_EXTENSIONS=<ext>` alone pulls in the extension and its bundled mode, with no separate EXTRA_MODES entry. An explicit EXTRA_MODES still wins (a mode already present by name is left untouched), and extensions without a `mode/` are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| const os = require('os'); | ||
| const path = require('path'); | ||
|
|
||
| // Extra plugins injected via environment variables rather than |
There was a problem hiding this comment.
My preference would be to have another pluginConfig.json file that gets pointed at with a separate ENV value, and can include the default pluginConfig. That way it can be tracked in the specific build without hitting the main set.
Then, as a customization, allow the modes and extensions for each mode list to be customizeable. The default versus optional is a bit tricky since it affects build time, but it could be setup so that a second declaration over-ride or merges with the first one using the customization $set notation.
There was a problem hiding this comment.
I'd also suggest a way to include an https:// referenced plugin that gets dynamically loaded - see the customization PR.
There was a problem hiding this comment.
Yeah, I think I'm following. Let me tinker with it a bit, and see what I can come up with.
(I'm chuffed that I've been able to reduce the ~100 file changes to one essential change.)
There was a problem hiding this comment.
So, just added a APP_PLUGIN_CONFIG environment variable.
Example customPlugin.json:
{
"include": "./pluginConfig.json",
"extensions": [
{
"packageName": "@acme/extension-remote",
"importPath": "https://cdn.example.com/acme-remote.umd.js",
"globalName": "AcmeRemote"
}
]
}Still testing/confirming everything works.
Adds a third composition layer to the plugin-import generator so a build can
pin exactly which extensions/modes it ships in its own tracked file, without
editing the shared default pluginConfig.json. Keeps EXTRA_EXTENSIONS /
EXTRA_MODES unchanged (they still append last).
Layers, each overriding the previous:
1. pluginConfig.json static default
2. APP_PLUGIN_CONFIG=<file> tracked override; may `include` the default
(string or array), merges over it — plain
arrays append (de-duped by package name),
immutability-helper commands like { $set: [] }
replace a list. Same notation the
CustomizationService uses.
3. EXTRA_EXTENSIONS / EXTRA_MODES env name-injection, appended last.
Implemented entirely in writePluginImportsFile.js; all downstream resolution,
aliasing and asset copying already operate on the in-memory config, so they
inherit the merged result unchanged. Includes a cycle guard and fail-fast
errors. Per-mode dependency override left as a documented TODO(scope-B).
Adds pluginConfig.example.json (generic example) and documents the env vars
in platform/app/.env.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ension The ohif-fhir-viewer extension declares jszip and dcmjs-ecg as runtime dependencies, but pnpm-lock.yaml predated them. With frozenLockfile: true, a plain `pnpm install` refused to add them, so webpack failed to resolve both modules. Regenerate the lockfile so a frozen install pulls them in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-resolve the extensions/ohif-fhir-viewer importer after switching its @ohif/* peerDependencies from published semver ranges to workspace:*. This prunes the published @ohif/*@3.12.5 + @cornerstonejs/*@4.15.29 subtree that the extension was pulling in (the broken nested @cornerstonejs/core that failed the webpack build) and links @ohif/core, extension-cornerstone, extension-default, and i18n to the in-tree workspace packages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Context
Environment-variable plugin composition (for the FHIRcast demo, and useful generally).
The bulk of the FHIRcast feature lives in an external OHIF extension:
https://github.com/node-on-fhir/nof-ohif-viewer
This PR adds only the upstream-side hooks that such an extension needs, with a deliberately small, merge-friendly surface:
APP_PLUGIN_CONFIG) — pin a build's plugin set in its own file thatincludes and merges over the default, instead of editing the sharedpluginConfig.json.A deployment opts in entirely through environment variables; the demo's extension, mode, and FHIR data source stay in
nof-ohif-viewer.Environment variables
This PR introduces three new environment variables. All are optional — none are required to build or run OHIF normally — and all are read at build time by
platform/app/.webpack/writePluginImportsFile.js.APP_PLUGIN_CONFIG''(none)includes + merges over the defaultEXTRA_EXTENSIONS''(none)EXTRA_MODES''(none)EXTRA_EXTENSIONS— Comma-separated extension package names appended topluginConfig.extensions. Each entry may be suffixed with=<directory>topoint at an out-of-tree package root (
.resolves to the repo root,~to the home directory). Companion-mode auto-detection: if an injected extension bundles a mode in amode/subdirectory (with its ownpackage.json), that mode is registered automatically — soEXTRA_EXTENSIONS=<ext>alone usuallybrings in both the extension and its mode.
EXTRA_MODES— Comma-separated mode package names, same=<directory>suffix option. Usually unnecessary thanks to companion-mode auto-detection above; reach for it to add a standalone mode (not bundled in an extension) or to override the auto-detected one.APP_PLUGIN_CONFIG— Path to a tracked override plugin-config file (relative toplatform/app/, or absolute, or~-prefixed). The file mayincludeother config files (a string or array of paths, resolved relative to the file itself) — typically the defaultpluginConfig.json— and layer its ownextensions/modes/publicover them. Plain arrays append (de-duped by package name; an override entry with the same name replaces the base one, so you can pin a version or flipdefault). immutability-helper command objects like{ "$set": [...] }replace a list wholesale — the same notation OHIF'sCustomizationServiceuses.Precedence
The plugin config is assembled in three layers at build time, each overriding the previous:
So a tracked file pins the reproducible baseline, and the env vars stay available for ad-hoc, 12-Factor-style additions on top.
Examples
1. Inject the FHIRcast extension — its mode is auto-detected:
2. Inject an out-of-tree extension by directory:
EXTRA_EXTENSIONS=@acme/extension-foo=~/code/acme-foo pnpm dev3. Pin a build's plugin set in a tracked override file.
platform/app/pluginConfig.fhircast.json:{ "include": "./pluginConfig.json", "extensions": [ { "packageName": "@ohif/extension-nof-ohif-viewer" } ], "modes": [ { "packageName": "fhir-viewer" } ] }4. Ship a minimal build —
$setreplaces the mode list wholesale:{ "include": "./pluginConfig.json", "modes": { "$set": [ { "packageName": "@ohif/mode-basic" } ] } }5. Combine a tracked baseline with an ad-hoc addition (env appended last):
A runnable, generic example ships at
platform/app/pluginConfig.example.json, and all three variables are documented inplatform/app/.env.6. Load a remotely hosted extension:
platform/app/pluginConfig.remoteExtension.json:{ "include": "./pluginConfig.json", "extensions": [ { "packageName": "@acme/extension-remote", "importPath": "https://cdn.example.com/acme-remote.umd.js", "globalName": "AcmeRemote" } ] }Late-registered data source guard
platform/app/src/routes/Mode/Mode.tsx— A data source registered by an extension (for example one created during a SMART on FHIR launch) may not be active yet on the render where extension dependencies finish loading.ModeRoutenow bails out and re-runs once the active data source appears (dataSourcewasadded to the effect's dependency array) instead of dereferencing an undefined data source. Without this guard, a mode that activates an extension-provided data source in
onModeInitcan crash on first render.Changes & Results
Here's a summary of the changes — 4 source files, plus the regenerated lockfile:
platform/app/.webpack/writePluginImportsFile.jsTwo build-time composition mechanisms, both feeding the same in-memory
pluginConfigbefore the name, alias, asset-copy, andpluginImports.jscaches arebuilt — so injected/merged plugins are treated identically to those declared in
pluginConfig.json:EXTRA_EXTENSIONS/EXTRA_MODESinjection. Comma-separated package names — each optionally suffixed with=<directory>to point at an out-of-treeplugin root — are appended to the config. This is what lets the demo register
@ohif/extension-nof-ohif-viewerand the node-on-fhir mode without editingtracked files. A companion mode bundled in the extension's
mode/subdirectory is auto-registered, soEXTRA_EXTENSIONSalone usually suffices.APP_PLUGIN_CONFIGlayered override. A newloadPluginConfig()assembles the config in three layers — static default →APP_PLUGIN_CONFIGoverridefile →
EXTRA_*env injection (last).mergePluginConfig()appends plain arrays (de-duped by package name, same-name entries replacing the base) andapplies immutability-helper
$set/$push/etc. command objects — the same notationCustomizationServiceuses.loadConfigWithIncludes()resolves anoverride's
includechain (string or array, relative to the file) with a cycle guard and fail-fast errors. A deployment can thus pin a build's plugin set inits own tracked file without touching the shared default.
platform/app/pluginConfig.example.json(new)A generic, runnable override that
includes the default and demonstrates appending plugins plus the$setreplace notation. Doubles as livingdocumentation.
platform/app/.envDocuments all three environment variables (
EXTRA_EXTENSIONS,EXTRA_MODES,APP_PLUGIN_CONFIG) as commented examples.platform/app/src/routes/Mode/Mode.tsxGuards against
dataSourcebeingundefinedat the point extension dependencies finish loading — which happens when a data source is registered late (e.g.one entered via a SMART on FHIR launch). The effect now bails and re-runs once
dataSourceappears (it is added to the effect's dependency array) instead ofdereferencing
undefined.pnpm-lock.yamlRegenerated against the workspace so
pnpm install --frozen-lockfilestays green in CI (transitive@babel/*supports-color peer resolution; no dependencyadditions or removals —
immutability-helperwas already a dependency via@ohif/core).Overall: this PR integrates the "Node on FHIR" extension and mode into OHIF through environment-variable injection, adding SMART on FHIR client configuration, a WebSocket-capable FHIR proxy for local development, and a guard for data sources registered at SMART-launch time — without modifying any tracked plugin or data-source configuration files.
OHIF Preferences panels supports SMART registration
RIS reading worklist supports a SMART Launch functionality
OHIF extension receives the SMART Launch URL and fetches the FHIR data; Subscribe to FHIRcast
RIS updates the underlying ImagingStudy status; event bus triggers FHIRCast
OHIF receives events via FHIRCast
Testing
3a.. See the following RIS Installation Instructions for a more detailed walkthrough.
3b.
Install OHIF with FHIR Datastore and FHIRCast
The extension and mode are injected at dev-server start via the
EXTRA_EXTENSIONS/EXTRA_MODESenv vars.Register OHIF/Viewers as a SMART on FHIR client (from Preferences config page)
In the RIS:
5.a Register a new user
5.b Load a Synthea sample patient in with the data-importer
5.c Select the patient from the Patient Directory
5.d Use the Order Entry page to add an XR order
5.e Go to the Tech Worklist, start and complete the exam; attach a .dcm file
5.f Go to the Reading Worklist, launch the exam in OHIF
Back in OHIF
6.a Verify that dicom image loads
6.b Subscribe to the patient (third tab)
RIS again
7.a. Read the exam; finalize
Very that OHIF receives the event
Checklist
PR
semantic-release format and guidelines.
Code
etc.)
Public Documentation Updates
additions or removals.
Tested Environment