Skip to content

[APPS-2945] Update: switch the apps plugin's static checks to the Node backend runtime - #557

Open
tyffical wants to merge 1 commit into
tiffany.trinh/apps-2961-parallel-local-executionfrom
tiffany.trinh/apps-2945-follow-terrapin-flag
Open

tyffical wants to merge 1 commit into
tiffany.trinh/apps-2961-parallel-local-executionfrom
tiffany.trinh/apps-2945-follow-terrapin-flag

Conversation

@tyffical

@tyffical tyffical commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Architecture

flowchart TD
    T[".backend.* transform"] --> R["runBackendStaticChecks"]
    G["dev-server module-graph walk"] --> R
    B["production backend build (moduleParsed)"] --> R
    R --> M["rejectGlobalsMissingFromNode"]
    M --> Q{"XMLHttpRequest, EventSource or Deno used where undefined throws?"}
    Q -->|"bare use, or dereferenced, called, constructed, extended, destructured"| E["build error with a hint"]
    Q -->|"typeof, if, ??, delete, case, polyfill install, destructuring default"| A["allowed"]
Loading

Changes

8 changes across the static checks, vite/index.ts, constants.ts, README.md and tests
What changed File
Reject XMLHttpRequest, EventSource and Deno with hints. reject-globals-missing-from-node.ts
Remove the Deno-only checks. run-backend-static-checks.ts
Report a watched global only where undefined throws. ambient-global-access.ts
Share the AST parent-map helper. walk-ast.ts
Warn at dev-server start on a local Node major other than 24. vite/index.ts
Add NODE_BACKEND_RUNTIME and its Node major. constants.ts
Document the rules and their gaps. README.md
Test must-reject and must-not-reject forms. reject-globals-missing-from-node.test.ts, index.test.ts

QA Instructions

yarn typecheck:all && yarn lint && yarn cli integrity
# Expected: all exit 0, integrity leaves no file changes ✅ VERIFIED
yarn test:unit packages/plugins/apps
# Expected: Test Suites: 41 total; Tests: 1021 total, all passed except 15 manifest-tags and parameters tests, which fail on macOS's /var TMPDIR ✅ VERIFIED
TMPDIR=<dir outside /var> yarn test:unit packages/plugins/apps/src/vite/manifest-tags.integration.test.ts packages/plugins/apps/src/vite/parameters.integration.test.ts
# Expected: Test Suites: 2 passed, 2 total; Tests: 33 passed, 33 total ✅ VERIFIED
  • Manual QA runs a Vite 8 scaffold against a Datadog staging org with this branch's packed plugin; set DD_SITE=<staging site>, DD_API_KEY and DD_APP_KEY.
QA helper scripts (also used by the stacked PRs)
  • install-plugin.sh:
#!/usr/bin/env bash
# Usage: QA_APP=<scaffold> install-plugin.sh <pr-number> <build-plugins-checkout>
# Builds the checkout's packages, packs @datadog/vite-plugin to vite-plugin-<pr>.tgz here, and installs it into QA_APP.
set -eu
QA_DIR="$(cd "$(dirname "$0")" && pwd)"
W="$2"
echo "building $(git -C "$W" log --oneline -1)"
(cd "$W" && yarn build:all > "$QA_DIR/logs/build-$1.log" 2>&1)
(cd "$W/packages/published/vite-plugin" && yarn pack --out "$QA_DIR/vite-plugin-$1.tgz" > /dev/null)
cd "${QA_APP:?set QA_APP to the scaffold}"
npm install "$QA_DIR/vite-plugin-$1.tgz" > "$QA_DIR/logs/install-$1.log" 2>&1
echo "installed @datadog/vite-plugin $(node -p "require('@datadog/vite-plugin/package.json').version") from vite-plugin-$1.tgz"
  • qa-run.sh:
