diff --git a/.yarn/cache/@rollup-plugin-terser-npm-0.4.4-c6896dd264-a5e066ddea.zip b/.yarn/cache/@rollup-plugin-terser-npm-0.4.4-c6896dd264-a5e066ddea.zip new file mode 100644 index 000000000..4330a00fd Binary files /dev/null and b/.yarn/cache/@rollup-plugin-terser-npm-0.4.4-c6896dd264-a5e066ddea.zip differ diff --git a/.yarn/cache/smob-npm-1.5.0-acdaaf382d-a1ea453bce.zip b/.yarn/cache/smob-npm-1.5.0-acdaaf382d-a1ea453bce.zip new file mode 100644 index 000000000..4a65fc40d Binary files /dev/null and b/.yarn/cache/smob-npm-1.5.0-acdaaf382d-a1ea453bce.zip differ diff --git a/.yarn/cache/terser-npm-5.37.0-7dbdc43c6e-3afacf7c38.zip b/.yarn/cache/terser-npm-5.37.0-7dbdc43c6e-3afacf7c38.zip new file mode 100644 index 000000000..9ec2ef107 Binary files /dev/null and b/.yarn/cache/terser-npm-5.37.0-7dbdc43c6e-3afacf7c38.zip differ diff --git a/LICENSES-3rdparty.csv b/LICENSES-3rdparty.csv index 8405fc40f..f8873962b 100644 --- a/LICENSES-3rdparty.csv +++ b/LICENSES-3rdparty.csv @@ -171,6 +171,7 @@ Component,Origin,Licence,Copyright @rollup/plugin-commonjs,virtual,MIT,Rich Harris (https://github.com/rollup/plugins/tree/master/packages/commonjs/#readme) @rollup/plugin-json,virtual,MIT,rollup (https://github.com/rollup/plugins/tree/master/packages/json#readme) @rollup/plugin-node-resolve,virtual,MIT,Rich Harris (https://github.com/rollup/plugins/tree/master/packages/node-resolve/#readme) +@rollup/plugin-terser,virtual,MIT,Peter Placzek (https://github.com/rollup/plugins/tree/master/packages/terser#readme) @rollup/pluginutils,virtual,MIT,Rich Harris (https://github.com/rollup/plugins/tree/master/packages/pluginutils#readme) @rollup/rollup-darwin-arm64,npm,MIT,Lukas Taegert-Atkinson (https://rollupjs.org/) @rspack/binding,npm,MIT,(https://rspack.dev) @@ -822,6 +823,7 @@ simple-git,npm,MIT,Steve King (https://www.npmjs.com/package/simple-git) sisteransi,npm,MIT,Terkel Gjervig (https://terkel.com) slash,npm,MIT,Sindre Sorhus (sindresorhus.com) slice-ansi,npm,MIT,(https://www.npmjs.com/package/slice-ansi) +smob,npm,MIT,Peter Placzek (https://github.com/Tada5hi/smob#readme) snapdragon,npm,MIT,Jon Schlinkert (https://github.com/jonschlinkert/snapdragon) snapdragon-node,npm,MIT,Jon Schlinkert (https://github.com/jonschlinkert/snapdragon-node) snapdragon-util,npm,MIT,Jon Schlinkert (https://github.com/jonschlinkert/snapdragon-util) diff --git a/README.md b/README.md index 77f81e6b9..37754e150 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ To interact with Datadog directly from your builds. - [Usage](#usage) - [Configuration](#configuration) - [`auth.apiKey`](#authapikey) + - [`auth.appKey`](#authappkey) - [`logLevel`](#loglevel) - [`customPlugins`](#customplugins) - [Features](#features) @@ -128,6 +129,12 @@ Follow the specific documentation for each bundler: In order to interact with Datadog, you have to use [your own API Key](https://app.datadoghq.com/organization-settings/api-keys). +### `auth.appKey` + +> default `null` + +In order to interact with Datadog, you have to use [your own Application Key](https://app.datadoghq.com/organization-settings/application-keys). + ### `logLevel` > default: `'warn'` diff --git a/packages/core/src/helpers.ts b/packages/core/src/helpers.ts index 3b0fcd1f2..582d3d4d7 100644 --- a/packages/core/src/helpers.ts +++ b/packages/core/src/helpers.ts @@ -99,7 +99,7 @@ export const ERROR_CODES_NO_RETRY = [400, 403, 413]; export const NB_RETRIES = 5; // Do a retriable fetch. export const doRequest = (opts: RequestOpts): Promise => { - const { url, method = 'GET', getData, onRetry, type = 'text' } = opts; + const { auth, url, method = 'GET', getData, onRetry, type = 'text' } = opts; return retry( async (bail: (e: Error) => void, attempt: number) => { let response: Response; @@ -110,14 +110,24 @@ export const doRequest = (opts: RequestOpts): Promise => { // https://github.com/nodejs/node/issues/46221 duplex: 'half', }; + let requestHeaders: RequestInit['headers'] = {}; + + // Do auth if present. + if (auth?.apiKey) { + requestHeaders['DD-API-KEY'] = auth.apiKey; + } + + if (auth?.appKey) { + requestHeaders['DD-APPLICATION-KEY'] = auth.appKey; + } if (typeof getData === 'function') { const { data, headers } = await getData(); requestInit.body = data; - requestInit.headers = headers; + requestHeaders = { ...requestHeaders, ...headers }; } - response = await fetch(url, requestInit); + response = await fetch(url, { ...requestInit, headers: requestHeaders }); } catch (error: any) { // We don't want to retry if there is a non-fetch related error. bail(error); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 55f553d4c..a9d148a25 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -41,9 +41,16 @@ export type SerializedInput = Assign; export type BuildReport = { + bundler: Omit; errors: string[]; warnings: string[]; - logs: { pluginName: string; type: LogLevel; message: string; time: number }[]; + logs: { + bundler: BundlerFullName; + pluginName: string; + type: LogLevel; + message: string; + time: number; + }[]; entries?: Entry[]; inputs?: Input[]; outputs?: Output[]; @@ -127,6 +134,7 @@ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none'; export type AuthOptions = { apiKey?: string; + appKey?: string; }; export interface BaseOptions { @@ -149,9 +157,10 @@ export type OptionsWithDefaults = Assign; export type PluginName = `datadog-${Lowercase}-plugin`; -type Data = { data: BodyInit; headers?: Record }; +type Data = { data?: BodyInit; headers?: Record }; export type RequestOpts = { url: string; + auth?: AuthOptions; method?: string; getData?: () => Promise | Data; type?: 'json' | 'text'; diff --git a/packages/factory/src/helpers.ts b/packages/factory/src/helpers.ts index 8d7bfdb38..d8d1d3478 100644 --- a/packages/factory/src/helpers.ts +++ b/packages/factory/src/helpers.ts @@ -29,6 +29,7 @@ const logPriority: Record = { export const getLoggerFactory = (build: BuildReport, logLevel: LogLevel = 'warn'): GetLogger => (name) => { + const cleanName = name.replace(/(^datadog-|-plugin$)/g, ''); const log = (text: any, type: LogLevel = 'debug') => { // By default (debug) we print dimmed. let color = c.dim; @@ -45,11 +46,17 @@ export const getLoggerFactory = logFn = console.log; } - const prefix = `[${type}|${name}]`; + const prefix = `[${type}|${build.bundler.fullName}|${cleanName}]`; // Keep a trace of the log in the build report. const content = typeof text === 'string' ? text : JSON.stringify(text, null, 2); - build.logs.push({ pluginName: name, type, message: content, time: Date.now() }); + build.logs.push({ + bundler: build.bundler.fullName, + pluginName: cleanName, + type, + message: content, + time: Date.now(), + }); if (type === 'error') { build.errors.push(content); } @@ -66,7 +73,7 @@ export const getLoggerFactory = return { getLogger: (subName: string) => { const logger = getLoggerFactory(build, logLevel); - return logger(`${name}:${subName}`); + return logger(`${cleanName}:${subName}`); }, error: (text: any) => log(text, 'error'), warn: (text: any) => log(text, 'warn'), @@ -94,17 +101,20 @@ export const getContext = ({ errors: [], warnings: [], logs: [], + bundler: { + name: bundlerName, + fullName: `${bundlerName}${variant}` as BundlerFullName, + variant, + version: bundlerVersion, + }, }; const context: GlobalContext = { auth: options.auth, pluginNames: [], bundler: { - name: bundlerName, - fullName: `${bundlerName}${variant}` as BundlerFullName, - variant, + ...build.bundler, // This will be updated in the bundler-report plugin once we have the configuration. outDir: cwd, - version: bundlerVersion, }, build, // This will be updated in the bundler-report plugin once we have the configuration. diff --git a/packages/plugins/build-report/src/helpers.ts b/packages/plugins/build-report/src/helpers.ts index 5c62745fb..449fce03c 100644 --- a/packages/plugins/build-report/src/helpers.ts +++ b/packages/plugins/build-report/src/helpers.ts @@ -50,6 +50,7 @@ export const serializeBuildReport = (report: BuildReport): SerializedBuildReport // To make it JSON serializable, we need to remove the self references // and replace them with strings, we'll use "filepath" to still have them uniquely identifiable. const jsonReport: SerializedBuildReport = { + bundler: report.bundler, errors: report.errors, warnings: report.warnings, logs: report.logs, @@ -103,6 +104,7 @@ export const serializeBuildReport = (report: BuildReport): SerializedBuildReport // Mostly useful for debugging and testing. export const unserializeBuildReport = (report: SerializedBuildReport): BuildReport => { const buildReport: BuildReport = { + bundler: report.bundler, errors: report.errors, warnings: report.warnings, logs: report.logs, diff --git a/packages/plugins/injection/src/helpers.ts b/packages/plugins/injection/src/helpers.ts index 991e3a330..ed5b61dbe 100644 --- a/packages/plugins/injection/src/helpers.ts +++ b/packages/plugins/injection/src/helpers.ts @@ -108,7 +108,10 @@ export const getContentToInject = (contentToInject: Map) => { return ''; } - const stringToInject = Array.from(contentToInject.values()).join('\n\n'); + const stringToInject = Array.from(contentToInject.values()) + // Wrapping it in order to avoid variable name collisions. + .map((content) => `(() => {${content}})();`) + .join('\n\n'); return `${BEFORE_INJECTION}\n${stringToInject}\n${AFTER_INJECTION}`; }; diff --git a/packages/published/esbuild-plugin/package.json b/packages/published/esbuild-plugin/package.json index 338b9fb02..4cc424b7b 100644 --- a/packages/published/esbuild-plugin/package.json +++ b/packages/published/esbuild-plugin/package.json @@ -65,6 +65,7 @@ "@rollup/plugin-commonjs": "28.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", "esbuild": "0.24.0", diff --git a/packages/published/esbuild-plugin/src/index.ts b/packages/published/esbuild-plugin/src/index.ts index 2d2be69ee..c0687c8b7 100644 --- a/packages/published/esbuild-plugin/src/index.ts +++ b/packages/published/esbuild-plugin/src/index.ts @@ -7,24 +7,29 @@ // will be updated using the 'yarn cli integrity' command. import type { Options } from '@dd/core/types'; +import type { + // #types-export-injection-marker + ErrorTrackingTypes, + TelemetryTypes, + // #types-export-injection-marker +} from '@dd/factory'; import * as factory from '@dd/factory'; import esbuild from 'esbuild'; import pkg from '../package.json'; -export const datadogEsbuildPlugin = factory.buildPluginFactory({ - bundler: esbuild, - version: pkg.version, -}).esbuild; - export type EsbuildPluginOptions = Options; - export type { // #types-export-injection-marker ErrorTrackingTypes, TelemetryTypes, // #types-export-injection-marker -} from '@dd/factory'; +}; + +export const datadogEsbuildPlugin = factory.buildPluginFactory({ + bundler: esbuild, + version: pkg.version, +}).esbuild; export const version = pkg.version; export const helpers = factory.helpers; diff --git a/packages/published/rollup-plugin/package.json b/packages/published/rollup-plugin/package.json index 26fc05906..a810d24ab 100644 --- a/packages/published/rollup-plugin/package.json +++ b/packages/published/rollup-plugin/package.json @@ -65,6 +65,7 @@ "@rollup/plugin-commonjs": "28.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", "esbuild": "0.24.0", diff --git a/packages/published/rollup-plugin/src/index.ts b/packages/published/rollup-plugin/src/index.ts index cf0023c89..a1d657111 100644 --- a/packages/published/rollup-plugin/src/index.ts +++ b/packages/published/rollup-plugin/src/index.ts @@ -7,24 +7,29 @@ // will be updated using the 'yarn cli integrity' command. import type { Options } from '@dd/core/types'; +import type { + // #types-export-injection-marker + ErrorTrackingTypes, + TelemetryTypes, + // #types-export-injection-marker +} from '@dd/factory'; import * as factory from '@dd/factory'; import rollup from 'rollup'; import pkg from '../package.json'; -export const datadogRollupPlugin = factory.buildPluginFactory({ - bundler: rollup, - version: pkg.version, -}).rollup; - export type RollupPluginOptions = Options; - export type { // #types-export-injection-marker ErrorTrackingTypes, TelemetryTypes, // #types-export-injection-marker -} from '@dd/factory'; +}; + +export const datadogRollupPlugin = factory.buildPluginFactory({ + bundler: rollup, + version: pkg.version, +}).rollup; export const version = pkg.version; export const helpers = factory.helpers; diff --git a/packages/published/rspack-plugin/package.json b/packages/published/rspack-plugin/package.json index 45cce1fe4..e2a9a7fbf 100644 --- a/packages/published/rspack-plugin/package.json +++ b/packages/published/rspack-plugin/package.json @@ -65,6 +65,7 @@ "@rollup/plugin-commonjs": "28.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", "esbuild": "0.24.0", diff --git a/packages/published/rspack-plugin/src/index.ts b/packages/published/rspack-plugin/src/index.ts index bc0d3a6d3..03ec3d2ac 100644 --- a/packages/published/rspack-plugin/src/index.ts +++ b/packages/published/rspack-plugin/src/index.ts @@ -7,24 +7,29 @@ // will be updated using the 'yarn cli integrity' command. import type { Options } from '@dd/core/types'; +import type { + // #types-export-injection-marker + ErrorTrackingTypes, + TelemetryTypes, + // #types-export-injection-marker +} from '@dd/factory'; import * as factory from '@dd/factory'; import rspack from '@rspack/core'; import pkg from '../package.json'; -export const datadogRspackPlugin = factory.buildPluginFactory({ - bundler: rspack, - version: pkg.version, -}).rspack; - export type RspackPluginOptions = Options; - export type { // #types-export-injection-marker ErrorTrackingTypes, TelemetryTypes, // #types-export-injection-marker -} from '@dd/factory'; +}; + +export const datadogRspackPlugin = factory.buildPluginFactory({ + bundler: rspack, + version: pkg.version, +}).rspack; export const version = pkg.version; export const helpers = factory.helpers; diff --git a/packages/published/vite-plugin/package.json b/packages/published/vite-plugin/package.json index db3f63a1e..6ab6230c5 100644 --- a/packages/published/vite-plugin/package.json +++ b/packages/published/vite-plugin/package.json @@ -65,6 +65,7 @@ "@rollup/plugin-commonjs": "28.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", "esbuild": "0.24.0", diff --git a/packages/published/vite-plugin/src/index.ts b/packages/published/vite-plugin/src/index.ts index 45f149a65..074d01ee2 100644 --- a/packages/published/vite-plugin/src/index.ts +++ b/packages/published/vite-plugin/src/index.ts @@ -7,24 +7,29 @@ // will be updated using the 'yarn cli integrity' command. import type { Options } from '@dd/core/types'; +import type { + // #types-export-injection-marker + ErrorTrackingTypes, + TelemetryTypes, + // #types-export-injection-marker +} from '@dd/factory'; import * as factory from '@dd/factory'; import vite from 'vite'; import pkg from '../package.json'; -export const datadogVitePlugin = factory.buildPluginFactory({ - bundler: vite, - version: pkg.version, -}).vite; - export type VitePluginOptions = Options; - export type { // #types-export-injection-marker ErrorTrackingTypes, TelemetryTypes, // #types-export-injection-marker -} from '@dd/factory'; +}; + +export const datadogVitePlugin = factory.buildPluginFactory({ + bundler: vite, + version: pkg.version, +}).vite; export const version = pkg.version; export const helpers = factory.helpers; diff --git a/packages/published/webpack-plugin/package.json b/packages/published/webpack-plugin/package.json index b6cfcd7b2..506760381 100644 --- a/packages/published/webpack-plugin/package.json +++ b/packages/published/webpack-plugin/package.json @@ -65,6 +65,7 @@ "@rollup/plugin-commonjs": "28.0.1", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.3.0", + "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", "esbuild": "0.24.0", diff --git a/packages/published/webpack-plugin/src/index.ts b/packages/published/webpack-plugin/src/index.ts index 0c490b337..2e338f4bf 100644 --- a/packages/published/webpack-plugin/src/index.ts +++ b/packages/published/webpack-plugin/src/index.ts @@ -7,24 +7,29 @@ // will be updated using the 'yarn cli integrity' command. import type { Options } from '@dd/core/types'; +import type { + // #types-export-injection-marker + ErrorTrackingTypes, + TelemetryTypes, + // #types-export-injection-marker +} from '@dd/factory'; import * as factory from '@dd/factory'; import webpack from 'webpack'; import pkg from '../package.json'; -export const datadogWebpackPlugin = factory.buildPluginFactory({ - bundler: webpack, - version: pkg.version, -}).webpack; - export type WebpackPluginOptions = Options; - export type { // #types-export-injection-marker ErrorTrackingTypes, TelemetryTypes, // #types-export-injection-marker -} from '@dd/factory'; +}; + +export const datadogWebpackPlugin = factory.buildPluginFactory({ + bundler: webpack, + version: pkg.version, +}).webpack; export const version = pkg.version; export const helpers = factory.helpers; diff --git a/packages/tests/jest.config.js b/packages/tests/jest.config.js index 2ad83add8..2b2598680 100644 --- a/packages/tests/jest.config.js +++ b/packages/tests/jest.config.js @@ -7,13 +7,10 @@ module.exports = { clearMocks: true, globalSetup: '/src/_jest/globalSetup.ts', preset: 'ts-jest/presets/js-with-ts', - reporters: [['default', { summaryThreshold: 2 }]], // Without it, vite import is silently crashing the process with code SIGHUP 129 resetModules: true, roots: ['./src/'], setupFilesAfterEnv: ['/src/_jest/setupAfterEnv.ts'], testEnvironment: 'node', testMatch: ['**/*.test.*'], - // We're building a lot of projects in parallel, so we need to increase the timeout. - testTimeout: 20000, }; diff --git a/packages/tests/src/_jest/helpers/mocks.ts b/packages/tests/src/_jest/helpers/mocks.ts index 0318f267e..0cd2d5320 100644 --- a/packages/tests/src/_jest/helpers/mocks.ts +++ b/packages/tests/src/_jest/helpers/mocks.ts @@ -4,6 +4,7 @@ import { outputJsonSync } from '@dd/core/helpers'; import type { + BuildReport, File, GetCustomPlugins, GetPluginsOptions, @@ -32,10 +33,9 @@ export const defaultEntries = { app2: './hard_project/main2.js', }; +export const defaultAuth = { apiKey: '123', appKey: '123' }; export const defaultPluginOptions: GetPluginsOptions = { - auth: { - apiKey: '123', - }, + auth: defaultAuth, disableGit: false, logLevel: 'debug', }; @@ -98,20 +98,27 @@ export const getEsbuildMock = (overrides: Partial = {}): PluginBuil }; }; +export const getMockBuild = (overrides: Partial = {}): BuildReport => ({ + errors: [], + warnings: [], + logs: [], + ...overrides, + bundler: { + name: 'esbuild', + fullName: 'esbuild', + version: 'FAKE_VERSION', + ...(overrides.bundler || {}), + }, +}); + export const getContextMock = (overrides: Partial = {}): GlobalContext => { return { - auth: { apiKey: 'FAKE_API_KEY' }, + auth: defaultAuth, bundler: { - name: 'esbuild', - fullName: 'esbuild', + ...getMockBuild().bundler, outDir: '/cwd/path', - version: 'FAKE_VERSION', - }, - build: { - warnings: [], - errors: [], - logs: [], }, + build: getMockBuild(), cwd: '/cwd/path', inject: jest.fn(), pluginNames: [], diff --git a/packages/tests/src/core/helpers.test.ts b/packages/tests/src/core/helpers.test.ts index aceac59eb..d76a1a197 100644 --- a/packages/tests/src/core/helpers.test.ts +++ b/packages/tests/src/core/helpers.test.ts @@ -295,6 +295,33 @@ describe('Core Helpers', () => { }).rejects.toThrow('Response body object should not be disturbed or locked'); expect(scope.isDone()).toBe(true); }); + + test('Should add authentication headers when needed.', async () => { + const fetchMock = jest + .spyOn(global, 'fetch') + .mockImplementation(() => Promise.resolve(new Response('{}'))); + const { doRequest } = await import('@dd/core/helpers'); + await doRequest({ + ...requestOpts, + auth: { + apiKey: 'api_key', + appKey: 'app_key', + }, + }); + + expect(fetchMock).toHaveBeenCalledWith( + INTAKE_URL, + expect.objectContaining({ + headers: { + // Coming from the getDataMock. + 'Content-Encoding': 'gzip', + // Coming from the requestOpts.auth. + 'DD-API-KEY': 'api_key', + 'DD-APPLICATION-KEY': 'app_key', + }, + }), + ); + }); }); describe('truncateString', () => { diff --git a/packages/tests/src/factory/helpers.test.ts b/packages/tests/src/factory/helpers.test.ts index 7cdd4e578..7b782a4e6 100644 --- a/packages/tests/src/factory/helpers.test.ts +++ b/packages/tests/src/factory/helpers.test.ts @@ -5,7 +5,7 @@ import type { BuildReport, GlobalContext, Logger, Options, ToInjectItem } from '@dd/core/types'; import { getContext, getLoggerFactory } from '@dd/factory/helpers'; import { BUNDLER_VERSIONS } from '@dd/tests/_jest/helpers/constants'; -import { defaultPluginOptions } from '@dd/tests/_jest/helpers/mocks'; +import { defaultPluginOptions, getMockBuild } from '@dd/tests/_jest/helpers/mocks'; import { BUNDLERS, runBundlers } from '@dd/tests/_jest/helpers/runBundlers'; import type { CleanupFn } from '@dd/tests/_jest/helpers/types'; import stripAnsi from 'strip-ansi'; @@ -87,7 +87,7 @@ describe('Factory Helpers', () => { describe('getLoggerFactory', () => { const setupLogger = (name: string): [Logger, BuildReport] => { - const mockBuild = { errors: [], warnings: [], logs: [] }; + const mockBuild = getMockBuild(); const loggerFactory = getLoggerFactory(mockBuild, 'debug'); const logger = loggerFactory(name); @@ -111,41 +111,42 @@ describe('Factory Helpers', () => { const assessLogs = (name: string) => { expect(logMock).toHaveBeenCalledTimes(2); - expect(getOutput(logMock, 0)).toBe(`[info|${name}] An info message.`); - expect(getOutput(logMock, 1)).toBe(`[debug|${name}] A debug message.`); + expect(getOutput(logMock, 0)).toBe(`[info|esbuild|${name}] An info message.`); + expect(getOutput(logMock, 1)).toBe(`[debug|esbuild|${name}] A debug message.`); expect(errorMock).toHaveBeenCalledTimes(1); - expect(getOutput(errorMock, 0)).toBe(`[error|${name}] An error occurred.`); + expect(getOutput(errorMock, 0)).toBe(`[error|esbuild|${name}] An error occurred.`); expect(warnMock).toHaveBeenCalledTimes(1); - expect(getOutput(warnMock, 0)).toBe(`[warn|${name}] A warning message.`); + expect(getOutput(warnMock, 0)).toBe(`[warn|esbuild|${name}] A warning message.`); }; const assessReport = (name: string, buildReport: BuildReport) => { expect(buildReport.logs).toHaveLength(4); - expect(buildReport.logs[0]).toEqual({ + const baseLog = { + bundler: 'esbuild', pluginName: name, + time: expect.any(Number), + }; + expect(buildReport.logs[0]).toEqual({ + ...baseLog, type: 'error', message: 'An error occurred.', - time: expect.any(Number), }); expect(buildReport.logs[1]).toEqual({ - pluginName: name, + ...baseLog, type: 'warn', message: 'A warning message.', - time: expect.any(Number), }); expect(buildReport.logs[2]).toEqual({ - pluginName: name, + ...baseLog, type: 'info', message: 'An info message.', - time: expect.any(Number), }); expect(buildReport.logs[3]).toEqual({ - pluginName: name, + ...baseLog, type: 'debug', message: 'A debug message.', - time: expect.any(Number), }); expect(buildReport.errors).toEqual(['An error occurred.']); diff --git a/packages/tests/src/plugins/error-tracking/sourcemaps/files.test.ts b/packages/tests/src/plugins/error-tracking/sourcemaps/files.test.ts index 80ee489a4..48d0f808f 100644 --- a/packages/tests/src/plugins/error-tracking/sourcemaps/files.test.ts +++ b/packages/tests/src/plugins/error-tracking/sourcemaps/files.test.ts @@ -3,7 +3,7 @@ // Copyright 2019-Present Datadog, Inc. import { getSourcemapsFiles } from '@dd/error-tracking-plugin/sourcemaps/files'; -import { getContextMock } from '@dd/tests/_jest/helpers/mocks'; +import { getContextMock, getMockBuild } from '@dd/tests/_jest/helpers/mocks'; import path from 'path'; import { getSourcemapsConfiguration } from '../testHelpers'; @@ -22,9 +22,7 @@ describe('Error Tracking Plugin Sourcemaps Files', () => { version: '1.0.0', }, build: { - warnings: [], - errors: [], - logs: [], + ...getMockBuild(), outputs: [ 'fixtures/common.js', 'fixtures/common.min.js.map', diff --git a/packages/tests/src/plugins/injection/index.test.ts b/packages/tests/src/plugins/injection/index.test.ts index c8d2724ba..598b737df 100644 --- a/packages/tests/src/plugins/injection/index.test.ts +++ b/packages/tests/src/plugins/injection/index.test.ts @@ -152,7 +152,7 @@ describe('Injection Plugin', () => { // Add a special case of import to confirm this is working as expected in the middle of the code. { type: 'code', - value: `import chalk from 'chalk';\nconsole.log(chalk.bold.red('${specialLog}'));\n`, + value: `const chalk = require('chalk');\nconsole.log(chalk.bold.red('${specialLog}'));\n`, position: InjectPosition.MIDDLE, }, ]; diff --git a/packages/tests/src/plugins/telemetry/index.test.ts b/packages/tests/src/plugins/telemetry/index.test.ts index 1051fb124..ee80defa6 100644 --- a/packages/tests/src/plugins/telemetry/index.test.ts +++ b/packages/tests/src/plugins/telemetry/index.test.ts @@ -114,41 +114,39 @@ describe('Telemetry Universal Plugin', () => { // We don't want to crash if there are no bundlers to test here. // Which can happen when using --bundlers. - if (expectations.length > 0) { - let cleanup: CleanupFn; - - beforeAll(async () => { - const pluginConfig: Options = { - telemetry: { - enableTracing: true, - endPoint: FAKE_URL, - filters: [], - }, - logLevel: 'warn', - customPlugins: (options: Options, context: GlobalContext) => - debugFilesPlugins(context), - }; - // This one is called at initialization, with the initial context. - addMetricsMocked.mockImplementation(getAddMetricsImplem(metrics)); - cleanup = await runBundlers( - pluginConfig, - getComplexBuildOverrides(), - activeBundlers, - ); - }); + if (!expectations.length) { + return; + } - afterAll(async () => { - await cleanup(); - }); + let cleanup: CleanupFn; - test.each(expectations)( - '$name - $version | Should get the related metrics', - ({ name, expectedMetrics }) => { - const metricNames = metrics[name].map((metric) => metric.metric).sort(); - expect(metricNames).toEqual(expect.arrayContaining(expectedMetrics)); + beforeAll(async () => { + const pluginConfig: Options = { + telemetry: { + enableTracing: true, + endPoint: FAKE_URL, + filters: [], }, - ); - } + logLevel: 'warn', + customPlugins: (options: Options, context: GlobalContext) => + debugFilesPlugins(context), + }; + // This one is called at initialization, with the initial context. + addMetricsMocked.mockImplementation(getAddMetricsImplem(metrics)); + cleanup = await runBundlers(pluginConfig, getComplexBuildOverrides(), activeBundlers); + }); + + afterAll(async () => { + await cleanup(); + }); + + test.each(expectations)( + '$name - $version | Should get the related metrics', + ({ name, expectedMetrics }) => { + const metricNames = metrics[name].map((metric) => metric.metric).sort(); + expect(metricNames).toEqual(expect.arrayContaining(expectedMetrics)); + }, + ); }); describe('Without enableTracing', () => { diff --git a/packages/tests/src/tools/src/rollupConfig.test.ts b/packages/tests/src/tools/src/rollupConfig.test.ts index cc83b9597..ea5f9774c 100644 --- a/packages/tests/src/tools/src/rollupConfig.test.ts +++ b/packages/tests/src/tools/src/rollupConfig.test.ts @@ -6,6 +6,7 @@ import { datadogEsbuildPlugin } from '@datadog/esbuild-plugin'; import { datadogRollupPlugin } from '@datadog/rollup-plugin'; import { datadogRspackPlugin } from '@datadog/rspack-plugin'; import { datadogVitePlugin } from '@datadog/vite-plugin'; +import { SUPPORTED_BUNDLERS } from '@dd/core/constants'; import { formatDuration, getUniqueId, rm } from '@dd/core/helpers'; import type { BundlerFullName, Options } from '@dd/core/types'; import { @@ -37,6 +38,7 @@ import { ROOT } from '@dd/tools/constants'; import { bgYellow, execute, green } from '@dd/tools/helpers'; import type { BuildOptions } from 'esbuild'; import fs from 'fs'; +import { glob } from 'glob'; import nock from 'nock'; import path from 'path'; @@ -72,12 +74,18 @@ const datadogRspackPluginMock = jest.mocked(datadogRspackPlugin); const datadogVitePluginMock = jest.mocked(datadogVitePlugin); const getWebpackPluginMock = jest.mocked(getWebpackPlugin); +const getPackagePath = (bundlerName: string) => { + // Cover for names that have a version in it, eg: webpack5, webpack4. + const cleanBundlerName = SUPPORTED_BUNDLERS.find((name) => bundlerName.startsWith(name)); + if (!cleanBundlerName) { + throw new Error(`Bundler not supported: "${bundlerName}"`); + } + return path.resolve(ROOT, `packages/published/${cleanBundlerName}-plugin/dist/src`); +}; + // Ensure our packages have been built not too long ago. const getPackageDestination = (bundlerName: string) => { - const packageDestination = path.resolve( - ROOT, - `packages/published/${bundlerName}-plugin/dist/src`, - ); + const packageDestination = getPackagePath(bundlerName); // If we don't need this bundler, no need to check for its bundle. if (BUNDLERS.find((bundler) => bundler.name.startsWith(bundlerName)) === undefined) { @@ -115,6 +123,22 @@ const getPackageDestination = (bundlerName: string) => { return packageDestination; }; +const getBuiltFiles = () => { + const pkgs = glob.sync('packages/plugins/**/package.json', { cwd: ROOT }); + const builtFiles = []; + + for (const pkg of pkgs) { + const content = require(path.resolve(ROOT, pkg)); + if (!content.toBuild) { + continue; + } + + builtFiles.push(...Object.keys(content.toBuild).map((f) => `${f}.js`)); + } + + return builtFiles; +}; + describe('Bundling', () => { let bundlerVersions: Partial> = {}; let processErrors: string[] = []; @@ -213,6 +237,31 @@ describe('Bundling', () => { const nameSize = Math.max(...BUNDLERS.map((bundler) => bundler.name.length)) + 1; + describe.each( + // Only do bundlers that are requested to be tested. + SUPPORTED_BUNDLERS.filter((bundlerName: string) => + BUNDLERS.find((bundler) => bundler.name.startsWith(bundlerName)), + ), + )('Bundler: %s', (bundlerName) => { + test(`Should add the correct files to @datadog/${bundlerName}-plugin.`, () => { + const builtFiles = getBuiltFiles(); + const expectedFiles = [ + 'index.d.ts', + 'index.js', + 'index.js.map', + 'index.mjs', + 'index.mjs.map', + ...builtFiles, + ].sort(); + const existingFiles = fs.readdirSync(getPackagePath(bundlerName)).sort(); + const exceptions = [ + // We are adding this file ourselves in the test to test both webpack4 and webpack5. + 'index4.js', + ]; + expect(existingFiles.filter((f) => !exceptions.includes(f))).toEqual(expectedFiles); + }); + }); + describe.each(BUNDLERS)('Bundler: $name', (bundler) => { test.each<{ projectName: string; filesToRun: string[] }>([ { projectName: 'easy', filesToRun: ['main.js'] }, diff --git a/packages/tools/src/helpers.ts b/packages/tools/src/helpers.ts index ad44339e2..25ef19a88 100644 --- a/packages/tools/src/helpers.ts +++ b/packages/tools/src/helpers.ts @@ -4,7 +4,7 @@ import { ALL_BUNDLERS, SUPPORTED_BUNDLERS } from '@dd/core/constants'; import { readJsonSync } from '@dd/core/helpers'; -import type { GetPlugins, Logger } from '@dd/core/types'; +import type { BuildReport, GetPlugins, Logger } from '@dd/core/types'; import chalk from 'chalk'; import { execFile, execFileSync } from 'child_process'; import path from 'path'; @@ -134,6 +134,11 @@ export const getWorkspaces = async ( // TODO: Update this, it's a bit hacky. export const getSupportedBundlers = (getPlugins: GetPlugins) => { + const bundler: BuildReport['bundler'] = { + name: 'esbuild', + fullName: 'esbuild', + version: '1.0.0', + }; const plugins = getPlugins( { telemetry: {}, @@ -150,15 +155,14 @@ export const getSupportedBundlers = (getPlugins: GetPlugins) => { version: '0', start: 0, bundler: { - name: 'esbuild', - fullName: 'esbuild', + ...bundler, outDir: ROOT, - version: '1.0.0', }, build: { warnings: [], errors: [], logs: [], + bundler, }, inject() {}, pluginNames: [], diff --git a/packages/tools/src/rollupConfig.mjs b/packages/tools/src/rollupConfig.mjs index bf6d25d46..a424b539a 100644 --- a/packages/tools/src/rollupConfig.mjs +++ b/packages/tools/src/rollupConfig.mjs @@ -6,18 +6,24 @@ import babel from '@rollup/plugin-babel'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import { nodeResolve } from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import chalk from 'chalk'; +import glob from 'glob'; import modulePackage from 'module'; +import path from 'path'; import dts from 'rollup-plugin-dts'; import esbuild from 'rollup-plugin-esbuild'; +const CWD = process.env.PROJECT_CWD; + /** * @param {{module: string; main: string;}} packageJson * @param {import('rollup').RollupOptions} config * @returns {import('rollup').RollupOptions} */ export const bundle = (packageJson, config) => ({ - ...config, input: 'src/index.ts', + ...config, external: [ // All peer dependencies are external dependencies. ...Object.keys(packageJson.peerDependencies), @@ -29,7 +35,15 @@ export const bundle = (packageJson, config) => ({ '@dd/tests', // We never want to include Node.js built-in modules in the bundle. ...modulePackage.builtinModules, + ...(config.external || []), ], + onwarn(warning, warn) { + // Ignore warnings about undefined `this`. + if (warning.code === 'THIS_IS_UNDEFINED') { + return; + } + warn(warning); + }, plugins: [ babel({ babelHelpers: 'bundled', @@ -40,37 +54,91 @@ export const bundle = (packageJson, config) => ({ nodeResolve({ preferBuiltins: true }), ...config.plugins, ], - output: { +}); + +/** + * @param {{module: string; main: string;}} packageJson + * @param {Partial} overrides + * @returns {import('rollup').OutputOptions} + */ +const getOutput = (packageJson, overrides = {}) => { + const filename = overrides.format === 'esm' ? packageJson.module : packageJson.main; + return { exports: 'named', sourcemap: true, - ...config.output, - }, -}); + entryFileNames: `[name]${path.extname(filename)}`, + dir: path.dirname(filename), + plugins: [terser()], + format: 'cjs', + // No chunks. + manualChunks: () => '[name]', + ...overrides, + }; +}; /** * @param {{module: string; main: string;}} packageJson * @returns {import('rollup').RollupOptions[]} */ -export const getDefaultBuildConfigs = (packageJson) => [ - bundle(packageJson, { - plugins: [esbuild()], - output: { - file: packageJson.module, - format: 'esm', - }, - }), - bundle(packageJson, { - plugins: [esbuild()], - output: { - file: packageJson.main, - format: 'cjs', - }, - }), - // FIXME: This build is sloooow. - bundle(packageJson, { - plugins: [dts()], - output: { - dir: 'dist/src', - }, - }), -]; +export const getDefaultBuildConfigs = async (packageJson) => { + // Verify if we have anything else to build from plugins. + const pkgs = glob.sync('packages/plugins/**/package.json', { cwd: CWD }); + const pluginBuilds = []; + for (const pkg of pkgs) { + const { default: content } = await import(path.resolve(CWD, pkg), { + assert: { type: 'json' }, + }); + + if (!content.toBuild) { + continue; + } + + console.log( + `Will also build ${chalk.green.bold(content.name)} additional files: ${chalk.green.bold(Object.keys(content.toBuild).join(', '))}`, + ); + + pluginBuilds.push( + ...Object.entries(content.toBuild).map(([name, config]) => { + return bundle(packageJson, { + plugins: [esbuild()], + external: config.external, + input: { + [name]: path.join(CWD, path.dirname(pkg), config.entry), + }, + output: [ + getOutput(packageJson, { + format: 'cjs', + sourcemap: false, + plugins: [terser({ mangle: true })], + }), + ], + }); + }), + ); + } + + const configs = [ + // Main bundle. + bundle(packageJson, { + plugins: [esbuild()], + input: { + index: 'src/index.ts', + }, + output: [ + getOutput(packageJson, { format: 'esm' }), + getOutput(packageJson, { format: 'cjs' }), + ], + }), + ...pluginBuilds, + // Bundle type definitions. + // FIXME: This build is sloooow. + // Check https://github.com/timocov/dts-bundle-generator + bundle(packageJson, { + plugins: [dts()], + output: { + dir: 'dist/src', + }, + }), + ]; + return configs; +}; diff --git a/yarn.lock b/yarn.lock index fc39359dc..6855f6bcd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1451,6 +1451,7 @@ __metadata: "@rollup/plugin-commonjs": "npm:28.0.1" "@rollup/plugin-json": "npm:6.1.0" "@rollup/plugin-node-resolve": "npm:15.3.0" + "@rollup/plugin-terser": "npm:0.4.4" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" @@ -1484,6 +1485,7 @@ __metadata: "@rollup/plugin-commonjs": "npm:28.0.1" "@rollup/plugin-json": "npm:6.1.0" "@rollup/plugin-node-resolve": "npm:15.3.0" + "@rollup/plugin-terser": "npm:0.4.4" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" @@ -1517,6 +1519,7 @@ __metadata: "@rollup/plugin-commonjs": "npm:28.0.1" "@rollup/plugin-json": "npm:6.1.0" "@rollup/plugin-node-resolve": "npm:15.3.0" + "@rollup/plugin-terser": "npm:0.4.4" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" @@ -1550,6 +1553,7 @@ __metadata: "@rollup/plugin-commonjs": "npm:28.0.1" "@rollup/plugin-json": "npm:6.1.0" "@rollup/plugin-node-resolve": "npm:15.3.0" + "@rollup/plugin-terser": "npm:0.4.4" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" @@ -1583,6 +1587,7 @@ __metadata: "@rollup/plugin-commonjs": "npm:28.0.1" "@rollup/plugin-json": "npm:6.1.0" "@rollup/plugin-node-resolve": "npm:15.3.0" + "@rollup/plugin-terser": "npm:0.4.4" "@types/babel__core": "npm:^7" "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" @@ -2758,6 +2763,22 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-terser@npm:0.4.4": + version: 0.4.4 + resolution: "@rollup/plugin-terser@npm:0.4.4" + dependencies: + serialize-javascript: "npm:^6.0.1" + smob: "npm:^1.0.0" + terser: "npm:^5.17.4" + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10/a5e066ddea55fc8c32188bc8b484cca619713516f10e3a06801881ec98bf37459ca24e5fe8711f93a5fa7f26a6e9132a47bc1a61c01e0b513dfd79a96cdc6eb7 + languageName: node + linkType: hard + "@rollup/pluginutils@npm:^5.0.1, @rollup/pluginutils@npm:^5.0.5, @rollup/pluginutils@npm:^5.1.0": version: 5.1.0 resolution: "@rollup/pluginutils@npm:5.1.0" @@ -10694,6 +10715,13 @@ __metadata: languageName: node linkType: hard +"smob@npm:^1.0.0": + version: 1.5.0 + resolution: "smob@npm:1.5.0" + checksum: 10/a1ea453bcea89989062626ea30a1fcb42c62e96255619c8641ffa1d7ab42baf415975c67c718127036901b9e487d8bf4c46219e50cec54295412c1227700b8fe + languageName: node + linkType: hard + "snapdragon-node@npm:^2.0.1": version: 2.1.1 resolution: "snapdragon-node@npm:2.1.1" @@ -11215,6 +11243,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.17.4": + version: 5.37.0 + resolution: "terser@npm:5.37.0" + dependencies: + "@jridgewell/source-map": "npm:^0.3.3" + acorn: "npm:^8.8.2" + commander: "npm:^2.20.0" + source-map-support: "npm:~0.5.20" + bin: + terser: bin/terser + checksum: 10/3afacf7c38c47a5a25dbe1ba2e7aafd61166474d4377ec0af490bd41ab3686ab12679818d5fe4a3e7f76efee26f639c92ac334940c378bbc31176520a38379c3 + languageName: node + linkType: hard + "terser@npm:^5.26.0": version: 5.29.2 resolution: "terser@npm:5.29.2"