feat(data): zero-config dev auth for GraphQLResolver via everywhere view#68
feat(data): zero-config dev auth for GraphQLResolver via everywhere view#68mackenziemcclaskey merged 11 commits intomainfrom
Conversation
During local development, TridentResolver now defaults to posting through a /_we/trident proxy instead of calling Trident directly. The dev proxy (added to the Vite plugin in 'everywhere view') reads gateway and token from the auth login config at request time, so no credentials are needed in plugin source code. - TridentResolver constructor is now (referenceId, schemas, options?) where options.endpoint and options.bearerToken are optional overrides - Explicit endpoint/bearerToken still accepted for production use - /_we/trident middleware proxies to the configured gateway, returning a 401 with a login instruction if no token is stored - Updated README to document auth login as step 1 of the data flow - Auth error messages now direct users to run auth login instead of editing plugin source Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…via view When `everywhere view` starts the dev server, it reads the auth config written by `everywhere auth login` and injects __WE_TRIDENT_ENDPOINT__ and __WE_TRIDENT_TOKEN__ into the HTML as inline globals via transformIndexHtml. TridentResolver picks these up at construction time so developers no longer need to hardcode credentials in their plugin source. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- view.ts: adopt updated EverywhereBaseCommand import path from lib/command.js - README: include `route` in TridentResolver example import Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rospection queries.
…proxy When everywhere view proxies requests to the real Trident API, respect any Authorization header or session cookie already on the incoming request (e.g. from WE-NEXT) instead of unconditionally overwriting with the stored config token. Falls back to the stored token only when no auth is present. Also removes the now-dead /_we/trident proxy path — TridentResolver sends requests directly to /api/data/graphql which is handled by the Vite proxy in non-mock mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WIP commit dropped node_modules/tokens/theme.css from the css-node-import-plugin fixture, breaking the bundler test that checks @import resolution from node_modules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jheddings
left a comment
There was a problem hiding this comment.
Posting the critical findings from a thorough review here as inline comments. There are also several non-blocking concerns and suggestions (e.g. PR description vs. implementation drift, mock-data default, import type consistency, globalThis.window test teardown, window.location.origin baked at construction, missing integration test for the proxy hook) — happy to follow up with those separately if useful.
- Remove fixture package-lock.json that contained private Workday Artifactory registry URLs, violating the public OSS guardrail - Fix README prose: resolver uses window.location.origin/api/data/graphql, not /_we/trident (that proxy path was removed in f42949c) - Remove README production-override example that passed a third options argument to TridentResolver, which only accepts two parameters Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| (globalThis as Record<string, unknown>)['window'] = { | ||
| location: { origin: 'https://tenant.workday.com' }, | ||
| }; | ||
| }); |
There was a problem hiding this comment.
This beforeEach overwrites globalThis.window for every test in the file, but there's no matching afterEach to restore it. Vitest doesn't isolate global-object mutations across files in the same worker by default, so if any later test file relies on happy-dom's real window, behavior becomes order-dependent.
Small fix using vitest's stub helpers:
beforeEach(() => {
vi.stubGlobal('window', { location: { origin: 'https://tenant.workday.com' } });
});
afterEach(() => {
vi.unstubAllGlobals();
});or stash and restore the original explicitly.
There was a problem hiding this comment.
Alternatively, we could drop the explicit dependency on the window global in the TridentResolver class and accept host as a constructor argument instead. This way, you don't have to worry about managing state in the tests, making them less brittle.
- Use vi.stubGlobal/vi.unstubAllGlobals in TridentResolver tests to prevent globalThis.window leaking across test files in the same worker - Flip --mock-data default to false so everywhere view hits the real API after auth login, matching README docs and developer expectations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| (globalThis as Record<string, unknown>)['window'] = { | ||
| location: { origin: 'https://tenant.workday.com' }, | ||
| }; | ||
| }); |
There was a problem hiding this comment.
Alternatively, we could drop the explicit dependency on the window global in the TridentResolver class and accept host as a constructor argument instead. This way, you don't have to worry about managing state in the tests, making them less brittle.
| this.endpoint = `${endpoint}${path}`; | ||
| this.bearerToken = bearerToken; // look at config file for bearer token | ||
| constructor(referenceId: string, schemas: Record<string, ModelSchema>) { | ||
| this.endpoint = `${window.location.origin}/api/data/graphql`; | ||
| this.referenceId = referenceId; |
There was a problem hiding this comment.
Concern — browser-only assumption baked at construction. window.location.origin is referenced unconditionally in the constructor, so:
- Importing/instantiating this class in any non-DOM context (Node test runner without happy-dom, SSR, a Worker without
location, a build-time codegen step) throwsReferenceError: window is not definedbefore the caller can do anything about it. - The endpoint is frozen at construction. A consumer that wants to point at a different gateway in production (different region, multi-tenant routing, an embedded sandbox) has no escape hatch — the README/PR description claim
TridentResolverOptionsprovides one, but it doesn't exist.
Accepting an optional endpoint (or host) on the constructor — defaulting to globalThis.window?.location.origin + '/api/data/graphql' resolved lazily inside execute() — would address both, and dovetails with @mpfilbin's suggestion on the test file.
Proxy auth: - Replace resolveProxyAuth with isUnauthenticated predicate for clarity - Check specifically for we_session cookie instead of any cookie, to avoid unrelated localhost cookies suppressing bearer token injection - Add precedence test and unrelated-cookie test to proxy-auth suite GraphQLResolver (renamed from TridentResolver): - Rename class to GraphQLResolver — Trident is an internal codename - Accept optional endpoint constructor arg so non-browser environments and tests don't need to mock window; defaults to window.location.origin at runtime via globalThis.window?.location.origin - Remove wd-graphql-developer-info and x-api-gateway-originator headers - Export deprecated TridentResolver alias for backwards compat Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CLI package was named @workday/wdcli-plugin-everywhere (a legacy internal wdcli identity). oclif resolves command IDs by looking for the plugin name in node_modules. Because only @workday/everywhere is installed, every command lookup failed with "command everywhere:X not found". Renaming the package to @workday/everywhere fixes the resolution path and removes spurious hidden:true flags from the regenerated manifest. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| { | ||
| "name": "@workday/wdcli-plugin-everywhere", | ||
| "description": "wdcli plugin for managing Workday Everywhere plugins", | ||
| "name": "@workday/everywhere", |
There was a problem hiding this comment.
BUG FIX: This was previously "@workday/wdcli-plugin-everywhere" (a legacy internal identity). oclif resolves command IDs by searching node_modules for the plugin name at runtime. Because only "@workday/everywhere" is installed, every command lookup failed with "command everywhere:X not found". Correcting the name here also removes spurious hidden flags from all user facing commands in the regenerated manifest.
Drops the deprecated TridentResolver re-export entirely and updates all README references to use GraphQLResolver instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
TridentResolvertoGraphQLResolver— same interface, cleaner name that reflects what it actually doesGraphQLResolveraccepts an optionalendpointargument; when omitted it defaults to/api/data/graphqlon the current origin (safe in both browser and Node/SSR environments)everywhere viewnow proxies/api/data/graphqlto the real API server and injects the stored bearer token only when the incoming request carries no existing auth (noAuthorizationheader and nowe_sessioncookie) — so the WE-NEXT shell's session is respected and never overwritten--mock-dataflag default flipped tofalse— real API is used by default when credentials are present; mock mode is opt-in/_we/tridentproxy middlewareGraphQLResolverthroughout and documentauth loginas step 1 of the data connection flowcli/package.jsonnamefield (@workday/wdcli-plugin-everywhere→@workday/everywhere) sonpx @workday/everywhere initresolves correctly — oclif was failing to find the plugin because the name didn't match what's installedTest plan
just test— all 306 tests passjust check— typecheck + lint cleaneverywhere auth login, theneverywhere viewin a plugin directory — confirm requests reach the real GraphQL endpoint with the bearer token injectedwe_sessioncookie present), confirm the stored token is NOT injected (existing auth is preserved)Authorizationheader is sent when no token is configurednpx @workday/everywhere init— confirm the command resolves without "command everywhere:init not found"🤖 Generated with Claude Code