#!/usr/bin/env bash
# Usage: QA_APP=<scaffold> qa-run.sh <development|dev-verify> <fixture>...   (needs DD_SITE, DD_API_KEY, DD_APP_KEY)
# Starts the scaffold's Vite dev server on 5173 in the given mode with every fetch the process makes logged,
# transforms each src/<fixture>.backend.ts, runs each accepted fixture's exported functions once through
# /__dd/executeAction (dev-verify sends it to the cloud), and prints the plugin's warnings and cloud log lines,
# then every request the process made. QA_NODE picks the Node binary and QA_NODE_OPTIONS adds Node options.
set -u
MODE="$1"; shift
# Fixtures live in src/; QA_URL_PREFIX is their path from Vite's root (empty for an app with root: 'src').
URL_PREFIX="${QA_URL_PREFIX-src/}"
QA_DIR="$(cd "$(dirname "$0")" && pwd)"
mkdir -p "$QA_DIR/logs"
# The request log lives in the temp dir, since a backend function's fetch appends to it inside the guarded run.
LOG="$QA_DIR/logs/vite-$MODE-$$.log"; NET_LOG="$(node -p "require('os').tmpdir()")/qa-net-$MODE-$$.log"; : > "$NET_LOG"
cd "${QA_APP:?set QA_APP to the scaffold}"
if [ -n "$(lsof -ti tcp:5173 -sTCP:LISTEN)" ]; then echo "port 5173 is busy"; exit 1; fi
env -u DD_OAUTH_ACCESS_TOKEN -u DATADOG_OAUTH_ACCESS_TOKEN QA_NET_LOG="$NET_LOG" \
    NODE_OPTIONS="--import $QA_DIR/log-requests.mjs${QA_EXTRA_IMPORT:+ --import $QA_EXTRA_IMPORT}${QA_NODE_OPTIONS:+ $QA_NODE_OPTIONS}" \
    "${QA_NODE:-node}" node_modules/vite/bin/vite.js --mode "$MODE" --port 5173 --strictPort < /dev/null > "$LOG" 2>&1 &
