Skip to content

feat: CLI command and environment variables (to support fhircast) - #6143

Open
awatson1978 wants to merge 18 commits into
OHIF:masterfrom
awatson1978:cli-tool
Open

feat: CLI command and environment variables (to support fhircast) #6143
awatson1978 wants to merge 18 commits into
OHIF:masterfrom
awatson1978:cli-tool

Conversation

@awatson1978

@awatson1978 awatson1978 commented Jul 14, 2026

Copy link
Copy Markdown

Context

Externally developed extensions (for example node-on-fhir/ohif-fhir-viewer, which adds FHIR data sources and SMART-on-FHIR launch toward FHIRcast support) currently require editing tracked files (pluginConfig.json, webpack config) to be loaded by the viewer. That makes hosted/containerized deployments awkward — the plugin set can't vary per environment without forking tracked files — and keeps downstream forks in permanent conflict with upstream at the same lines.

This PR makes plugin registration composable in three layers, so an extension can be injected at build time via environment variables (12-factor style), via a tracked override config, or via the classic pluginConfig.json / ohif-cli path — and all three converge on the same mechanism in writePluginImportsFile.js.

Changes & Results

  • EXTRA_EXTENSIONS / EXTRA_MODES env vars — inject plugins by package name (comma-separated, optional =<directory> override for out-of-tree checkouts) as the last configuration layer. No tracked-file edits needed to load an in-tree or out-of-tree extension.
  • APP_PLUGIN_CONFIG — selects a tracked override plugin-config file that can include the default and extend/replace it (supports immutability-helper command specs like $set/$push). Added pluginConfig.example.json as a template.
  • Companion-mode auto-detection — an extension that bundles a mode in a mode/ subdirectory (with its own package.json) gets that mode registered automatically. Applies to any declared extension the build can locate on disk: in-tree (extensions/) or declared with a directory — via pluginConfig.json, APP_PLUGIN_CONFIG, or EXTRA_EXTENSIONS. (An out-of-tree extension wired up with ohif-cli link-extension still pairs with link-mode, exactly as the CLI intends.)
  • De-duplication — the env layer merges through mergePluginList() (de-dupe by package name, env entry wins), so declaring the same plugin in both a config file and an env var registers it once.
  • ModeRoute data-source race fix (platform/app/src/routes/Mode/Mode.tsx) — a data source registered by an extension (e.g. entered via a SMART-on-FHIR launch) may not be active yet on the render where extension dependencies finish loading; bail out and re-run once it appears instead of crashing.
  • .env documents the new variables; pnpm-lock.yaml regenerated.

Stock behavior is unchanged: with no env vars and the default pluginConfig.json, the generated pluginImports.js is identical (no first-party extension bundles a mode/).

Testing

Clone the example extension out of tree (sibling of Viewers) and install each side:

git clone https://github.com/node-on-fhir/ohif-fhir-viewer
(cd ohif-fhir-viewer && pnpm install --config.auto-install-peers=false)

git clone https://github.com/OHIF/Viewers && cd Viewers
pnpm install

CLI style (works today; verified end-to-end against master with the pnpm-compatible CLI):

pnpm run cli link-extension ../ohif-fhir-viewer
pnpm run cli link-mode ../ohif-fhir-viewer/mode
pnpm dev

Env-var style (this PR): one variable replaces both link commands and leaves the working tree untouched — the bundled mode is auto-detected:

EXTRA_EXTENSIONS="@ohif/fhir-viewer=$PWD/../ohif-fhir-viewer" pnpm dev
# in-tree clones need only the name: EXTRA_EXTENSIONS=@ohif/fhir-viewer pnpm dev

In each case, visit http://localhost:3000/fhir-viewer — the extension and its bundled fhir-viewer mode both load. A stock pnpm dev (no env vars, default pluginConfig) is unaffected.

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary for any public API
    additions or removals. (The new env vars are documented in platform/app/.env; happy to add a docs.ohif.org page if maintainers want one.)

Tested Environment

  • OS: macOS (Apple Silicon)
  • Node version: 25.9.0
  • Browser: Chrome/Chromium (latest)

awatson1978 and others added 13 commits June 12, 2026 17:20
…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>
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>
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>
…d extensions

The bundled-mode auto-detect previously scanned only EXTRA_EXTENSIONS, so an
extension registered via pluginConfig.json (the ohif-cli link-extension path)
loaded without its companion mode. Scan the fully-merged pluginConfig.extensions
list instead, and route the EXTRA_EXTENSIONS/EXTRA_MODES layer through
mergePluginList() so a plugin declared in both places registers once (env wins).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@netlify

