Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/plugins/injection/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
17 changes: 8 additions & 9 deletions packages/plugins/injection/src/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
})
Expand Down
20 changes: 18 additions & 2 deletions packages/plugins/injection/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> => {
Expand Down Expand Up @@ -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)}).`);
};
30 changes: 24 additions & 6 deletions packages/plugins/injection/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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())));
Expand All @@ -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;
}
}
});
});
});

Expand Down Expand Up @@ -295,7 +313,7 @@ describe('Injection Plugin', () => {
}[] = [
{
name: 'Easy build without injections',
entry: { main: easyProjectEntry },
entry: { main: easyProjectWithCSSEntry },
positions: [],
injections: [[], 0],
expectations: easyWithoutInjections,
Expand All @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions packages/plugins/injection/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 21 additions & 5 deletions packages/plugins/injection/src/rollup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand Down Expand Up @@ -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]);
}
Expand Down
9 changes: 8 additions & 1 deletion packages/plugins/injection/src/xpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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);

Expand Down