Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
04ee196
Bootstrap
yoannmoinet Dec 8, 2025
c8ca3c4
Add jslib dependency
yoannmoinet Dec 9, 2025
caab070
Factorise helpers for API requests
yoannmoinet Dec 9, 2025
54ad1eb
Implement assets upload
yoannmoinet Dec 9, 2025
7225934
Update documentation
yoannmoinet Dec 9, 2025
dcd4eab
Update integrity
yoannmoinet Dec 12, 2025
128b7a2
Resolve identifier
yoannmoinet Dec 12, 2025
2238a94
Update docs
yoannmoinet Dec 12, 2025
3417abb
Close git timer
yoannmoinet Dec 12, 2025
7f4cf24
Move form helper into request
yoannmoinet Dec 12, 2025
e1889d5
Extract prettyObject helper
yoannmoinet Dec 12, 2025
e1e537a
Add tests
yoannmoinet Dec 15, 2025
a6fe1ed
Add alpha note
yoannmoinet Dec 15, 2025
e0d22a1
Hide some docs from root readme
yoannmoinet Dec 15, 2025
02ef348
Mock the entire package
yoannmoinet Dec 15, 2025
19c5afe
Use more robust url for nock
yoannmoinet Dec 15, 2025
b9ae58c
Fix `DD_SITE` leak from `dd-trace` in CI
yoannmoinet Dec 16, 2025
4884300
Rename claude settings
yoannmoinet Jan 20, 2026
c8d4f25
Require both name and repository
yoannmoinet Jan 20, 2026
60b5575
Update glob dependency
yoannmoinet Jan 23, 2026
da2e18b
Hash identifier
yoannmoinet Jan 23, 2026
b416138
Update upload API usage
yoannmoinet Jan 23, 2026
3d53ff7
Update apps plugin upload to use name and bundle fields
yoannmoinet Jan 23, 2026
ffed543
Return both identifier and name from resolveIdentifier
yoannmoinet Jan 23, 2026
109333e
Update upload tests for name and bundle fields
yoannmoinet Jan 23, 2026
5de7aae
Update identifier tests for object return type
yoannmoinet Jan 23, 2026
aeca99e
Add comprehensive test coverage for name configuration option
yoannmoinet Jan 23, 2026
bfccddc
Document new name configuration option in README
yoannmoinet Jan 23, 2026
dc5952d
Change resolution of identifier and name
yoannmoinet Jan 26, 2026
63228a9
Add missing appKey
yoannmoinet Jan 26, 2026
bbdc012
Update tests following identifier resolution change
yoannmoinet Jan 26, 2026
1c8699e
Handle sourcemaps even without git data
yoannmoinet Jan 27, 2026
219fe2e
Update the request helper
yoannmoinet Jan 27, 2026
b1aea40
Update the test following `createRequestData` update
yoannmoinet Jan 27, 2026
bd55945
Strip common prefix dirs
yoannmoinet Jan 27, 2026
10e206a
Add missing appKey
yoannmoinet Jan 27, 2026
d06ca58
Log upload API response
yoannmoinet Jan 27, 2026
9f22f70
Fix Vite's HTML rendering with injection
yoannmoinet Jan 29, 2026
7251313
Forward auth.site to Browser SDK setup
yoannmoinet Jan 29, 2026
f9531ac
Rename tags in OOTB dashboard
yoannmoinet Jan 29, 2026
cf1d523
Change some logLevel in order not to spam "info"
yoannmoinet Jan 29, 2026
2ae0df3
Change apps output
yoannmoinet Jan 29, 2026
ff3d93d
Better output
yoannmoinet Feb 2, 2026
c097035
Merge remote-tracking branch 'origin/master' into yoann/apps-plugin
yoannmoinet Feb 2, 2026
34c0586
Fix lint
yoannmoinet Feb 2, 2026
7e6f6b1
Update tests and mocks after changes of APIs
yoannmoinet Feb 2, 2026
d66a445
Remove hard link
yoannmoinet Feb 2, 2026
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
1 change: 1 addition & 0 deletions .claude/settings.local.json → .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"Bash(yarn test:e2e:*)",
"Bash(yarn test:unit:*)",
"Bash(yarn typecheck:all)",
"Bash(yarn typecheck:*)",
"Bash(yarn)"
],
"deny": []
Expand Down
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ packages/plugins/async-queue @yoannmoin

# Output
packages/plugins/output @yoannmoinet

# Apps
packages/plugins/apps @yoannmoinet
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ yarn-error.log
!.yarn/plugins
!.vscode