netlify Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit b79e0af
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a56c39f458db1000810ca2e
😎 Deploy Preview https://deploy-preview-6143--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

awatson1978 and others added 3 commits July 14, 2026 16:31
The merge commit resolved code conflicts but committed the lockfile with
its git conflict markers still in place. The CI auto-merge of those markers
left an overrides snapshot that no longer matches pnpm-workspace.yaml,
failing the frozen install (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Plugin composition documentation and example configuration were added, the FHIR viewer extension was registered, and mode initialization now waits for an available data source before continuing.

Changes

Plugin composition and mode initialization

Layer / File(s) Summary
Plugin composition configuration
platform/app/.env, platform/app/pluginConfig.example.json, platform/app/pluginConfig.json
Documents layered plugin overrides and extra plugin inputs, adds an example override configuration, and registers @ohif/fhir-viewer version 0.0.1.
Data source readiness during mode loading
platform/app/src/routes/Mode/Mode.tsx
Logs a warning and exits when no active data source exists after extension loading, then retries when dataSource becomes available.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: wayfarer3130

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise, semantic-release styled, and broadly matches the PR’s main plugin-injection and CLI-related changes.
Description check ✅ Passed The description includes the required Context, Changes & Results, Testing, and Checklist sections with concrete details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
platform/app/src/routes/Mode/Mode.tsx (2)

130-155: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make ModeRoute react to active data-source changes. dataSource is read straight from extensionManager, so the [dataSource] dependency only helps if some other render happens. Subscribe to ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED here (as DataSourceWrapper does), or move the value into React state/useSyncExternalStore, so late-registered sources can actually trigger initialization.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform/app/src/routes/Mode/Mode.tsx` around lines 130 - 155, Update
ModeRoute’s data-source handling so changes to extensionManager’s active data
source trigger a React render and rerun the initialization effect. Subscribe to
ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, following the existing
DataSourceWrapper pattern, or expose the value through React
state/useSyncExternalStore; preserve the current dataSource guard and
initialization flow.

143-155: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Guard setStudyInstanceUIDs against stale initialize() completions.
When dataSource changes, an older in-flight initialize() can still resolve and overwrite the newer study list, which drives the viewer with stale data. Add a generation/cancel guard before setting state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform/app/src/routes/Mode/Mode.tsx` around lines 143 - 155, Update the
initializeDataSource effect around dataSource.initialize so asynchronous
completions are invalidated during cleanup or when dependencies change. Track an
active generation/cancellation guard and check it before setStudyInstanceUIDs,
ensuring only the latest initializeDataSource call can update state while
preserving layoutTemplateData cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@platform/app/src/routes/Mode/Mode.tsx`:
- Around line 130-155: Update ModeRoute’s data-source handling so changes to
extensionManager’s active data source trigger a React render and rerun the
initialization effect. Subscribe to
ExtensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, following the existing
DataSourceWrapper pattern, or expose the value through React
state/useSyncExternalStore; preserve the current dataSource guard and
initialization flow.
- Around line 143-155: Update the initializeDataSource effect around
dataSource.initialize so asynchronous completions are invalidated during cleanup
or when dependencies change. Track an active generation/cancellation guard and
check it before setStudyInstanceUIDs, ensuring only the latest
initializeDataSource call can update state while preserving layoutTemplateData
cleanup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3423d86-fa4c-464a-a216-a3f810e1865e

📥 Commits

Reviewing files that changed from the base of the PR and between b880d2a and 8eea47c.

⛔ Files ignored due to path filters (2)
  • platform/app/.webpack/writePluginImportsFile.js is excluded by !**/.webpack/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • platform/app/.env
  • platform/app/pluginConfig.example.json
  • platform/app/pluginConfig.json
  • platform/app/src/routes/Mode/Mode.tsx

The master-merge commit absorbed a local ohif-cli link-extension
registration into the tracked pluginConfig.json. CI clones do not contain
extensions/ohif-fhir-viewer (it is a separate repository), so the generated
pluginImports.js referenced an unresolvable package and the production
build failed. The extension is deployment-time configuration: inject it
via EXTRA_EXTENSIONS or APP_PLUGIN_CONFIG, or re-run the CLI locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
awatson1978 added a commit to node-on-fhir/ohif-fhir-viewer that referenced this pull request Jul 14, 2026
Point at the cli-tool PR branch, show both installation styles
(EXTRA_EXTENSIONS env var and ohif-cli link-extension), document the
companion-mode auto-detection, and warn against committing the
link-extension pluginConfig.json entry (CI clones lack this extension).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant