Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Everything you need to know about breaking changes and major version bumps.

<!-- #toc -->
- [v3 to v4](#v3-to-v4)
- [`npm run dev` now executes backend functions in-process instead of via a cloud round trip](#npm-run-dev-now-executes-backend-functions-in-process-instead-of-via-a-cloud-round-trip)
- [Run `npm run dev:verify` to check cloud parity before publishing](#run-npm-run-devverify-to-check-cloud-parity-before-publishing)
- [`process.env` is now allowlisted during local execution](#processenv-is-now-allowlisted-during-local-execution)
- [Custom Credentials resolve locally via `datadog-app.local.json`](#custom-credentials-resolve-locally-via-datadog-applocaljson)
- [`getInitiatingUser()` and `getExecutionUser()` continue to return your real identity locally](#getinitiatinguser-and-getexecutionuser-continue-to-return-your-real-identity-locally)
- [v2 to v3](#v2-to-v3)
- [Renamed `disabled` to `enable`](#renamed-disabled-to-enable)
- [Removed `options.errorTracking.sourcemaps.disableGit`](#removed-optionserrortrackingsourcemapsdisablegit)
Expand All @@ -17,6 +23,80 @@ Everything you need to know about breaking changes and major version bumps.
- [Log Level](#log-level)
<!-- #toc -->

## v3 to v4
Comment thread
tyffical marked this conversation as resolved.
Comment thread
tyffical marked this conversation as resolved.

This release changes how `npm run dev` runs an app's backend functions (`*.backend.ts`). Apps that don't define any backend functions are unaffected.

### `npm run dev` now executes backend functions in-process instead of via a cloud round trip

Previously, `npm run dev` bundled a backend function's file and sent it to Datadog's API on every call, executing it in the cloud and returning the result over the network.

`npm run dev` now loads the function's file directly into the local Vite dev server and executes it there, instead of bundling it and sending it to the cloud on every call. `$.Actions` calls still reach Datadog's API exactly as they do in production, and connection scoping and input/output validation behave the same as before — a function that only reads its arguments and calls `$.Actions` needs no changes.

Static imports of Node built-ins (`fs`, `child_process`, `net`, etc.), dynamic imports of one using a literal string specifier (e.g. `import('fs')`), and raw network globals (`fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`) in a backend file are rejected at build time — this check covers every app-local module resolved into the backend bundle, not just the entry file itself, so an app-local helper module is checked too. A dynamic import using a runtime-computed specifier (e.g. `import(moduleName)`) isn't caught by this check. Backend functions have never had access to these in production, and `npm run dev` ran a function through that same restricted production environment before this release too (see above) — so code relying on one of these already failed under `npm run dev`, just later than it does now, and with a runtime error instead of this build-time one.

A separate runtime guard also blocks network and subprocess access (`net`, `dns`, `child_process`, `worker_threads`, etc.) while a function body executes. It exists for what the build-time check above can't see: a `node_modules` dependency that reaches the network or spawns a process directly — bypassing `$.Actions` — fails at call time under `npm run dev` instead of at build time.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Qualify the runtime-guard claim for import-time dependency I/O

When a node_modules dependency performs network or subprocess I/O during its top-level initialization, it does not fail as claimed here: package modules are excluded from the static checks, and loadCustomerModuleEntry() evaluates them outside runBlocked (packages/plugins/apps/src/vite/local-execution.ts:227-253 explicitly documents this residual gap). Because Vite then caches the evaluated module, the operation can succeed locally even though production rejects it. Scope this statement to I/O initiated from the function body's execution context and warn that import-time dependency side effects still require dev:verify.

Useful? React with 👍 / 👎.


### Run `npm run dev:verify` to check cloud parity before publishing

Because a function's own code now runs locally instead of in Datadog's cloud, local execution can no longer catch every difference between local and production behavior on its own (see the `process.env` allowlist below, for example).

`npm run dev:verify` still executes backend functions through the full cloud round trip, the same way `npm run dev` used to. Run it before publishing an app to confirm a function's real dependencies (environment variables, connections) behave the same way in the cloud as they did locally.

```bash
npm run dev:verify
Comment thread
tyffical marked this conversation as resolved.
```
Comment thread
tyffical marked this conversation as resolved.

If your `package.json` doesn't have a `dev:verify` script yet, `dev:verify` is just your existing dev command with Vite's `--mode dev-verify` flag appended (e.g. `vite --mode dev-verify`) — add the script, or run the equivalent command directly.

### `process.env` is now allowlisted during local execution

A backend function running under `npm run dev` no longer has unscoped access to `process.env`. It's scoped to a small, fixed set of safe variables during local execution: `PATH`, `HOME`, `NODE_ENV`, and `TMPDIR`.

Reading any other variable — including one a secret-backed connection would resolve to in production — returns `undefined` locally, even though the equivalent read against the deployed function succeeds in production. The one exception is a Custom Credentials-backed variable declared in `datadog-app.local.json` (see below).

```diff
export function myBackendFunction() {
- const region = process.env.AWS_REGION; // resolved in production
+ const region = process.env.AWS_REGION; // undefined under `npm run dev` — not in the local allowlist
}
```

If a backend function depends on a variable like this, verify it with `npm run dev:verify` (see above) before publishing, since that path still runs against the real cloud environment.

### Custom Credentials resolve locally via `datadog-app.local.json`

Previously, a backend function's Custom Credentials-backed connection resolved to its real value under `npm run dev`, because the function ran through the cloud round trip described above. Now that the function runs locally, that same variable would otherwise fall under the allowlist above and read as `undefined`.

`npm run dev` closes that gap by resolving Custom Credentials from a `datadog-app.local.json` file in your project root, if you create one. Add it to `.gitignore` and map each credential's env var name to its real value:
Comment thread
tyffical marked this conversation as resolved.
Comment thread
tyffical marked this conversation as resolved.
Comment thread
tyffical marked this conversation as resolved.

```json
{
"STRIPE_API_KEY": "sk_test_..."
}
```

A missing file resolves to no extra variables — most projects won't have one, and the variable then reads as `undefined` locally until you add it. A present-but-malformed file (invalid JSON, or a value that isn't a string) throws instead of silently resolving to `undefined`, so a typo doesn't look identical to an undeclared secret.

A `datadog-app.local.json` entry is only resolved for code that runs inside the function body — code at the module's top level (e.g. a client constructed at import time) still sees the variable as `undefined`, since credentials aren't resolved until the function is actually invoked. Move the read, and anything constructed from it, inside the function body:

```diff
-const client = new StripeClient(process.env.STRIPE_API_KEY); // undefined — evaluated at module load, before credentials resolve
-
export function myBackendFunction() {
- // ...
+ const client = new StripeClient(process.env.STRIPE_API_KEY); // real value, once declared in datadog-app.local.json
}
```

### `getInitiatingUser()` and `getExecutionUser()` continue to return your real identity locally

Previously, `getInitiatingUser()` and `getExecutionUser()` (from `@datadog/apps-backend/user`) returned your real identity under `npm run dev`, because the function ran through the cloud round trip described above and `$.Source` came from that same authenticated session.

Now that the function runs locally, `npm run dev` fetches your real, authenticated identity from a preview call before running any backend code, so `getInitiatingUser()` and `getExecutionUser()` keep returning that real identity (`id`, `orgId`, and optionally `email` and `name`) instead of falling back to a placeholder.
Comment thread
tyffical marked this conversation as resolved.

Both calls return the identity of that preview invocation, i.e. your own account — not necessarily the identity a real deployed trigger would pass to `getExecutionUser()` (a scheduled run or a different end user's action, for example). `npm run dev:verify` doesn't help here either — it's also a manual preview under your own credentials, with no way to simulate another trigger's identity. Confirm behavior against an execution's real identity by publishing the app and running it through an actual deployed trigger instead.

## v2 to v3

To sum up, here's the complete migration (to adapt for other bundlers) :
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ To interact with Datadog directly from your builds.
> **Migrations**:
> - [v1 to v2](/MIGRATIONS.md#v1-to-v2).
> - [v2 to v3](/MIGRATIONS.md#v2-to-v3).
> - [v3 to v4](/MIGRATIONS.md#v3-to-v4).

## Table of content <!-- #omit in toc -->

Expand Down