[APPS-2792] Add: wire local execution into the real dev server - #481
Draft
tyffical wants to merge 3 commits into
Draft
Conversation
|
Wires the new direct-import local-execution path (local-execution.ts) into the real Vite dev server: threads server.ssrLoadModule through as the loadModule dependency, drops the bundling step from /__dd/executeAction entirely (debugBundle and executeActionViaCloud still bundle, unchanged), and forwards connectionId end-to-end through makeExecuteActionRemotely so a $.Actions call naming a specific connection actually reaches it instead of being silently dropped. Also forces @datadog/apps-backend and @datadog/action-catalog through Vite's SSR transform pipeline (ssr.noExternal) rather than letting the dev server's default node_modules externalization `require()` them directly -- both ship ESM-only, so an externalized `require()` throws "Cannot use import statement outside a module".
tyffical
force-pushed
the
tiffany.trinh/apps-2792-wire-into-dev-server
branch
from
August 7, 2026 20:24
6e85225 to
ae53df1
Compare
This was referenced Aug 7, 2026
dev-server.test.ts and local-execution.test.ts (build-plugins#480/#484) each defined their own near-identical LoadModule resolver double. Factor the common resolve-or-throw logic into moduleResolverFor in the shared mocks helper so both can build on it instead of duplicating it.
…C-proxy transform server.ssrLoadModule(func.absolutePath) went through the same transform hook that rewrites *.backend.ts into the client-side RPC-proxy stub (globalThis.DD_APPS_RUNTIME.executeBackendFunction(...)) — so local execution's "real" import was actually still the proxy stub, which crashes immediately since that global doesn't exist server-side. Every existing test mocked loadModule directly, so none of them exercised the real transform pipeline and caught this. Mark local execution's own load with a query suffix (matching Vite's own ?raw/?url convention) and have the transform hook skip proxy generation for that specific marked request, deferring to Vite's normal TS/esbuild transform instead. Checking the marker rather than the generic Vite-supplied options.ssr flag keeps this from also affecting any other, unrelated future SSR-context load of the same file.
tyffical
added a commit
that referenced
this pull request
Aug 11, 2026
…function body server.ssrLoadModule(func.absolutePath) goes through the same transform hook (vite/index.ts) that rewrites *.backend.ts into the client-side RPC-proxy stub — so local execution's "real" import can actually still be the proxy stub, which crashes since globalThis.DD_APPS_RUNTIME doesn't exist server-side. Every existing test here mocks loadModule directly, so none of them exercise the real transform pipeline and would catch this. Append the same query-suffix marker introduced in #481 (matching Vite's own ?raw/?url convention) so the shared transform hook can recognize this specific request and skip proxy generation for it. The transform-hook side of this fix lives in #481, since that's where local execution is actually wired to a real, plugin-registered dev server — this PR only needs its own call site and mocks to stay consistent with that contract so the two branches reconcile cleanly whichever merges first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
/__dd/executeActionstill bundles and calls the full cloud round trip. This PR is what actually makesnpm run devfast: it swaps the customer-facing endpoint over to the in-process path and drops the bundling step from it entirely.$.Actionscalls droppedconnectionIdend-to-end, so an action naming a specific connection had no way to reach it. Now threaded throughExecuteAction→makeExecuteActionRemotely→ the single-actionpreview-asyncquery spec.Architecture
createDevServerMiddlewarenow routes the two execution endpoints down genuinely different paths — one bundle-free and in-process, one bundling and cloud-bound — that only reconverge at the sharedsubmitQuery/pollQueryExecutionhelpers once an$.Actionscall needs to reach the real Datadog API:The
executeActionpath never bundles at all —executeScriptLocallyimports the customer's real file directly vialoadModule(Vite's ownssrLoadModule, so it gets the same TS-transform/resolve rules and HMR-aware module cache a real request gets) and runs the exported function in this process. No auth check happens until the function actually calls$.Actions; that call becomes its own direct single-actionpreview-asyncquery viamakeExecuteActionRemotely, rather than being wrapped in a whole-script query. TheexecuteActionViaCloudpath is the unchanged production round trip: bundle the whole function with Rollup, wrap it as ajsFunctionWithActionsquery, and submit/poll it the same way. See the RFC's Proposed Solution for the design-level version of this split.Changes
/__dd/executeActionnow looks up the requested function and runs it directly viaexecuteScriptLocally— no bundling on this path at all./__dd/debugBundleand the cloud round trip (/__dd/executeActionViaCloud) are unchanged and still bundle.makeExecuteActionRemotelynow forwardsconnectionIdinto the single-actionpreview-asyncquery spec ({fqn, inputs, connectionId}) instead of silently dropping it.createDevServerMiddlewaretakes a newloadModule: LoadModuleparameter, threaded fromvite/index.ts'sconfigureServer(server)asserver.ssrLoadModule.bind(server)— the real Vite dev server's own module loader, giving the local path the same TS-transform/resolve rules and HMR-aware module cache a real request gets.config()hook returningssr: { noExternal: [...] }for@datadog/apps-backend/@datadog/action-catalog. Found while testing: both ship ESM-only, and Vite's dev server externalizesnode_modulesby default (a plainrequire(), for speed) — which throwsCannot use import statement outside a modulethe first time a customer's function actually uses either SDK locally.noExternalforces Vite's SSR transform pipeline to handle them instead, matching how the production bundling path already inlines every dependency.createServer, middleware mode, no port bound) rooted at the sameapps_backend_projectfixture, and lets its realssrLoadModuleimport a real.backend.tsfile directly — no mocked bundler, no mockedloadModule. Confirms a real@datadog/apps-backendtyped import resolves$.Sourcecorrectly through this exact path.$.Actions), a clear error when a function does call$.Actionswith no auth configured, the single-actionpreview-asyncrequest-body shape now includingconnectionId, and the newconfig()hook'sssr.noExternalcontract. Existing cloud-path tests unchanged aside from the newloadModuleparameter threaded through everycreateDevServerMiddlewarecall.QA Instructions
yarn test:unit packages/plugins/apps # Expected: Test Suites: 24 passed / Tests: 314 passed ✅ VERIFIEDyarn workspace @dd/apps-plugin run typecheck # Expected: no output, clean exit ✅ VERIFIEDnpx eslint packages/plugins/apps/src/vite/dev-server.ts packages/plugins/apps/src/vite/dev-server.test.ts packages/plugins/apps/src/vite/dev-server.integration.test.ts packages/plugins/apps/src/vite/index.ts packages/plugins/apps/src/vite/index.test.ts --quiet # Expected: no output, clean exit ✅ VERIFIEDNo manual local/staging QA beyond the automated real end-to-end test above (a real Vite dev server, real
ssrLoadModule, real@datadog/apps-backendfixture): this branch isn't merged tomainyet, so there's no released package to link into a real scaffolded app and click through. Manual QA against a realnpm run devsession is planned once this stack is closer to landing.Blast Radius
npm run dev's/__dd/executeActionnow executes locally by direct import, with no bundling step, instead of round-tripping to the cloud. Still gated behind this whole stack not being released yet (no version bump, nobump.yamltrigger in this PR)./__dd/executeActionViaCloud) — nothing currently calling/__dd/executeActionin production exists yet (this endpoint isn't released), so there's no live caller to break.ssr.noExternalconfig change affects every Vite dev-server session this plugin runs in, not just the local-execution path — low risk in practice (it only forces two specific, already-known-to-this-plugin packages through the transform pipeline instead of externalizing them), but worth noting as a config-surface change.Out of Scope / Follow-ups
npm run dev:verifyCLI (mode-aware routing to/__dd/executeActionViaCloud, web-ui template changes)@datadog/action-catalogfixture package for a typed-import e2e test$.ActionsroutingDocumentation