for _ in $(seq 1 180); do curl -s -o /dev/null http://localhost:5173/ && break; sleep 1; done
for fixture in "$@"; do
  module="$(curl -s "http://localhost:5173/$URL_PREFIX$fixture.backend.ts")"
  if ! printf %s "$module" | grep -q executeBackendFunction; then
    message="$(printf %s "$module" | sed 's/\x1b\[[0-9;]*m//g' | grep -oE '"message":"([^"\\]|\\.)*' | head -1)"
    echo "$fixture: rejected: ${message:0:180}"
    continue
  fi
  echo "$fixture: accepted"
  for fn in $(sed -n 's/^export async function \([A-Za-z0-9_]*\).*/\1/p' "src/$fixture.backend.ts"); do
    FN="$(printf %s "$URL_PREFIX$fixture" | shasum -a 256 | cut -d' ' -f1).$fn"
    resp="$(curl -s -m 120 -w '\n%{http_code}' -X POST -H 'Content-Type: application/json' \
      -d "{\"functionName\":\"$FN\",\"args\":[\"QA\"]}" http://localhost:5173/__dd/executeAction)"
    echo "  $fn: $(printf %s "$resp" | sed '$d' | cut -c1-230) [HTTP $(printf %s "$resp" | tail -1)]"
  done
done
echo "plugin log:"
sed 's/\x1b\[[0-9;]*m//g' "$LOG" | grep -E "[|[](warn|error)\||via cloud as|script limit|in a child process|Debugger listening" | sed -E 's/(pid|temp dir|ws:\/\/127\.0\.0\.1:)[^,)]*/\1 <redacted>/g' | cut -c1-230
echo "requests made:"
sed -E "s#https://(api\.)?${DD_SITE}#<staging>#" "$NET_LOG" | sed -E 's#/execution-long-polling/[^ ]+#/execution-long-polling/<receipt>#' | sort | uniq -c
kill "$(lsof -ti tcp:5173 -sTCP:LISTEN)" 2>/dev/null
for _ in $(seq 1 10); do [ -z "$(lsof -ti tcp:5173 -sTCP:LISTEN)" ] && break; sleep 1; done
lsof -ti tcp:5173 -sTCP:LISTEN || echo "server stopped"
  • log-requests.mjs:
// Preloaded with --import: appends the method and URL of every fetch the process makes to $QA_NET_LOG.
import { appendFileSync } from 'node:fs';

const realFetch = globalThis.fetch;
globalThis.fetch = (input, init) => {
    const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
    const method = init?.method ?? (typeof input === 'object' && 'method' in input ? input.method : 'GET');
    appendFileSync(process.env.QA_NET_LOG, `${method} ${url.split('?')[0]}\n`);
    return realFetch(input, init);
};
Local execution and dev:verify: fetch, node:os, feature checks and defaults pass; Deno uses fail
  • src/useFetch.backend.ts:
export async function getStatus() {
    const response = await fetch('https://example.com');
    return response.status;
}
  • src/useNode.backend.ts:
import os from 'os';

export async function getHostAndStatus() {
    const response = await fetch('https://example.com');
    return { host: os.hostname(), status: response.status };
}
  • src/useDeno.backend.ts:
export async function getCwd() {
    return Deno.cwd();
}
  • src/useFeatureCheck.backend.ts:
export async function detectRuntime() {
    const deno = typeof Deno !== 'undefined' || globalThis.Deno?.version !== undefined;
    return { deno, eventSource: 'EventSource' in globalThis };
}
  • src/useDefaults.backend.ts:
export async function fillMissing() {
    const { EventSource = class Polyfill {} } = globalThis;
    switch (undefined) {
        case globalThis.Deno:
            return { eventSource: EventSource.name };
        default:
            return null;
    }
}
  • src/useDenoChain.backend.ts:
export async function getEnv() {
    return (globalThis.Deno?.env).get('X');
}
cd "$QA_DIR" && QA_APP=<vite 8 scaffold> ./install-plugin.sh 557 <build-plugins checkout of this branch>
QA_APP=<vite 8 scaffold> ./qa-run.sh development useFetch useNode useDeno useFeatureCheck useDefaults useDenoChain
# Output: getStatus 200, getHostAndStatus {"host":"<host>","status":200}, 4 "in a child process" lines;
# useDeno and useDenoChain: rejected: "\"Deno\" is not available in the Node backend runtime. Use a Node API or a node: built-in import instead";
# detectRuntime {"deno":false,"eventSource":false}, fillMissing {"eventSource":"Polyfill"} ✅ VERIFIED
QA_APP=<vite 8 scaffold> ./qa-run.sh dev-verify useFetch useNode useDeno useFeatureCheck useDefaults useDenoChain
# Output: the same verdicts and results, 4 preview-async requests to <staging>, getHostAndStatus {"host":"sandbox","status":200} ✅ VERIFIED

Blast Radius

  • Every apps-plugin build and dev server: Node built-ins and fetch pass, and the three missing globals fail where they'd throw.
  • A dev server on a Node major other than 24 logs one warning.
  • No feature flag.
  • Low risk: the checks loosen to match the runtime.

Out of Scope / Follow-ups

5 items deferred
Item Status Next step
Some throwing uses pass: plain assignments, exported values, let aliases of globalThis, bulk copies Accepted dev:verify catches them
A guarded use (if (globalThis.Deno) globalThis.Deno.cwd()) still fails Accepted Use an imported polyfill, as the hint says
child_process imports pass Stacked on this PR [APPS-2945 Update: drop the in-process local execution guard in favor of Node's permission model](#563)
The runtime doesn't pass process.env values Stacked on this PR [APPS-2945 Add: warn on process.env reads in backend functions](#565)
The jsSandboxWithActions and backendEnvironment: 'sandbox' paths are gone Deferred Restore only if Add a backendEnvironment override for app uploads stays

Documentation

🤖 Generated with Claude Code

@tyffical

tyffical commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T04:35:03.872074Z 23556ed Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

Runtime selection is consistently propagated, fails safely to v1, and has comprehensive coverage across affected execution paths.

Review effort: Balanced
Findings: None

What changed in this PR

Aligns backend static checks with each organization’s Terrapin feature-flag runtime while preserving v1 checks for cloud execution and fallback scenarios.

Changes:

  • Adds memoized, authenticated backend-runtime resolution with safe v1 fallback.
  • Threads runtime selection through build, transform, and local module-graph checks.
  • Adds runtime-specific tests and documentation.
File Description
packages/​plugins/​apps/​src/​backend-runtime.ts Resolves and caches the organization’s backend runtime.
packages/​plugins/​apps/​src/​backend-runtime.test.ts Tests authentication, fallback, timeout, caching, and logging.
packages/​plugins/​apps/​src/​backend/​ast-parsing/​run-backend-static-checks.ts Makes existing restrictions v1-only.
packages/​plugins/​apps/​src/​backend/​ast-parsing/​run-backend-static-checks.test.ts Verifies runtime-specific checks and warnings.
packages/​plugins/​apps/​src/​backend/​ast-parsing/​reject-restricted-globals.ts Updates runtime documentation.
packages/​plugins/​apps/​src/​vite/​index.ts Resolves and propagates runtime across Vite workflows.
packages/​plugins/​apps/​src/​vite/​index.test.ts Tests transforms, packaging, local execution, and dev:verify behavior.
packages/​plugins/​apps/​src/​vite/​backend-static-checks-plugin.ts Applies the selected runtime during nested builds.
packages/​plugins/​apps/​src/​vite/​backend-static-checks-plugin.test.ts Covers v1 and v2 plugin behavior.
packages/​plugins/​apps/​src/​vite/​build-backend-functions.ts Propagates runtime into production backend builds.
packages/​plugins/​apps/​src/​vite/​build-backend-functions.test.ts Tests runtime behavior in bundled dependency graphs.
packages/​plugins/​apps/​src/​vite/​dev-server-module-graph.ts Applies runtime-specific checks during local traversal.
packages/​plugins/​apps/​src/​vite/​dev-server-module-graph.test.ts Tests transitive module checks for both runtimes.
packages/​plugins/​apps/​src/​vite/​dev-server.ts Keeps cloud-generated bundles on v1.
packages/​plugins/​apps/​src/​vite/​dev-server.integration.test.ts Updates integration calls with explicit runtime.
packages/​plugins/​apps/​README.md Documents runtime resolution and resulting behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23556ed62a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/plugins/apps/src/backend-runtime.ts Outdated
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 2 times, most recently from 6164a77 to a160fb7 Compare October 7, 2026 18:17
@datadog-prod-us1-6

datadog-prod-us1-6 Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Pipelines  Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: b71e121 | Docs | View more details | Give us feedback!

@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch from 14c5415 to e898bea Compare October 7, 2026 18:33
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 5 times, most recently from abf34ad to 892bda9 Compare October 15, 2026 01:04
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 3 times, most recently from 2352fe9 to e7d5434 Compare October 15, 2026 19:07
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 3 times, most recently from e44fe4a to 141d960 Compare October 16, 2026 00:47
@tyffical
tyffical marked this pull request as ready for review October 16, 2026 01:25
@tyffical
tyffical requested a review from a team as a code owner October 16, 2026 01:25
@tyffical
tyffical requested review from oliverli and removed request for a team October 16, 2026 01:25
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 2 times, most recently from 04671dd to 696d422 Compare October 16, 2026 16:37
@tyffical tyffical changed the title [APPS-2945] Add: follow the org's Terrapin backend runtime in static checks [APPS-2945] Add: follow the org's backend runtime in static checks Oct 16, 2026
Comment thread packages/plugins/apps/src/backend-runtime.test.ts Outdated
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 2 times, most recently from a64a6ad to 31ea49f Compare October 18, 2026 05:41
@tyffical
tyffical requested a review from a team as a code owner October 18, 2026 05:41
@tyffical
tyffical requested review from michaeldfoley and removed request for a team October 18, 2026 05:41
@tyffical tyffical changed the title [APPS-2945] Add: follow the org's backend runtime in static checks [APPS-2945] Update: switch the apps plugin's static checks to the Node backend runtime (v2) Oct 18, 2026
@tyffical
tyffical removed this pull request from stack #559 October 18, 2026 05:56
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 3 times, most recently from e59b683 to c499254 Compare October 22, 2026 16:46
@tyffical
tyffical changed the base branch from master to tiffany.trinh/apps-2961-parallel-local-execution October 22, 2026 16:46
@github-actions

Copy link
Copy Markdown

Live Debugger Runtime Benchmark

SDK-loaded dormant-probe runtime overhead, measured against an uninstrumented bundle in the same browser session.

Browser Workload Quality Per-call overhead upper
chrome Hot clean <= 4.73 ns
chrome Tiny clean <= 0.03 ns
firefox Hot clean <= 9.84 ns
firefox Tiny clean <= 0.18 ns
safari Hot clean <= 5.59 ns
safari Tiny clean <= 0.90 ns

What do the Tiny and Hot workloads represent?

Browser Debugger SDK: 7.16.1 · built 2026-10-21 · S3 ETag 1426183a

Full diagnostics
browser  workload  quality  per-call overhead upper  overhead upper          95% CI        A/A diag        block CI  acf(1)   baseline  instrumented                         samples
-------  --------  -------  -----------------------  --------------  --------------  --------------  --------------  ------  ---------  ------------  ------------------------------
chrome   Hot       clean                 <= 4.73 ns        <= 8.51%   4.71..4.73 ns  -0.01..0.01 ns   4.70..4.73 ns    0.02  50.943 ms     55.270 ms   102 (trim 20%, outliers 8.8%)
chrome   Tiny      clean                 <= 0.03 ns        <= 0.33%  -0.01..0.03 ns  -0.03..0.01 ns  -0.01..0.02 ns    0.00  55.673 ms     55.705 ms  102 (trim 20%, outliers 18.6%)
firefox  Hot       clean                 <= 9.84 ns       <= 35.86%   9.82..9.84 ns  -0.01..0.00 ns   9.82..9.84 ns    0.09  40.620 ms     55.160 ms   102 (trim 20%, outliers 5.9%)
firefox  Tiny      clean                 <= 0.18 ns        <= 1.18%   0.17..0.18 ns  -0.01..0.00 ns   0.17..0.18 ns    0.06  53.220 ms     53.820 ms  102 (trim 20%, outliers 11.8%)
safari   Hot       clean                 <= 5.59 ns       <= 20.55%   5.58..5.59 ns  -0.01..0.00 ns   5.58..5.59 ns   -0.02  45.940 ms     55.380 ms   102 (trim 20%, outliers 5.9%)
safari   Tiny      clean                 <= 0.90 ns       <= 21.35%   0.82..0.90 ns  -0.00..0.00 ns   0.82..0.90 ns   -0.14  29.000 ms     35.000 ms   102 (trim 20%, outliers 2.9%)

Raw samples are in the live-debugger-runtime-bench-results artifact.

@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch from c499254 to 06018e9 Compare October 23, 2026 07:29
@tyffical tyffical changed the title [APPS-2945] Update: switch the apps plugin's static checks to the Node backend runtime (v2) [APPS-2945] Update: switch the apps plugin's static checks to the Node backend runtime Oct 23, 2026
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from cf49bfa to 314d9ca Compare October 23, 2026 13:09
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch from 06018e9 to 7509268 Compare October 23, 2026 13:16
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 314d9ca to 86c8430 Compare October 23, 2026 13:41
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch 2 times, most recently from eb7a6fb to 92afaf2 Compare October 23, 2026 14:30
@tyffical
tyffical requested a review from ksun154 October 23, 2026 14:56
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 86c8430 to b50260f Compare October 23, 2026 16:18
…d runtime

Backend functions run on the Node backend runtime, but the plugin's
static checks still enforced the Deno runtime's rules, so they rejected
code the Node backend runtime runs.

The checks now apply the Node backend runtime's rules: Node built-in
imports and Node's network globals, such as fetch and WebSocket, are
allowed, and XMLHttpRequest, EventSource and Deno, which Node lacks, are
rejected with a hint. They fail only where the missing global would
throw: a bare use other than typeof, or using it as an object, function
or class, including through a binding it was declared or destructured
into. Feature checks, polyfill installs, delete and destructuring
defaults pass. The Deno-only checks are removed: the Node built-in
import check, the network-global check and the crypto/Intl warning.

Local execution runs on the developer's own Node, so the dev server now
warns once at start when its major version isn't the Node backend
runtime's (24), and when it's before 22, which lacks the global
WebSocket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2945-follow-terrapin-flag branch from 92afaf2 to b71e121 Compare October 23, 2026 16:19
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.

3 participants