diff --git a/.gitignore b/.gitignore index ed993498c..0934134d2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ yarn-error.log node_modules/ dist/ dist-basic/ +.dts-tmp-*/ .vitrine/* !.vitrine/project.json diff --git a/.yarn/cache/dts-bundle-generator-npm-9.5.1-0927b6826f-8abddebcaa.zip b/.yarn/cache/dts-bundle-generator-npm-9.5.1-0927b6826f-8abddebcaa.zip new file mode 100644 index 000000000..5b1c09314 Binary files /dev/null and b/.yarn/cache/dts-bundle-generator-npm-9.5.1-0927b6826f-8abddebcaa.zip differ diff --git a/.yarn/cache/dts-bundle-generator-patch-fdc30fb73d-41c445decf.zip b/.yarn/cache/dts-bundle-generator-patch-fdc30fb73d-41c445decf.zip new file mode 100644 index 000000000..6d29801d5 Binary files /dev/null and b/.yarn/cache/dts-bundle-generator-patch-fdc30fb73d-41c445decf.zip differ diff --git a/.yarn/cache/rollup-plugin-dts-npm-6.1.1-f13d240779-8a66833a5a.zip b/.yarn/cache/rollup-plugin-dts-npm-6.1.1-f13d240779-8a66833a5a.zip deleted file mode 100644 index e0b9629c3..000000000 Binary files a/.yarn/cache/rollup-plugin-dts-npm-6.1.1-f13d240779-8a66833a5a.zip and /dev/null differ diff --git a/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch b/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch new file mode 100644 index 000000000..1140fdcfb --- /dev/null +++ b/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch @@ -0,0 +1,14 @@ +diff --git a/dist/types-usage-evaluator.js b/dist/types-usage-evaluator.js +index a09f141f087b152c34ad9c88e66ed516f8294865..b3eb7f6a799992fe6c15b7caaa17a7eecde24478 100644 +--- a/dist/types-usage-evaluator.js ++++ b/dist/types-usage-evaluator.js +@@ -111,6 +111,9 @@ class TypesUsageEvaluator { + if (ts.isExportDeclaration(node) && node.exportClause !== undefined && ts.isNamedExports(node.exportClause)) { + for (const exportElement of node.exportClause.elements) { + const exportElementSymbol = (0, typescript_1.getImportExportReferencedSymbol)(exportElement, this.typeChecker); ++ if (exportElementSymbol === undefined) { ++ continue; ++ } + // i.e. `import * as NS from './local-module'` + const namespaceImportForElement = (0, typescript_1.getDeclarationsForSymbol)(exportElementSymbol).find(ts.isNamespaceImport); + if (namespaceImportForElement !== undefined) { diff --git a/LICENSES-3rdparty.csv b/LICENSES-3rdparty.csv index e37f4bfd5..b49ace056 100644 --- a/LICENSES-3rdparty.csv +++ b/LICENSES-3rdparty.csv @@ -374,6 +374,7 @@ diff,npm,BSD-3-Clause,(https://www.npmjs.com/package/diff) diff-sequences,npm,MIT,(https://www.npmjs.com/package/diff-sequences) dir-glob,npm,MIT,Kevin Mårtensson (github.com/kevva) doctrine,npm,Apache-2.0,(https://github.com/eslint/doctrine) +dts-bundle-generator,patch,MIT,Evgeniy Timokhov (https://github.com/timocov/dts-bundle-generator) eastasianwidth,npm,MIT,Masaki Komagata (https://www.npmjs.com/package/eastasianwidth) ecc-jsbn,npm,MIT,Jeremie Miller (https://github.com/quartzjer/ecc-jsbn) ejs,npm,Apache-2.0,Matthew Eernisse (https://github.com/mde/ejs) @@ -700,7 +701,6 @@ retry,npm,MIT,Tim Koschützki (https://github.com/tim-kos/node-retry) reusify,npm,MIT,Matteo Collina (https://github.com/mcollina/reusify#readme) rimraf,npm,ISC,Isaac Z. Schlueter (http://blog.izs.me/) rollup,npm,MIT,Rich Harris (https://rollupjs.org/) -rollup-plugin-dts,virtual,LGPL-3.0-only,Arpad Borsos (https://github.com/Swatinem/rollup-plugin-dts#readme) rollup-plugin-esbuild,virtual,MIT,EGOIST (https://www.npmjs.com/package/rollup-plugin-esbuild) rollup-plugin-import-css,virtual,MIT,Jacob Leeson (https://github.com/jleeson/rollup-plugin-import-css#readme) run-parallel,npm,MIT,Feross Aboukhadijeh (https://github.com/feross/run-parallel) diff --git a/package.json b/package.json index 5ba5ca00d..3d6c1be81 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,9 @@ "typescript": "5.4.3" }, "resolutions": { - "rollup": "4.45.1" + "rollup": "4.45.1", + "dts-bundle-generator/typescript": "5.4.3", + "dts-bundle-generator@npm:9.5.1": "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch" }, "packageManager": "yarn@4.10.3" } diff --git a/packages/core/src/helpers/request.ts b/packages/core/src/helpers/request.ts index 427ef9c5c..329cc259b 100644 --- a/packages/core/src/helpers/request.ts +++ b/packages/core/src/helpers/request.ts @@ -3,10 +3,7 @@ // 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'; @@ -61,7 +58,7 @@ export const getOriginHeaders = (opts: { bundler: string; plugin: string; versio }; export type RequestData = { - data: Gzip | Readable; + data: ReadableStream; headers: Record; }; @@ -78,8 +75,12 @@ export const createRequestData = async (options: { // 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; + + // Use Web Streams pipeThrough instead of Node.js pipe() to keep the pipeline lazy. + // Node.js pipe() immediately puts the source into flowing mode (starts reading blobs + // via process.nextTick), which races with cleanup of file-backed blobs after the + // request completes. Web Streams only start reading when the output is consumed. + const data = zip ? req.body!.pipeThrough(new CompressionStream('gzip')) : req.body!; const headers = { 'Content-Encoding': zip ? 'gzip' : 'multipart/form-data', diff --git a/packages/plugins/apps/package.json b/packages/plugins/apps/package.json index 978bf8414..a09626633 100644 --- a/packages/plugins/apps/package.json +++ b/packages/plugins/apps/package.json @@ -12,14 +12,14 @@ "directory": "packages/plugins/apps" }, "buildPlugin": { - "hideFromRootReadme": true - }, - "toBuild": { - "apps-runtime": { - "entry": "./src/built/apps-runtime.ts", - "format": [ - "esm" - ] + "hideFromRootReadme": true, + "toBuild": { + "apps-runtime": { + "entry": "./src/built/apps-runtime.ts", + "format": [ + "esm" + ] + } } }, "exports": { diff --git a/packages/plugins/error-tracking/src/sourcemaps/sender.test.ts b/packages/plugins/error-tracking/src/sourcemaps/sender.test.ts index 1f6d5ab63..56ad02553 100644 --- a/packages/plugins/error-tracking/src/sourcemaps/sender.test.ts +++ b/packages/plugins/error-tracking/src/sourcemaps/sender.test.ts @@ -20,8 +20,6 @@ import { getSourcemapsConfiguration, addFixtureFiles, } from '@dd/tests/_jest/helpers/mocks'; -import { type Stream } from 'stream'; -import { unzipSync } from 'zlib'; jest.mock('@dd/core/helpers/fs', () => { const original = jest.requireActual('@dd/core/helpers/fs'); @@ -42,19 +40,6 @@ jest.mock('@dd/core/helpers/request', () => { const doRequestMock = jest.mocked(doRequest); -function readFully(stream: Stream): Promise { - const chunks: any[] = []; - return new Promise((resolve, reject) => { - stream.on('data', (chunk) => chunks.push(chunk)); - - stream.on('end', () => { - resolve(Buffer.concat(chunks)); - }); - - stream.on('error', reject); - }); -} - const contextMock = getContextMock(); const uploadContextMock = { apiKey: contextMock.auth.apiKey, @@ -106,8 +91,9 @@ describe('Error Tracking Plugin Sourcemaps', () => { const payload = getPayloadMock(); const { data, headers } = await getData(payload)(); - const zippedData = await readFully(data); - const unzippedData = unzipSync(zippedData).toString('utf-8'); + const unzippedData = await new Response( + data.pipeThrough(new DecompressionStream('gzip')), + ).text(); const dataLines = unzippedData.split(/[\r\n]/g).filter(Boolean); const boundary = headers['content-type'] .split('boundary=') diff --git a/packages/plugins/metrics/src/common/sender.ts b/packages/plugins/metrics/src/common/sender.ts index 03ba81f40..818fafb7b 100644 --- a/packages/plugins/metrics/src/common/sender.ts +++ b/packages/plugins/metrics/src/common/sender.ts @@ -7,6 +7,7 @@ import type { Logger, Metric, MetricToSend } from '@dd/core/types'; import chalk from 'chalk'; export const METRICS_API_PATH = 'api/v1/series'; +const METRICS_BATCH_SIZE = 500; const green = chalk.bold.green; @@ -54,15 +55,28 @@ Sending ${metricsToSend.length} metrics with configuration: Metrics: - ${metricsNames.join('\n - ')}`); - return doRequest({ - method: 'POST', - url: `https://api.${auth.site}/${METRICS_API_PATH}?api_key=${auth.apiKey}`, - getData: () => ({ - data: JSON.stringify({ series: metricsToSend } satisfies { - series: Metric[]; + const batches: Metric[][] = []; + for (let i = 0; i < metricsToSend.length; i += METRICS_BATCH_SIZE) { + batches.push(metricsToSend.slice(i, i + METRICS_BATCH_SIZE)); + } + + if (batches.length > 1) { + log.debug(`Sending metrics in ${batches.length} batches of up to ${METRICS_BATCH_SIZE}.`); + } + + return Promise.all( + batches.map((batch) => + doRequest({ + method: 'POST', + url: `https://api.${auth.site}/${METRICS_API_PATH}?api_key=${auth.apiKey}`, + getData: () => ({ + data: JSON.stringify({ series: batch } satisfies { + series: Metric[]; + }), + }), }), - }), - }).catch((e) => { + ), + ).catch((e) => { log.error(`Error sending metrics ${e}`); }); }; diff --git a/packages/plugins/rum/package.json b/packages/plugins/rum/package.json index 15beca2e9..21937f9f8 100644 --- a/packages/plugins/rum/package.json +++ b/packages/plugins/rum/package.json @@ -12,18 +12,22 @@ "directory": "packages/plugins/rum" }, "buildPlugin": { - "hideFromRootReadme": true - }, - "toBuild": { - "rum-browser-sdk": { - "entry": "./src/built/rum-browser-sdk.ts" - }, - "privacy-helpers": { - "format": [ - "cjs", - "esm" - ], - "entry": "./src/built/privacy-helpers.ts" + "hideFromRootReadme": true, + "inlinedLibraries": [ + "@datadog/browser-core", + "@datadog/browser-rum-core" + ], + "toBuild": { + "rum-browser-sdk": { + "entry": "./src/built/rum-browser-sdk.ts" + }, + "privacy-helpers": { + "format": [ + "cjs", + "esm" + ], + "entry": "./src/built/privacy-helpers.ts" + } } }, "exports": { @@ -40,6 +44,7 @@ }, "devDependencies": { "@datadog/browser-rum": "6.26.0", + "@datadog/browser-rum-core": "6.26.0", "typescript": "5.4.3" } } diff --git a/packages/plugins/rum/src/browserSdkTypes.ts b/packages/plugins/rum/src/browserSdkTypes.ts new file mode 100644 index 000000000..298bf2f80 --- /dev/null +++ b/packages/plugins/rum/src/browserSdkTypes.ts @@ -0,0 +1,5 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +export type { RumPublicApi, RumInitConfiguration } from '@datadog/browser-rum-core'; diff --git a/packages/plugins/rum/src/index.ts b/packages/plugins/rum/src/index.ts index fbeafdb2c..e33bbca0b 100644 --- a/packages/plugins/rum/src/index.ts +++ b/packages/plugins/rum/src/index.ts @@ -6,11 +6,12 @@ import type { PluginOptions, GetPlugins } from '@dd/core/types'; import { InjectPosition } from '@dd/core/types'; import path from 'path'; +import type { RumInitConfiguration, RumPublicApi } from './browserSdkTypes'; import { CONFIG_KEY, PLUGIN_NAME } from './constants'; import { getSourceCodeContextSnippet } from './getSourceCodeContextSnippet'; import { getPrivacyPlugin } from './privacy'; import { getInjectionValue } from './sdk'; -import type { RumOptions, RumOptionsWithSdk, RumPublicApi, RumInitConfiguration } from './types'; +import type { RumOptions, RumOptionsWithSdk } from './types'; import { validateOptions } from './validate'; export { CONFIG_KEY, PLUGIN_NAME }; diff --git a/packages/plugins/rum/src/types.ts b/packages/plugins/rum/src/types.ts index 23986ca9c..ef16abe5b 100644 --- a/packages/plugins/rum/src/types.ts +++ b/packages/plugins/rum/src/types.ts @@ -2,12 +2,9 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2019-Present Datadog, Inc. -import type { - datadogRum, - RumInitConfiguration as ExpRumInitConfiguration, -} from '@datadog/browser-rum'; import type { Assign } from '@dd/core/types'; +import type { RumInitConfiguration } from './browserSdkTypes'; import type { PrivacyOptions, PrivacyOptionsWithDefaults } from './privacy/types'; export type SourceCodeContextOptions = { @@ -22,9 +19,6 @@ export type RumOptions = { sourceCodeContext?: SourceCodeContextOptions; }; -export type RumPublicApi = typeof datadogRum; -export type RumInitConfiguration = ExpRumInitConfiguration; - export type SDKOptions = Assign< RumInitConfiguration, { diff --git a/packages/published/esbuild-plugin/package.json b/packages/published/esbuild-plugin/package.json index 0736f7df1..44e4cd5c0 100644 --- a/packages/published/esbuild-plugin/package.json +++ b/packages/published/esbuild-plugin/package.json @@ -78,9 +78,9 @@ "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", + "dts-bundle-generator": "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch", "esbuild": "0.25.8", "rollup": "4.45.1", - "rollup-plugin-dts": "6.1.1", "rollup-plugin-esbuild": "6.1.1", "typescript": "5.4.3" }, diff --git a/packages/published/rollup-plugin/package.json b/packages/published/rollup-plugin/package.json index 2fdf492bb..c011f19fb 100644 --- a/packages/published/rollup-plugin/package.json +++ b/packages/published/rollup-plugin/package.json @@ -81,9 +81,9 @@ "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", + "dts-bundle-generator": "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch", "esbuild": "0.25.8", "rollup": "4.45.1", - "rollup-plugin-dts": "6.1.1", "rollup-plugin-esbuild": "6.1.1", "typescript": "5.4.3" }, diff --git a/packages/published/rspack-plugin/package.json b/packages/published/rspack-plugin/package.json index a9a68ecbe..d55c2678a 100644 --- a/packages/published/rspack-plugin/package.json +++ b/packages/published/rspack-plugin/package.json @@ -78,9 +78,9 @@ "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", + "dts-bundle-generator": "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch", "esbuild": "0.25.8", "rollup": "4.45.1", - "rollup-plugin-dts": "6.1.1", "rollup-plugin-esbuild": "6.1.1", "typescript": "5.4.3" }, diff --git a/packages/published/vite-plugin/package.json b/packages/published/vite-plugin/package.json index 5f00b83f4..3d4955bf2 100644 --- a/packages/published/vite-plugin/package.json +++ b/packages/published/vite-plugin/package.json @@ -78,9 +78,9 @@ "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", + "dts-bundle-generator": "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch", "esbuild": "0.25.8", "rollup": "4.45.1", - "rollup-plugin-dts": "6.1.1", "rollup-plugin-esbuild": "6.1.1", "typescript": "5.4.3" }, diff --git a/packages/published/webpack-plugin/package.json b/packages/published/webpack-plugin/package.json index 97cfd5c63..e85a2a1c9 100644 --- a/packages/published/webpack-plugin/package.json +++ b/packages/published/webpack-plugin/package.json @@ -78,9 +78,9 @@ "@rollup/plugin-terser": "0.4.4", "@types/babel__core": "^7", "@types/babel__preset-env": "^7", + "dts-bundle-generator": "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch", "esbuild": "0.25.8", "rollup": "4.45.1", - "rollup-plugin-dts": "6.1.1", "rollup-plugin-esbuild": "6.1.1", "typescript": "5.4.3" }, diff --git a/packages/tools/src/commands/integrity/index.ts b/packages/tools/src/commands/integrity/index.ts index 69e0083ef..eeda26687 100644 --- a/packages/tools/src/commands/integrity/index.ts +++ b/packages/tools/src/commands/integrity/index.ts @@ -26,6 +26,7 @@ class Integrity extends Command { const { updateDependencies } = await import('./dependencies'); const { updateFiles } = await import('./files'); const { updateReadmes, injectTocsInAllReadmes, verifyLinks } = await import('./readme'); + const { updateTsconfigPaths } = await import('./tsconfig'); const { getWorkspaces } = await import('@dd/tools/helpers'); const workspaces = await getWorkspaces(); @@ -51,6 +52,8 @@ class Integrity extends Command { errors.push(...(await verifyLinks())); // Update the files that need to be updated. errors.push(...(await updateFiles(plugins))); + // Sync @dd/* paths in the root tsconfig with each workspace's package.json exports. + updateTsconfigPaths(workspaces); // Run auto-fixes to ensure the code is correct. errors.push(...(await runAutoFixes())); diff --git a/packages/tools/src/commands/integrity/tsconfig.ts b/packages/tools/src/commands/integrity/tsconfig.ts new file mode 100644 index 000000000..73fcc017b --- /dev/null +++ b/packages/tools/src/commands/integrity/tsconfig.ts @@ -0,0 +1,55 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +import { outputJsonSync, readJsonSync } from '@dd/core/helpers/fs'; +import { ROOT } from '@dd/tools/constants'; +import { green } from '@dd/tools/helpers'; +import type { Workspace } from '@dd/tools/types'; +import path from 'path'; + +type PackageExports = Record>; + +const buildExpectedPaths = (workspaces: Workspace[]): Record => { + const paths: Record = {}; + for (const workspace of workspaces) { + if (!workspace.name.startsWith('@dd/')) { + continue; + } + const pkg = readJsonSync(path.resolve(ROOT, workspace.location, 'package.json')); + const pkgExports: PackageExports | undefined = pkg.exports; + if (!pkgExports) { + continue; + } + const location = workspace.location.replace(/\\/g, '/'); + const mainExport = pkgExports['.']; + if (typeof mainExport === 'string') { + paths[workspace.name] = [`${location}/${mainExport.replace(/^\.\//, '')}`]; + } + const wildcardExport = pkgExports['./*']; + if (typeof wildcardExport === 'string') { + const target = wildcardExport.replace(/^\.\//, '').replace(/\*\.ts$/, '*'); + paths[`${workspace.name}/*`] = [`${location}/${target}`]; + } + } + return paths; +}; + +const sortObject = (obj: Record): Record => { + return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))); +}; + +export const updateTsconfigPaths = (workspaces: Workspace[]) => { + const tsconfigPath = path.resolve(ROOT, 'tsconfig.json'); + const tsconfig = readJsonSync(tsconfigPath); + const expected = sortObject(buildExpectedPaths(workspaces)); + const current = tsconfig.compilerOptions?.paths ?? {}; + + if (JSON.stringify(current) === JSON.stringify(expected)) { + return; + } + + console.log(` Update ${green('@dd/*')} paths in ${green('tsconfig.json')}.`); + tsconfig.compilerOptions = { ...tsconfig.compilerOptions, paths: expected }; + outputJsonSync(tsconfigPath, tsconfig); +}; diff --git a/packages/tools/src/dtsBundlePlugin.mjs b/packages/tools/src/dtsBundlePlugin.mjs new file mode 100644 index 000000000..a13b36fb8 --- /dev/null +++ b/packages/tools/src/dtsBundlePlugin.mjs @@ -0,0 +1,152 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. + +// @ts-check + +import { generateDtsBundle } from 'dts-bundle-generator'; +import fs from 'fs'; +import { glob } from 'glob'; +import path from 'path'; +import ts from 'typescript'; + +const CWD = process.env.PROJECT_CWD || process.cwd(); + +/** + * @typedef {{ + * main: string; + * name: string; + * peerDependencies: Record; + * dependencies: Record + * }} PackageJson + * @typedef {import('rollup').Plugin} Plugin + */ + +/** + * Walks every plugin's package.json and returns the union of declared + * `buildPlugin.inlinedLibraries` — packages whose types should be inlined into + * the bundled .d.ts rather than left as imports (e.g. browser-SDK types that + * aren't real runtime deps of the published packages). + * @returns {string[]} + */ +const collectInlinedLibraries = () => { + const libs = new Set(); + for (const pkg of glob.sync('packages/plugins/*/package.json', { cwd: CWD })) { + const content = JSON.parse(fs.readFileSync(path.resolve(CWD, pkg), 'utf-8')); + for (const name of content.buildPlugin?.inlinedLibraries ?? []) { + libs.add(name); + } + } + return [...libs]; +}; + +/** + * Converts the root tsconfig's @dd/* `paths` (pointing at .ts sources) to the + * equivalent .d.ts paths rooted in outDir. Used to redirect dts-bundle-generator + * to the declarations pre-emitted by pass 1. + * @param {string} outDir + * @param {Record} srcPaths + * @returns {Record} + */ +const buildDtsPaths = (outDir, srcPaths) => { + /** @type {Record} */ + const result = {}; + const rel = path.relative(CWD, outDir).replace(/\\/g, '/'); + for (const [key, values] of Object.entries(srcPaths)) { + result[key] = values.map((v) => { + const dtsV = v.endsWith('.ts') ? v.replace(/\.ts$/, '.d.ts') : v; + return `${rel}/${dtsV}`; + }); + } + return result; +}; + +/** + * Returns a rollup plugin that generates a bundled .d.ts using dts-bundle-generator. + * + * Two-pass approach to avoid DOM lib vs @types/node conflicts: + * Pass 1 — TypeScript emits .d.ts for workspace files without DOM lib (no conflicts + * because workspace code is Node.js-only). + * Pass 2 — dts-bundle-generator runs against the emitted .d.ts with DOM lib enabled. + * Because every reachable file is a .d.ts (entry + @dd/* redirected via + * `paths`), it hits the `allFilesAreDeclarations` shortcut in + * compile-dts.js and skips its own compilation — so DOM vs Node.js type + * conflicts never arise. DOM lib is still needed at this stage so the + * TypesUsageEvaluator can resolve Window / EventTarget / XMLHttpRequest + * etc. referenced in @datadog/browser-* declarations. + * + * @param {PackageJson} packageJson + * @returns {Plugin} + */ +export const getDtsBundlePlugin = (packageJson) => ({ + name: 'dts-bundle-generator', + async closeBundle() { + const safeName = packageJson.name.replace(/[^a-zA-Z0-9]/g, '-'); + const tempDtsDir = path.join(CWD, `.dts-tmp-${safeName}`); + const tempBundleConfigPath = path.join(tempDtsDir, 'tsconfig.bundle.json'); + const entrySrcPath = path.resolve('src/index.ts'); + const entryDtsPath = path.join( + tempDtsDir, + path.relative(CWD, entrySrcPath).replace(/\.ts$/, '.d.ts'), + ); + + fs.mkdirSync(tempDtsDir, { recursive: true }); + try { + // Workspace @dd/* paths live in the root tsconfig — both passes + // extend it. Pass 1 inherits them as-is; pass 2 rewrites them to + // point at the .d.ts files emitted by pass 1. + const rootConfig = ts.readConfigFile( + path.join(CWD, 'tsconfig.json'), + ts.sys.readFile, + ).config; + const parsedConfig = ts.parseJsonConfigFileContent(rootConfig, ts.sys, CWD); + + // Pass 1 — emit. + ts.createProgram([entrySrcPath], { + ...parsedConfig.options, + noEmit: false, + declaration: true, + emitDeclarationOnly: true, + outDir: tempDtsDir, + }).emit(); + + // Pass 2 — bundle. dts-bundle-generator requires a real tsconfig path, + // so this one stays on disk. + fs.writeFileSync( + tempBundleConfigPath, + JSON.stringify({ + extends: path.join(CWD, 'tsconfig.json'), + compilerOptions: { + lib: ['es2022', 'dom'], + paths: buildDtsPaths(tempDtsDir, rootConfig.compilerOptions.paths), + }, + }), + ); + + const inlinedLibraries = collectInlinedLibraries(); + const importedLibraries = [ + ...Object.keys(packageJson.peerDependencies), + ...Object.keys(packageJson.dependencies), + ].filter((name) => !inlinedLibraries.includes(name)); + const [result] = generateDtsBundle( + [ + { + filePath: entryDtsPath, + // `exportReferencedTypes: false` keeps internal types from + // `@datadog/browser-*` as `declare` rather than `export`, + // so they don't pollute our public API surface. + output: { noBanner: true, exportReferencedTypes: false }, + libraries: { inlinedLibraries, importedLibraries }, + }, + ], + { preferredConfigPath: tempBundleConfigPath }, + ); + + const outputPath = path.resolve(path.dirname(packageJson.main), 'index.d.ts'); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, result); + } finally { + fs.rmSync(tempDtsDir, { recursive: true, force: true }); + } + }, +}); diff --git a/packages/tools/src/rollupConfig.mjs b/packages/tools/src/rollupConfig.mjs index a7a2692d9..fadfd2a5e 100644 --- a/packages/tools/src/rollupConfig.mjs +++ b/packages/tools/src/rollupConfig.mjs @@ -16,9 +16,10 @@ import fs from 'fs'; import { glob } from 'glob'; import modulePackage from 'module'; import path from 'path'; -import dts from 'rollup-plugin-dts'; import esbuild from 'rollup-plugin-esbuild'; +import { getDtsBundlePlugin } from './dtsBundlePlugin.mjs'; + const CWD = process.env.PROJECT_CWD || process.cwd(); const ROLLUP_PLUGIN_PATH = 'rollup-plugin/dist-basic/src'; const BUNDLER_NAME_RX = /^@datadog\/(.+)-plugin$/g; @@ -33,7 +34,6 @@ const BUNDLER_NAME_RX = /^@datadog\/(.+)-plugin$/g; * }} PackageJson * @typedef {{ basic?: boolean }} BuildOptions * @typedef {import('rollup').InputPluginOption} InputPluginOption - * @typedef {import('rollup').Plugin} Plugin * @typedef {import('@dd/core/types').Options} PluginOptions * @typedef {import('@dd/core/types').Assign< * import('rollup').RollupOptions, @@ -214,26 +214,32 @@ export const getSubBuilds = async (ddPlugin, packageJson, options) => { with: { type: 'json' }, }); - if (!content.toBuild) { + const toBuild = content.buildPlugin?.toBuild; + if (!toBuild) { continue; } console.log( - `Will also build ${chalk.green.bold(content.name)} additional files: ${chalk.green.bold(Object.keys(content.toBuild).join(', '))}`, + `Will also build ${chalk.green.bold(content.name)} additional files: ${chalk.green.bold(Object.keys(toBuild).join(', '))}`, ); subBuilds.push( - ...Object.entries(content.toBuild).map(([name, config]) => { - const outputs = (config.format ?? ['cjs']).map((format) => - getOutput( - packageJson, - { - format, - sourcemap: false, - plugins: [terser({ mangle: true })], - }, - options, - ), + ...Object.entries(toBuild).map(([name, config]) => { + const outputs = (config.format ?? ['cjs']).map( + /** + * @param {'esm' | 'cjs'} format + * @returns {OutputOptions} + */ + (format) => + getOutput( + packageJson, + { + format, + sourcemap: false, + plugins: [terser({ mangle: true })], + }, + options, + ), ); const plugins = [esbuild()]; if (ddPlugin) { @@ -266,10 +272,11 @@ export const getDefaultBuildConfigs = async (packageJson, options) => { // Plugins to use. const mainBundlePlugins = [esbuild()]; - const dtsBundlePlugins = [dts()]; if (ddPlugin) { mainBundlePlugins.push(ddPlugin(getPluginConfig(bundlerName, packageJson.name, true))); - dtsBundlePlugins.push(ddPlugin(getPluginConfig(bundlerName, `dts:${packageJson.name}`))); + } + if (!isBasicBuild && !process.env.NO_TYPES) { + mainBundlePlugins.push(getDtsBundlePlugin(packageJson)); } // Sub builds. @@ -288,19 +295,5 @@ export const getDefaultBuildConfigs = async (packageJson, options) => { output: mainBundleOutputs, }); - const configs = [mainBundleConfig, ...subBuilds]; - - // Bundle type definitions. - if (!isBasicBuild && !process.env.NO_TYPES) { - configs.push( - // FIXME: This build is sloooow. - bundle(packageJson, { - plugins: dtsBundlePlugins, - output: { - dir: 'dist/src', - }, - }), - ); - } - return configs; + return [mainBundleConfig, ...subBuilds]; }; diff --git a/packages/tools/src/rollupConfig.test.ts b/packages/tools/src/rollupConfig.test.ts index a8dc5b846..e01117bd6 100644 --- a/packages/tools/src/rollupConfig.test.ts +++ b/packages/tools/src/rollupConfig.test.ts @@ -129,13 +129,14 @@ const getBuiltFiles = () => { for (const pkg of pkgs) { const content = require(path.resolve(ROOT, pkg)); - if (!content.toBuild) { + const toBuild = content.buildPlugin?.toBuild; + if (!toBuild) { continue; } builtFiles.push( - ...Object.keys(content.toBuild).flatMap((f) => - (content.toBuild[f].format ?? ['cjs']).map((format: string) => + ...Object.keys(toBuild).flatMap((f) => + (toBuild[f].format ?? ['cjs']).map((format: string) => format === 'cjs' ? `${f}.js` : `${f}.mjs`, ), ), diff --git a/tsconfig.json b/tsconfig.json index 23417659a..6ea9682ca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,42 @@ "moduleResolution": "bundler", "noEmit": true, "noImplicitAny": true, + "paths": { + "@dd/apps-plugin": ["packages/plugins/apps/src/index.ts"], + "@dd/apps-plugin/*": ["packages/plugins/apps/src/*"], + "@dd/assets/*": ["packages/assets/src/*"], + "@dd/core/*": ["packages/core/src/*"], + "@dd/error-tracking-plugin": ["packages/plugins/error-tracking/src/index.ts"], + "@dd/error-tracking-plugin/*": ["packages/plugins/error-tracking/src/*"], + "@dd/factory": ["packages/factory/src/index.ts"], + "@dd/factory/*": ["packages/factory/src/*"], + "@dd/internal-analytics-plugin": ["packages/plugins/analytics/src/index.ts"], + "@dd/internal-analytics-plugin/*": ["packages/plugins/analytics/src/*"], + "@dd/internal-async-queue-plugin": ["packages/plugins/async-queue/src/index.ts"], + "@dd/internal-async-queue-plugin/*": ["packages/plugins/async-queue/src/*"], + "@dd/internal-build-report-plugin": ["packages/plugins/build-report/src/index.ts"], + "@dd/internal-build-report-plugin/*": ["packages/plugins/build-report/src/*"], + "@dd/internal-bundler-report-plugin": ["packages/plugins/bundler-report/src/index.ts"], + "@dd/internal-bundler-report-plugin/*": ["packages/plugins/bundler-report/src/*"], + "@dd/internal-custom-hooks-plugin": ["packages/plugins/custom-hooks/src/index.ts"], + "@dd/internal-custom-hooks-plugin/*": ["packages/plugins/custom-hooks/src/*"], + "@dd/internal-git-plugin": ["packages/plugins/git/src/index.ts"], + "@dd/internal-git-plugin/*": ["packages/plugins/git/src/*"], + "@dd/internal-injection-plugin": ["packages/plugins/injection/src/index.ts"], + "@dd/internal-injection-plugin/*": ["packages/plugins/injection/src/*"], + "@dd/internal-true-end-plugin": ["packages/plugins/true-end/src/index.ts"], + "@dd/internal-true-end-plugin/*": ["packages/plugins/true-end/src/*"], + "@dd/live-debugger-plugin": ["packages/plugins/live-debugger/src/index.ts"], + "@dd/live-debugger-plugin/*": ["packages/plugins/live-debugger/src/*"], + "@dd/metrics-plugin": ["packages/plugins/metrics/src/index.ts"], + "@dd/metrics-plugin/*": ["packages/plugins/metrics/src/*"], + "@dd/output-plugin": ["packages/plugins/output/src/index.ts"], + "@dd/output-plugin/*": ["packages/plugins/output/src/*"], + "@dd/rum-plugin": ["packages/plugins/rum/src/index.ts"], + "@dd/rum-plugin/*": ["packages/plugins/rum/src/*"], + "@dd/tests/*": ["packages/tests/src/*"], + "@dd/tools/*": ["packages/tools/src/*"] + }, "resolveJsonModule": true, "rootDir": "./", "skipLibCheck": true, diff --git a/yarn.lock b/yarn.lock index c77de7682..53c2bff71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1699,6 +1699,7 @@ __metadata: "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" chalk: "npm:2.3.1" + dts-bundle-generator: "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch" esbuild: "npm:0.25.8" eslint-scope: "npm:7.2.2" glob: "npm:11.1.0" @@ -1708,7 +1709,6 @@ __metadata: p-queue: "npm:6.6.2" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" - rollup-plugin-dts: "npm:6.1.1" rollup-plugin-esbuild: "npm:6.1.1" simple-git: "npm:3.36.0" typescript: "npm:5.4.3" @@ -1759,6 +1759,7 @@ __metadata: "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" chalk: "npm:2.3.1" + dts-bundle-generator: "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch" esbuild: "npm:0.25.8" eslint-scope: "npm:7.2.2" glob: "npm:11.1.0" @@ -1768,7 +1769,6 @@ __metadata: p-queue: "npm:6.6.2" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" - rollup-plugin-dts: "npm:6.1.1" rollup-plugin-esbuild: "npm:6.1.1" simple-git: "npm:3.36.0" typescript: "npm:5.4.3" @@ -1812,6 +1812,7 @@ __metadata: "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" chalk: "npm:2.3.1" + dts-bundle-generator: "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch" esbuild: "npm:0.25.8" eslint-scope: "npm:7.2.2" glob: "npm:11.1.0" @@ -1821,7 +1822,6 @@ __metadata: p-queue: "npm:6.6.2" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" - rollup-plugin-dts: "npm:6.1.1" rollup-plugin-esbuild: "npm:6.1.1" simple-git: "npm:3.36.0" typescript: "npm:5.4.3" @@ -1865,6 +1865,7 @@ __metadata: "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" chalk: "npm:2.3.1" + dts-bundle-generator: "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch" esbuild: "npm:0.25.8" eslint-scope: "npm:7.2.2" glob: "npm:11.1.0" @@ -1874,7 +1875,6 @@ __metadata: p-queue: "npm:6.6.2" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" - rollup-plugin-dts: "npm:6.1.1" rollup-plugin-esbuild: "npm:6.1.1" simple-git: "npm:3.36.0" typescript: "npm:5.4.3" @@ -1918,6 +1918,7 @@ __metadata: "@types/babel__preset-env": "npm:^7" async-retry: "npm:1.3.3" chalk: "npm:2.3.1" + dts-bundle-generator: "patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch" esbuild: "npm:0.25.8" eslint-scope: "npm:7.2.2" glob: "npm:11.1.0" @@ -1927,7 +1928,6 @@ __metadata: p-queue: "npm:6.6.2" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" - rollup-plugin-dts: "npm:6.1.1" rollup-plugin-esbuild: "npm:6.1.1" simple-git: "npm:3.36.0" typescript: "npm:5.4.3" @@ -2170,6 +2170,7 @@ __metadata: resolution: "@dd/rum-plugin@workspace:packages/plugins/rum" dependencies: "@datadog/browser-rum": "npm:6.26.0" + "@datadog/browser-rum-core": "npm:6.26.0" "@datadog/js-instrumentation-wasm": "npm:1.0.8" "@dd/core": "workspace:*" chalk: "npm:2.3.1" @@ -5973,6 +5974,30 @@ __metadata: languageName: node linkType: hard +"dts-bundle-generator@npm:9.5.1": + version: 9.5.1 + resolution: "dts-bundle-generator@npm:9.5.1" + dependencies: + typescript: "npm:>=5.0.2" + yargs: "npm:^17.6.0" + bin: + dts-bundle-generator: dist/bin/dts-bundle-generator.js + checksum: 10/8abddebcaab0d542afcb62971526beb5ea09f977b92cb06723e6046bde7d2bc9513c8508b937f62d7a46b28827b26fda4c31ed85fe2902e01685741f823a50eb + languageName: node + linkType: hard + +"dts-bundle-generator@patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch": + version: 9.5.1 + resolution: "dts-bundle-generator@patch:dts-bundle-generator@npm%3A9.5.1#~/.yarn/patches/dts-bundle-generator-npm-9.5.1-0927b6826f.patch::version=9.5.1&hash=e63d7e" + dependencies: + typescript: "npm:>=5.0.2" + yargs: "npm:^17.6.0" + bin: + dts-bundle-generator: dist/bin/dts-bundle-generator.js + checksum: 10/41c445decf3c4e083526e89bf0c6729cc12d6e2823ca79eade3ae9c0106813408b3fa5a3add226cd5323d287a71faf3b2a2ab9a841b1f76fcc5abfb3514d74e6 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -8790,7 +8815,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:0.30.21, magic-string@npm:^0.30.10, magic-string@npm:^0.30.3": +"magic-string@npm:0.30.21, magic-string@npm:^0.30.3": version: 0.30.21 resolution: "magic-string@npm:0.30.21" dependencies: @@ -9936,22 +9961,6 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-dts@npm:6.1.1": - version: 6.1.1 - resolution: "rollup-plugin-dts@npm:6.1.1" - dependencies: - "@babel/code-frame": "npm:^7.24.2" - magic-string: "npm:^0.30.10" - peerDependencies: - rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 - dependenciesMeta: - "@babel/code-frame": - optional: true - checksum: 10/8a66833a5af32f77d9bbc746339097d4af2382e5160f7629d85dcecb4efad12cbfebd37c79147fa688f073c333d71f53135e08a225a3fc3e9a3b3f92c46b2381 - languageName: node - linkType: hard - "rollup-plugin-esbuild@npm:6.1.1": version: 6.1.1 resolution: "rollup-plugin-esbuild@npm:6.1.1" @@ -11521,7 +11530,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.7.2": +"yargs@npm:^17.6.0, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: