Skip to content
Merged
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
14 changes: 14 additions & 0 deletions packages/plugins/apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ It needs Vite 6.4 or later: on an older Vite, it stops every Vite command with a
- [Development server authentication](#development-server-authentication)
- [Development server requests](#development-server-requests)
- [Cloud execution](#cloud-execution)
- [Local execution](#local-execution)
- [Package output](#package-output)
- [apps.enable](#appsenable)
- [apps.include](#appsinclude)
Expand Down Expand Up @@ -94,6 +95,19 @@ Cloud execution (`dev:verify`, `/__dd/executeActionViaCloud`) bundles a backend

When Datadog rejects a bundle as longer than the `jsFunctionWithActions` `script` input accepts, the dev server answers with a 413 naming the function, its size and Datadog's answer, and warns once per function until the dev server restarts.

## Local execution

`/__dd/executeAction` runs each backend function call the way production does: in a fresh Node process of its own. Calls run one at a time.

- **Fresh state**: module-level variables and globals start over on every call.
- **Timeout**: a call that runs past its time budget is killed, even inside a synchronous loop.
- **Guards**: guards in the call's process refuse writes outside the call's own temp dir (`os.tmpdir()`, also its working directory) and `/tmp`, subprocesses and worker threads, from the module's top-level code on. The heap is capped at 256 MB.
- **Environment**: a call sees no variables from the dev server's environment, keys included, except Node's TLS and time zone settings (`NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`, `SSL_CERT_DIR`, `TZ`), plus `SystemRoot` on Windows, and no Node options from `NODE_OPTIONS`. Vite still inlines `import.meta.env` and `define` values into the code it serves. `$.Actions` calls go back to the dev server, which holds the credentials and checks each call's connection against the function's allowed connections.
- **Logs**: a call's `stdout` and `stderr` reach the dev server's terminal line by line, as each line ends.
- **Debugging**: when the dev server runs under a debugger (`--inspect`), each call's process listens on a random local port and prints its `Debugger listening on ws://…` URL. Attach to that URL to debug the call; calls don't time out while the dev server is under a debugger.

Local execution doesn't support Yarn Plug'n'Play, whose packages load through hooks a call's process doesn't get: each call fails with a hint to set `nodeLinker: node-modules` or use `dev:verify`.

## Package output

A production `vite build` writes `datadog-app-assets.zip` beside the Vite output. The ZIP contains `frontend/`, `backend/`, and `manifest.json`. The app's identity is resolved by `@datadog/apps-cli` at deploy time.
Expand Down
6 changes: 6 additions & 0 deletions packages/plugins/apps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
"format": [
"esm"
]
},
"local-execution-child": {
"entry": "./src/vite/local-execution-child.ts",
"format": [
"esm"
]
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/plugins/apps/src/backend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export interface BackendFunction {
allowedConnectionIds: string[];
}

/** Shape of a backend function's result, shared by the remote (dev-server.ts) and in-process (local-execution.ts) paths — mirrors the app-builder query response's `{ data: <value> }` wrapper. */
/** Shape of a backend function's result, shared by the remote (dev-server.ts) and local (local-execution-process.ts) paths — mirrors the app-builder query response's `{ data: <value> }` wrapper. */
export type BackendOutputs = { data: unknown };
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,30 @@ import { build, createServer, type ViteDevServer } from 'vite';
import { ARCHIVE_FILENAME } from '../constants';
import type { AppsManifest } from '../types';

import {
buildLocalExecutionChild,
removeLocalExecutionChild,
} from './local-execution-process.fixtures';

// Jest compiles loadViteParseAst's dynamic import into a `require`, which gets Vite's CJS
// entry and no `parseAst`; the published build keeps the real import.
jest.mock('@dd/apps-plugin/vite/vite-parse-ast', () => ({
loadViteParseAst: async () => parseAst,
}));

let mockChildEntryPath = '';
jest.mock('@dd/apps-plugin/vite/local-execution-child-path', () => ({
getLocalExecutionChildPath: () => mockChildEntryPath,
}));

beforeAll(async () => {
mockChildEntryPath = await buildLocalExecutionChild();
});

afterAll(() => {
removeLocalExecutionChild();
});

const FIXTURES_DIR = path.resolve(__dirname, '../../../../tests/src/_jest/fixtures');
const PROJECT_DIR = path.join(FIXTURES_DIR, 'apps_backend_library_project');
const ACTION_CATALOG_DIR = path.join(FIXTURES_DIR, 'action_catalog_project');
Expand Down Expand Up @@ -71,6 +89,8 @@ async function assembleApp(
await fsp.mkdir(path.dirname(linkPath), { recursive: true });
await fsp.symlink(checkout, linkPath, 'dir');
};
// A call's process loads modules through the project's own Vite module runner.
await link(path.dirname(require.resolve('vite/package.json')), 'vite');
const linkedPlainCheckout = path.join(tempDir, 'linked-plain-lib');
await copy(path.join(PROJECT_DIR, 'packages/linked-plain-lib'), linkedPlainCheckout);
await link(linkedPlainCheckout, 'linked-plain-backend-lib');
Expand Down
Loading
Loading