.claude/*
!.claude/commands/*
!.claude/settings.json

node_modules/
dist/
dist-basic/
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
5 changes: 5 additions & 0 deletions LICENSES-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ http-signature,npm,MIT,Joyent Inc (https://github.com/joyent/node-http-signatur
human-signals,npm,Apache-2.0,ehmicky (https://git.io/JeluP)
husky,npm,MIT,Typicode (https://github.com/typicode/husky#readme)
ignore,npm,MIT,kael (https://www.npmjs.com/package/ignore)
immediate,npm,MIT,(https://www.npmjs.com/package/immediate)
import-fresh,npm,MIT,Sindre Sorhus (https://sindresorhus.com)
import-local,npm,MIT,Sindre Sorhus (https://sindresorhus.com)
imurmurhash,npm,MIT,Jens Taylor (https://github.com/jensyt/imurmurhash-js)
Expand Down Expand Up @@ -565,9 +566,11 @@ json-stream-stringify,npm,MIT,Faleij (https://github.com/faleij)
json-stringify-safe,npm,ISC,Isaac Z. Schlueter (https://github.com/isaacs/json-stringify-safe)
json5,npm,MIT,Aseem Kishore (http://json5.org/)
jsprim,npm,MIT,(https://www.npmjs.com/package/jsprim)
jszip,npm,(MIT OR GPL-3.0-or-later),Stuart Knightley (https://www.npmjs.com/package/jszip)
keyv,npm,MIT,Jared Wray (https://github.com/jaredwray/keyv)
leven,npm,MIT,Sindre Sorhus (sindresorhus.com)
levn,npm,MIT,George Zahariev (https://github.com/gkz/levn)
lie,npm,MIT,(https://www.npmjs.com/package/lie)
lines-and-columns,npm,MIT,Brian Donovan (https://github.com/eventualbuddha/lines-and-columns#readme)
lint-staged,npm,MIT,Andrey Okonetchnikov (https://www.npmjs.com/package/lint-staged)
listr2,npm,MIT,Cenk Kilic (https://srcs.kilic.dev)
Expand Down Expand Up @@ -638,6 +641,7 @@ p-timeout,npm,MIT,Sindre Sorhus (sindresorhus.com)
p-try,npm,MIT,Sindre Sorhus (sindresorhus.com)
package-json-from-dist,npm,BlueOak-1.0.0,Isaac Z. Schlueter (https://izs.me)
pad,npm,BSD-3-Clause,David Worms (https://github.com/adaltas/node-pad)
pako,npm,(MIT AND Zlib),(https://github.com/nodeca/pako)
parent-module,npm,MIT,Sindre Sorhus (sindresorhus.com)
parse-json,npm,MIT,Sindre Sorhus (https://sindresorhus.com)
path-exists,npm,MIT,Sindre Sorhus (sindresorhus.com)
Expand Down Expand Up @@ -707,6 +711,7 @@ serialize-javascript,npm,BSD-3-Clause,Eric Ferraiuolo (https://github.com/yahoo/
set-blocking,npm,ISC,Ben Coe (https://github.com/yargs/set-blocking#readme)
set-function-length,npm,MIT,Jordan Harband (https://github.com/ljharb/set-function-length#readme)
set-function-name,npm,MIT,Jordan Harband (https://github.com/ljharb/set-function-name#readme)
setimmediate,npm,MIT,YuzuJS (https://www.npmjs.com/package/setimmediate)
shebang-command,npm,MIT,Kevin Mårtensson (github.com/kevva)
shebang-regex,npm,MIT,Sindre Sorhus (sindresorhus.com)
side-channel,npm,MIT,Jordan Harband (https://github.com/ljharb/side-channel#readme)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Follow the specific documentation for each bundler:
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'none',
metadata?: {
name?: string;
};;
};
errorTracking?: {
enable?: boolean;
sourcemaps?: {
Expand Down
60 changes: 30 additions & 30 deletions packages/assets/src/dashboard.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion packages/core/src/helpers/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ const yellow = chalk.bold.yellow;
// - DATADOG_APP_KEY
// - DD_SOURCEMAP_INTAKE_URL
// - DATADOG_SOURCEMAP_INTAKE_URL
// - DD_APPS_INTAKE_URL
// - DATADOG_APPS_INTAKE_URL
// - DD_SITE
// - DATADOG_SITE
const OVERRIDE_VARIABLES = ['API_KEY', 'APP_KEY', 'SOURCEMAP_INTAKE_URL', 'SITE'] as const;
const OVERRIDE_VARIABLES = [
'API_KEY',
'APP_KEY',
'SOURCEMAP_INTAKE_URL',
'APPS_INTAKE_URL',
'SITE',
] as const;
type ENV_KEY = (typeof OVERRIDE_VARIABLES)[number];

// Return the environment variable that would be prefixed with either DATADOG_ or DD_.
Expand Down
40 changes: 40 additions & 0 deletions packages/core/src/helpers/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,50 @@
// Copyright 2019-Present Datadog, Inc.

import retry from 'async-retry';
import { Readable } from 'stream';
import type { RequestInit } from 'undici-types';
import type { Gzip } from 'zlib';
import { createGzip } from 'zlib';

import type { RequestOpts } from '../types';

export const getOriginHeaders = (opts: { bundler: string; plugin: string; version: string }) => {
return {
'DD-EVP-ORIGIN': `${opts.bundler}-build-plugin_${opts.plugin}`,
'DD-EVP-ORIGIN-VERSION': opts.version,
};
};

export type RequestData = {
data: Gzip | Readable;
headers: Record<string, string>;
};

export type FormBuilder = () => Promise<FormData> | FormData;

export const createRequestData = async (options: {
getForm: FormBuilder;
defaultHeaders: Record<string, string>;
zip?: boolean;
}): Promise<RequestData> => {
const { getForm, defaultHeaders = {}, zip = true } = options;
const form = await getForm();

// Serialize FormData through Request to get a streaming body
// and auto-generated headers (boundary) that we can forward while piping through gzip.
const req = new Request('fake://url', { method: 'POST', body: form });
const formStream = Readable.fromWeb(req.body!);
const data = zip ? formStream.pipe(createGzip()) : formStream;

const headers = {
'Content-Encoding': zip ? 'gzip' : 'multipart/form-data',
...defaultHeaders,
...Object.fromEntries(req.headers.entries()),
};

return { data, headers };
};

export const ERROR_CODES_NO_RETRY = [400, 403, 413];
export const NB_RETRIES = 5;
// Do a retriable fetch.
Expand Down
32 changes: 32 additions & 0 deletions packages/core/src/helpers/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import chalk from 'chalk';

// Format a duration 0h 0m 0s 0ms
export const formatDuration = (duration: number) => {
const days = Math.floor(duration / 1000 / 60 / 60 / 24);
Expand Down Expand Up @@ -60,5 +62,35 @@ export const filterSensitiveInfoFromRepositoryUrl = (repositoryUrl: string = '')
}
};

const formatValue = (value: unknown) => {
if (value === undefined) {
return 'undefined';
}

if (value === null) {
return 'null';
}

if (Array.isArray(value)) {
return value.join(', ');
}

if (typeof value === 'object') {
try {
return JSON.stringify(value, null, 2);
} catch {
return String(value);
}
}

return value?.toString() ?? '';
};

export const prettyObject = (obj: any) => {
return Object.entries(obj)
.map(([key, value]) => ` - ${key}: ${chalk.bold.green(formatValue(value))}`)
.join('\n');
};

let index = 0;
export const getUniqueId = () => `${Date.now()}.${performance.now()}.${++index}`;
3 changes: 3 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import type { TrackedFilesMatcher } from '@dd/internal-git-plugin/trackedFilesMatcher';
/* eslint-disable arca/import-ordering */
// #imports-injection-marker
import type { AppsOptions } from '@dd/apps-plugin/types';
import type * as apps from '@dd/apps-plugin';
import type { ErrorTrackingOptions } from '@dd/error-tracking-plugin/types';
import type * as errorTracking from '@dd/error-tracking-plugin';
import type { MetricsOptions } from '@dd/metrics-plugin/types';
Expand Down Expand Up @@ -257,6 +259,7 @@ export interface BaseOptions {
export interface Options extends BaseOptions {
// Each product should have a unique entry.
// #types-injection-marker
[apps.CONFIG_KEY]?: AppsOptions;
[errorTracking.CONFIG_KEY]?: ErrorTrackingOptions;
[metrics.CONFIG_KEY]?: MetricsOptions;
[output.CONFIG_KEY]?: OutputOptions;
Expand Down
1 change: 1 addition & 0 deletions packages/factory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@dd/apps-plugin": "workspace:*",
"@dd/core": "workspace:*",
"@dd/error-tracking-plugin": "workspace:*",
"@dd/internal-analytics-plugin": "workspace:*",
Expand Down
7 changes: 0 additions & 7 deletions packages/factory/src/helpers/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import type { Options, GlobalContext } from '@dd/core/types';
import { BUNDLER_VERSIONS } from '@dd/tests/_jest/helpers/constants';
import { cleanEnv } from '@dd/tests/_jest/helpers/env';
import { defaultPluginOptions } from '@dd/tests/_jest/helpers/mocks';
import { BUNDLERS, runBundlers } from '@dd/tests/_jest/helpers/runBundlers';

Expand All @@ -13,7 +12,6 @@ describe('Factory Helpers', () => {
const initialContexts: Record<string, GlobalContext> = {};
const buildRoots: Record<string, string> = {};
let workingDir: string;
let restoreEnv: () => void;

beforeAll(async () => {
const pluginConfig: Options = {
Expand Down Expand Up @@ -41,15 +39,10 @@ describe('Factory Helpers', () => {
},
};

restoreEnv = cleanEnv();
const result = await runBundlers(pluginConfig);
workingDir = result.workingDir;
});

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

describe('getContext', () => {
describe.each(BUNDLERS)('[$name|$version]', ({ name, version }) => {
test('Should have the right initial context.', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/factory/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { wrapGetPlugins } from './helpers/wrapPlugins';
import { ALL_ENVS, HOST_NAME } from '@dd/core/constants';
import { notifyOnEnvOverrides } from '@dd/core/helpers/env';
// #imports-injection-marker
import * as apps from '@dd/apps-plugin';
import * as errorTracking from '@dd/error-tracking-plugin';
import * as metrics from '@dd/metrics-plugin';
import * as output from '@dd/output-plugin';
Expand All @@ -49,6 +50,7 @@ import { getInjectionPlugins } from '@dd/internal-injection-plugin';
import { getTrueEndPlugins } from '@dd/internal-true-end-plugin';
// #imports-injection-marker
// #types-export-injection-marker
export type { types as AppsTypes } from '@dd/apps-plugin';
export type { types as ErrorTrackingTypes } from '@dd/error-tracking-plugin';
export type { types as MetricsTypes } from '@dd/metrics-plugin';
export type { types as OutputTypes } from '@dd/output-plugin';
Expand Down Expand Up @@ -159,6 +161,7 @@ export const buildPluginFactory = ({
// Add the customer facing plugins.
pluginsToAdd.push(
// #configs-injection-marker
['apps', apps.getPlugins],
['error-tracking', errorTracking.getPlugins],
['metrics', metrics.getPlugins],
['output', output.getPlugins],
Expand Down
77 changes: 77 additions & 0 deletions packages/plugins/apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Apps Plugin <!-- #omit in toc -->

A plugin to upload assets to Datadog's storage

> [!WARNING]
> The Apps plugin is in **alpha** and is likely to break in most setups.
> Use it only for experimentation; behavior and APIs may change without notice.

<!-- The title and the following line will both be added to the root README.md -->

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

<!-- This is auto generated with yarn cli integrity -->

<!-- #toc -->
- [Configuration](#configuration)
- [Assets Upload](#assets-upload)
- [apps.dryRun](#appsdryrun)
- [apps.enable](#appsenable)
- [apps.include](#appsinclude)
- [apps.identifier](#appsidentifier)
- [apps.name](#appsname)
<!-- #toc -->

## Configuration

```ts
apps?: {
dryRun?: boolean;
enable?: boolean;
include?: string[];
identifier?: string;
name?: string;
}
```

## Assets Upload

Upload built assets to Datadog storage as a compressed archive.

> [!NOTE]
> You can override the domain used in the request with the `DATADOG_SITE` environment variable or the `auth.site` options (eg. `datadoghq.eu`).
> You can override the full intake URL by setting the `DATADOG_APPS_INTAKE_URL` environment variable (eg. `https://apps-intake.datadoghq.com/api/v1/apps`).

### apps.dryRun

> default: `false`

Prepare the archive and log the upload summary without sending anything to Datadog.

### apps.enable

> default: `true` when an `apps` config block is present

Enable or disable the plugin without removing its configuration.

### apps.include

> default: `[]`

Additional glob patterns (relative to the project root) to include in the uploaded archive. The bundler output directory is always included.

### apps.identifier

> default: an internal computation between the `name` and `repository` fields in `package.json` or from the `git` plugin.

Override the app's identifier used to identify the current app against the assets upload API.

Can be useful to enforce a static identifier instead of relying on possibly changing information like app's name and repository's url.

### apps.name

> default: extracted from the `name` field in `package.json`.

Override the app's name used in the assets upload API request.

Can be useful to enforce a static name instead of relying on the package.json name field.
34 changes: 34 additions & 0 deletions packages/plugins/apps/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@dd/apps-plugin",
"packageManager": "yarn@4.0.2",
"license": "MIT",
"private": true,
"author": "Datadog",
"description": "A plugin to upload assets to Datadog's storage",
"homepage": "https://github.com/DataDog/build-plugins/tree/main/packages/plugins/apps#readme",
"repository": {
"type": "git",
"url": "https://github.com/DataDog/build-plugins",
"directory": "packages/plugins/apps"
},
"buildPlugin": {
"hideFromRootReadme": true
},
"exports": {
".": "./src/index.ts",
"./*": "./src/*.ts"
},
"scripts": {
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@dd/core": "workspace:*",
"chalk": "2.3.1",
"glob": "11.1.0",
"jszip": "3.10.1",
"pretty-bytes": "5.6.0"
},
"devDependencies": {
"typescript": "5.4.3"
}
}
Loading