When the orchestration interface was first built, the API client, state stores,
and UI views all lived inside ADP-Orchestration-UI. That kept iteration fast
early on, but as other tools—such as IDE extensions and test harnesses—needed to
talk to the orchestration backend, keeping client logic bundled into a single web
app became a bottleneck.
During this window, that monolith was cleanly separated. The core API client was
extracted into @adp/client, while the visual components were separated into
@adp/ui, each published as standalone packages at v0.2.0.
Extracting code out of an established SPA into an npm package immediately exposed
hidden bundler assumptions. The original codebase was built entirely within
Vite. When @adp/[email protected] was first installed into downstream consumers
running standard Node test runners, it failed in two distinct ways (605ec5e):
- Top-level bundler globals: Multiple modules read
import.meta.env.VITE_*at the root level for API URLs and Keycloak settings. In standard Node ESM or externalized Vitest execution,import.meta.envis undefined, causing an immediate runtime crash on package import. This was solved by routing all lookups through a defensivereadViteEnv()wrapper that returnsundefinedoutside Vite, allowing normal nullish-coalescing fallbacks to operate. - Missing ESM file extensions: With
"moduleResolution": "bundler", TypeScript emits relative import paths without.jsextensions. Vite resolves these effortlessly, but strict Node ESM refuses to load extensionless specifiers. A dedicated post-build step (scripts/fix-esm-extensions.mjs) was added to rewrite relative imports indist/to include explicit.jsextensions, verified directly vianpm packand strict Node imports.
Following the initial packaging fix, the client tackled authentication edge
cases. PR #1 addressed a persistent post-login redirect loop in Keycloak
(IP-E12-01). Rather than relying on ambient window location during OAuth
handshakes, the client now requires and preserves an explicit redirectUri
through all login and token exchange flows, bumping @adp/client to v0.3.0.
Meanwhile, @adp/ui completed its separation (146c6ec), resolved missing
optional platform binary entries in its lockfile (4a6dc7b), and successfully
cut v0.2.0 backed by the corrected client library.
Lessons learned
- Client SDKs extracted from bundlers must not assume bundler runtime globals.
Code written inside Vite frequently references
import.meta.envwithout a second thought. Once published as an npm package consumed by server scripts or Node CLI tools, those bare accesses throwTypeError. Guard environment reads with defensive helper functions. - TypeScript bundler module resolution does not generate valid Node ESM specifiers.
Relying on
"moduleResolution": "bundler"meanstscemits extensionless import specifiers that break outside bundlers. If a library needs to run under native Node ESM, run an explicit post-processing step to append.jsextensions to emitted files. - Pass redirect targets explicitly through authentication client abstractions.
Implicitly guessing post-auth redirect paths based on browser window state
breaks down across different hosting routes. Threading
redirectUriexplicitly through login methods prevents circular auth loops.