diff --git a/packages/plugins/injection/src/constants.ts b/packages/plugins/injection/src/constants.ts index 72561fa7..b2df165e 100644 --- a/packages/plugins/injection/src/constants.ts +++ b/packages/plugins/injection/src/constants.ts @@ -6,3 +6,4 @@ export const PLUGIN_NAME = 'datadog-injection-plugin'; export const DISTANT_FILE_RX = /^https?:\/\//; export const BEFORE_INJECTION = `// begin injection by Datadog build plugins`; export const AFTER_INJECTION = `// end injection by Datadog build plugins`; +export const SUPPORTED_EXTENSIONS = ['.mjs', '.mjsx', '.js', '.ts', '.tsx', '.jsx']; diff --git a/packages/plugins/injection/src/esbuild.ts b/packages/plugins/injection/src/esbuild.ts index dc336ebd..3182ac8b 100644 --- a/packages/plugins/injection/src/esbuild.ts +++ b/packages/plugins/injection/src/esbuild.ts @@ -8,17 +8,20 @@ import { outputFile } from '@dd/core/helpers/fs'; import { getAbsolutePath } from '@dd/core/helpers/paths'; import type { Logger, PluginOptions, GlobalContext, ResolvedEntry } from '@dd/core/types'; import { InjectPosition } from '@dd/core/types'; -import chalk from 'chalk'; import fs from 'fs'; import os from 'os'; import path from 'path'; import { PLUGIN_NAME } from './constants'; -import { getContentToInject, isNodeSystemError } from './helpers'; +import { + getContentToInject, + isNodeSystemError, + isFileSupported, + warnUnsupportedFile, +} from './helpers'; import type { ContentsToInject } from './types'; const fsp = fs.promises; -const yellow = chalk.bold.yellow; export const getEsbuildPlugin = ( log: Logger, @@ -119,17 +122,13 @@ export const getEsbuildPlugin = ( }) .filter(Boolean) as string[]; - const isSupported = (ext: string): boolean => { - return ['.js', '.ts', '.tsx', '.jsx'].includes(ext); - }; - // Write the content. const proms = outputs .filter((output) => { const { base, ext } = path.parse(output); - const isOutputSupported = isSupported(ext); + const isOutputSupported = isFileSupported(ext); if (!isOutputSupported) { - log.warn(`${yellow(ext)} files are not supported (${yellow(base)}).`); + warnUnsupportedFile(log, ext, base); } return isOutputSupported; }) diff --git a/packages/plugins/injection/src/helpers.ts b/packages/plugins/injection/src/helpers.ts index e3342739..faf0e6a7 100644 --- a/packages/plugins/injection/src/helpers.ts +++ b/packages/plugins/injection/src/helpers.ts @@ -8,10 +8,18 @@ import { doRequest } from '@dd/core/helpers/request'; import { truncateString } from '@dd/core/helpers/strings'; import type { Logger, ToInjectItem } from '@dd/core/types'; import { InjectPosition } from '@dd/core/types'; - -import { AFTER_INJECTION, BEFORE_INJECTION, DISTANT_FILE_RX } from './constants'; +import chalk from 'chalk'; + +import { + AFTER_INJECTION, + BEFORE_INJECTION, + DISTANT_FILE_RX, + SUPPORTED_EXTENSIONS, +} from './constants'; import type { ContentsToInject } from './types'; +const yellow = chalk.bold.yellow; + const MAX_TIMEOUT_IN_MS = 5000; export const getInjectedValue = async (item: ToInjectItem): Promise => { @@ -141,3 +149,11 @@ export interface NodeSystemError extends Error { export const isNodeSystemError = (e: unknown): e is NodeSystemError => { return e instanceof Error && 'code' in e; }; + +export const isFileSupported = (ext: string): boolean => { + return SUPPORTED_EXTENSIONS.includes(ext); +}; + +export const warnUnsupportedFile = (log: Logger, ext: string, filename: string): void => { + log.warn(`"${yellow(ext)}" files are not supported (${yellow(filename)}).`); +}; diff --git a/packages/plugins/injection/src/index.test.ts b/packages/plugins/injection/src/index.test.ts index 0f2596e4..2eb3f7e6 100644 --- a/packages/plugins/injection/src/index.test.ts +++ b/packages/plugins/injection/src/index.test.ts @@ -6,15 +6,15 @@ import { outputFileSync } from '@dd/core/helpers/fs'; import type { Assign, BundlerName, Options, ToInjectItem } from '@dd/core/types'; import { InjectPosition } from '@dd/core/types'; import { AFTER_INJECTION, BEFORE_INJECTION } from '@dd/internal-injection-plugin/constants'; -import { addInjections } from '@dd/internal-injection-plugin/helpers'; +import { addInjections, isFileSupported } from '@dd/internal-injection-plugin/helpers'; import { hardProjectEntries, defaultPluginOptions, - easyProjectEntry, + easyProjectWithCSSEntry, } from '@dd/tests/_jest/helpers/mocks'; import { BUNDLERS, runBundlers } from '@dd/tests/_jest/helpers/runBundlers'; import { header, licenses } from '@dd/tools/commands/oss/templates'; -import { escapeStringForRegExp, execute } from '@dd/tools/helpers'; +import { escapeStringForRegExp, execute, red } from '@dd/tools/helpers'; import chalk from 'chalk'; import { readFileSync } from 'fs'; import { glob } from 'glob'; @@ -110,7 +110,9 @@ describe('Injection Plugin', () => { }, }; - const { errors } = await runBundlers(pluginConfig); + const { errors } = await runBundlers(pluginConfig, { + entry: { main: easyProjectWithCSSEntry }, + }); buildErrors.push(...errors); // Store the calls, because Jest resets mocks in beforeEach 🤷 calls.push(...addInjectionsMock.mock.calls.flatMap((c) => Array.from(c[1].values()))); @@ -129,6 +131,22 @@ describe('Injection Plugin', () => { test('Should inject items through the context.', () => { expect(calls.find((c) => c.value === injectedString)).toBeDefined(); }); + + test('Should only inject in files we support', () => { + const outdir = outdirs[bundler.name]; + const builtFiles = glob.sync(path.resolve(outdir, '**/*.*'), {}); + for (const file of builtFiles) { + const content = readFileSync(file, 'utf8'); + const repetitions = isFileSupported(path.extname(file)) ? 1 : 0; + try { + expect(content).toRepeatStringTimes(injectedString, repetitions); + } catch (e: any) { + // Overwrite the error message so we know which file is failing. + e.message = `Failure on file "${red(file)}":\n${e.message}`; + throw e; + } + } + }); }); }); @@ -295,7 +313,7 @@ describe('Injection Plugin', () => { }[] = [ { name: 'Easy build without injections', - entry: { main: easyProjectEntry }, + entry: { main: easyProjectWithCSSEntry }, positions: [], injections: [[], 0], expectations: easyWithoutInjections, @@ -309,7 +327,7 @@ describe('Injection Plugin', () => { }, { name: 'Easy build with injections', - entry: { main: easyProjectEntry }, + entry: { main: easyProjectWithCSSEntry }, positions: Object.values(Position), injections: [toInjectItems, 10], expectations: easyWithInjections, diff --git a/packages/plugins/injection/src/index.ts b/packages/plugins/injection/src/index.ts index 01798f74..6a94b2cb 100644 --- a/packages/plugins/injection/src/index.ts +++ b/packages/plugins/injection/src/index.ts @@ -48,8 +48,11 @@ export const getInjectionPlugins: GetInternalPlugins = (arg: GetPluginsArg) => { esbuild: getEsbuildPlugin(log, context, contentsToInject), webpack: getXpackPlugin(bundler, log, context, injections, contentsToInject), rspack: getXpackPlugin(bundler, log, context, injections, contentsToInject), - rollup: getRollupPlugin(contentsToInject), - vite: { ...(getRollupPlugin(contentsToInject) as PluginOptions['vite']), enforce: 'pre' }, + rollup: getRollupPlugin(log, contentsToInject), + vite: { + ...(getRollupPlugin(log, contentsToInject) as PluginOptions['vite']), + enforce: 'pre', + }, }; // We need to handle the resolution in xpack, diff --git a/packages/plugins/injection/src/rollup.ts b/packages/plugins/injection/src/rollup.ts index b121b987..86d971b2 100644 --- a/packages/plugins/injection/src/rollup.ts +++ b/packages/plugins/injection/src/rollup.ts @@ -4,20 +4,30 @@ import { INJECTED_FILE } from '@dd/core/constants'; import { isInjectionFile } from '@dd/core/helpers/plugins'; -import type { PluginOptions } from '@dd/core/types'; +import type { Logger, PluginOptions } from '@dd/core/types'; import { InjectPosition } from '@dd/core/types'; +import path from 'path'; -import { getContentToInject } from './helpers'; +import { getContentToInject, isFileSupported, warnUnsupportedFile } from './helpers'; import type { ContentsToInject } from './types'; // Use "INJECTED_FILE" so it get flagged by isInjectionFile(). const TO_INJECT_ID = INJECTED_FILE; const TO_INJECT_SUFFIX = '?inject-proxy'; -export const getRollupPlugin = (contentsToInject: ContentsToInject): PluginOptions['rollup'] => { +export const getRollupPlugin = ( + log: Logger, + contentsToInject: ContentsToInject, +): PluginOptions['rollup'] => { return { banner(chunk) { - if (chunk.isEntry) { + if (chunk.isEntry && chunk.fileName) { + const { base, ext } = path.parse(chunk.fileName); + const isOutputSupported = isFileSupported(ext); + if (!isOutputSupported) { + warnUnsupportedFile(log, ext, base); + return ''; + } // Can be empty. return getContentToInject(contentsToInject[InjectPosition.BEFORE]); } @@ -76,7 +86,13 @@ export const getRollupPlugin = (contentsToInject: ContentsToInject): PluginOptio return null; }, footer(chunk) { - if (chunk.isEntry) { + if (chunk.isEntry && chunk.fileName) { + const { base, ext } = path.parse(chunk.fileName); + const isOutputSupported = isFileSupported(ext); + if (!isOutputSupported) { + warnUnsupportedFile(log, ext, base); + return ''; + } // Can be empty. return getContentToInject(contentsToInject[InjectPosition.AFTER]); } diff --git a/packages/plugins/injection/src/xpack.ts b/packages/plugins/injection/src/xpack.ts index 41116e9d..a97d7a24 100644 --- a/packages/plugins/injection/src/xpack.ts +++ b/packages/plugins/injection/src/xpack.ts @@ -9,7 +9,7 @@ import { InjectPosition } from '@dd/core/types'; import path from 'path'; import { PLUGIN_NAME } from './constants'; -import { getContentToInject, addInjections } from './helpers'; +import { getContentToInject, addInjections, isFileSupported, warnUnsupportedFile } from './helpers'; import type { ContentsToInject } from './types'; export const getXpackPlugin = @@ -111,6 +111,13 @@ export const getXpackPlugin = } for (const file of chunk.files) { + const { base, ext } = path.parse(file); + const isOutputSupported = isFileSupported(ext); + if (!isOutputSupported) { + warnUnsupportedFile(log, ext, base); + continue; + } + compilation.updateAsset(file, (old) => { const cached = cache.get(old);