diff --git a/.yarn/cache/@types-picomatch-npm-4.0.2-bea121c197-7b74f860c4.zip b/.yarn/cache/@types-picomatch-npm-4.0.2-bea121c197-7b74f860c4.zip new file mode 100644 index 000000000..765c9064b Binary files /dev/null and b/.yarn/cache/@types-picomatch-npm-4.0.2-bea121c197-7b74f860c4.zip differ diff --git a/LICENSES-3rdparty.csv b/LICENSES-3rdparty.csv index c60ce921e..b8d64ff11 100644 --- a/LICENSES-3rdparty.csv +++ b/LICENSES-3rdparty.csv @@ -232,6 +232,7 @@ Component,Origin,Licence,Copyright @types/mute-stream,npm,MIT,(https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mute-stream) @types/node,npm,MIT,(https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) @types/parse-json,npm,MIT,(https://www.npmjs.com/package/@types/parse-json) +@types/picomatch,npm,MIT,(https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/picomatch) @types/resolve,npm,MIT,(https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/resolve) @types/retry,npm,MIT,(https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/retry) @types/semver,npm,MIT,(https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/semver) diff --git a/packages/plugins/live-debugger/README.md b/packages/plugins/live-debugger/README.md index c367cdee3..2ccd92656 100644 --- a/packages/plugins/live-debugger/README.md +++ b/packages/plugins/live-debugger/README.md @@ -16,6 +16,7 @@ Automatically instrument JavaScript functions at build time to enable Live Debug - [metadata.version](#metadataversion) - [liveDebugger.include](#livedebuggerinclude) - [liveDebugger.exclude](#livedebuggerexclude) + - [liveDebugger.fileExtensions](#livedebuggerfileextensions) - [liveDebugger.honorSkipComments](#livedebuggerhonorskipcomments) - [liveDebugger.functionTypes](#livedebuggerfunctiontypes) - [liveDebugger.namedOnly](#livedebuggernamedonly) @@ -53,6 +54,7 @@ liveDebugger?: { enable?: boolean; include?: (string | RegExp)[]; exclude?: (string | RegExp)[]; + fileExtensions?: string[] | 'all'; honorSkipComments?: boolean; functionTypes?: FunctionKind[]; namedOnly?: boolean; @@ -127,9 +129,11 @@ If omitted, Live Debugger instrumentation still works, but browser build lookup ### liveDebugger.include -> default: `[/\.[jt]sx?$/]` +> default: `[]` (all paths) -Array of file patterns (strings or RegExp) to include for instrumentation. By default, all JavaScript and TypeScript files (`.js`, `.jsx`, `.ts`, `.tsx`) are included. +Array of file patterns to include for instrumentation. Strings are glob patterns resolved relative to the current working directory; regular expressions are matched directly against the file ID. Multiple patterns are alternatives, so a file is included when any pattern matches. + +The [`fileExtensions`](#livedebuggerfileextensions) filter is applied independently. For example, `include: ['src/**']` limits instrumentation to the `src` directory without accidentally including CSS or other non-JavaScript assets. ### liveDebugger.exclude @@ -146,6 +150,23 @@ Array of file patterns (strings or RegExp) to exclude from instrumentation. By d - Datadog browser SDK packages (`@datadog/browser-*`, when npm linked) - Datadog browser SDK source files (`browser-sdk/packages/`) +### liveDebugger.fileExtensions + +> default: `['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']` + +Non-empty array of file extensions eligible for instrumentation. Extension matching is case-insensitive and is applied in addition to `include` and `exclude`. + +Use custom extensions for file IDs containing JavaScript produced by another transform: + +```ts +liveDebugger: { + include: ['src/**'], + fileExtensions: ['.js', '.ts', '.vue'], +} +``` + +Set `fileExtensions: 'all'` to disable the extension guard. This is intended for advanced configurations where `include` fully identifies instrumentable files. + ### liveDebugger.honorSkipComments > default: `true` diff --git a/packages/plugins/live-debugger/package.json b/packages/plugins/live-debugger/package.json index f145f16d5..486a1f10d 100644 --- a/packages/plugins/live-debugger/package.json +++ b/packages/plugins/live-debugger/package.json @@ -25,13 +25,15 @@ "dependencies": { "@dd/core": "workspace:*", "@jridgewell/remapping": "2.3.5", - "chalk": "2.3.1" + "chalk": "2.3.1", + "picomatch": "4.0.3" }, "devDependencies": { "@babel/parser": "7.24.5", "@babel/traverse": "7.24.5", "@babel/types": "7.24.5", "@jridgewell/trace-mapping": "0.3.31", + "@types/picomatch": "4.0.2", "magic-string": "0.30.21", "typescript": "5.4.3" }, diff --git a/packages/plugins/live-debugger/src/constants.ts b/packages/plugins/live-debugger/src/constants.ts index 137408d06..0c12cbbb3 100644 --- a/packages/plugins/live-debugger/src/constants.ts +++ b/packages/plugins/live-debugger/src/constants.ts @@ -9,3 +9,14 @@ export const PLUGIN_NAME: PluginName = 'datadog-live-debugger-plugin' as const; // Skip instrumentation comment export const SKIP_INSTRUMENTATION_COMMENT = '@dd-no-instrumentation'; + +export const DEFAULT_FILE_EXTENSIONS = [ + '.js', + '.jsx', + '.ts', + '.tsx', + '.mjs', + '.cjs', + '.mts', + '.cts', +] as const; diff --git a/packages/plugins/live-debugger/src/filter.ts b/packages/plugins/live-debugger/src/filter.ts new file mode 100644 index 000000000..4c2ac0c71 --- /dev/null +++ b/packages/plugins/live-debugger/src/filter.ts @@ -0,0 +1,94 @@ +// 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 path from 'path'; +import picomatch from 'picomatch'; + +import type { FileExtensions, LiveDebuggerOptionsWithDefaults } from './types'; + +type Pattern = string | RegExp; +type Matcher = (id: string) => boolean; + +const BACKSLASH_PATTERN = /\\/g; +const WINDOWS_ABSOLUTE_PATH_PATTERN = /^(?:[A-Z]:[\\/]|\\\\)/i; +const ID_SUFFIX_PATTERN = /[?#]/; + +const normalizePath = (filePath: string): string => filePath.replace(BACKSLASH_PATTERN, '/'); + +const resolveGlob = (glob: string): string => { + if ( + glob.startsWith('**') || + path.isAbsolute(glob) || + WINDOWS_ABSOLUTE_PATH_PATTERN.test(glob) + ) { + return normalizePath(glob); + } + + const absoluteGlob = path.resolve(glob); + return normalizePath(absoluteGlob); +}; + +const createPatternMatcher = (pattern: Pattern): Matcher => { + if (pattern instanceof RegExp) { + return (id) => { + const normalizedId = normalizePath(id); + const matches = pattern.test(normalizedId); + pattern.lastIndex = 0; + return matches; + }; + } + + const glob = resolveGlob(pattern); + const matchesGlob = picomatch(glob, { dot: true }); + return (id) => { + const normalizedId = normalizePath(id); + return matchesGlob(normalizedId); + }; +}; + +const createPatternFilter = (include: Pattern[], exclude: Pattern[]): Matcher => { + const includeMatchers = include.map(createPatternMatcher); + const excludeMatchers = exclude.map(createPatternMatcher); + + return (id) => { + if (excludeMatchers.some((matches) => matches(id))) { + return false; + } + + return includeMatchers.length === 0 || includeMatchers.some((matches) => matches(id)); + }; +}; + +const createFileExtensionFilter = (fileExtensions: FileExtensions): Matcher => { + if (fileExtensions === 'all') { + return () => true; + } + + const normalizedExtensions = fileExtensions.map((extension) => extension.toLowerCase()); + return (id) => { + const [filePath] = id.split(ID_SUFFIX_PATTERN); + const normalizedFilePath = filePath.toLowerCase(); + return normalizedExtensions.some((extension) => normalizedFilePath.endsWith(extension)); + }; +}; + +export const createFileFilter = ( + options: Pick, +): Matcher => { + const matchesPatterns = createPatternFilter(options.include, options.exclude); + const matchesFileExtension = createFileExtensionFilter(options.fileExtensions); + + return (id) => matchesFileExtension(id) && matchesPatterns(id); +}; + +const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +export const createFileExtensionPattern = (fileExtensions: FileExtensions): RegExp | undefined => { + if (fileExtensions === 'all') { + return undefined; + } + + const escapedExtensions = fileExtensions.map(escapeRegExp); + return new RegExp(`(?:${escapedExtensions.join('|')})(?:[?#].*)?$`, 'i'); +}; diff --git a/packages/plugins/live-debugger/src/index.test.ts b/packages/plugins/live-debugger/src/index.test.ts index 2bbd7f5fa..f9d7c4d14 100644 --- a/packages/plugins/live-debugger/src/index.test.ts +++ b/packages/plugins/live-debugger/src/index.test.ts @@ -6,7 +6,7 @@ import { InjectPosition } from '@dd/core/types'; import { getContextMock, getGetPluginsArg } from '@dd/tests/_jest/helpers/mocks'; import type { UnpluginBuildContext, UnpluginContext } from 'unplugin'; -import { PLUGIN_NAME } from './constants'; +import { DEFAULT_FILE_EXTENSIONS, PLUGIN_NAME } from './constants'; import { getLiveDebuggerPlugin, getPlugins } from './index'; import { getRuntimeBootstrap } from './runtime-bootstrap'; import type { LiveDebuggerOptionsWithDefaults } from './types'; @@ -15,8 +15,9 @@ const makeOptions = ( overrides: Partial = {}, ): LiveDebuggerOptionsWithDefaults => ({ version: '1.0.0', - include: [/\.[jt]sx?$/], + include: [], exclude: [/\/node_modules\//], + fileExtensions: [...DEFAULT_FILE_EXTENSIONS], honorSkipComments: false, functionTypes: undefined, namedOnly: false, @@ -159,7 +160,7 @@ describe('getLiveDebuggerPlugin', () => { const handler = getHandler(makeOptions({ include: [/\.tsx?$/] })); const code = 'function f() { return 1; }'; - expect(handler(code, '/src/style.css')).toEqual({ code }); + expect(handler(code, '/src/utils.js')).toEqual({ code }); }); it('should process files matching an include pattern', () => { @@ -169,12 +170,63 @@ describe('getLiveDebuggerPlugin', () => { expect(handler(code, '/src/utils.ts').code).toContain('$dd_probes'); }); - it('should skip include filtering when include array is empty', () => { + it.each(DEFAULT_FILE_EXTENSIONS)( + 'should process the default %s file extension', + (extension) => { + const handler = getHandler(makeOptions({ include: [], exclude: [] })); + const code = 'function f() { return 1; }'; + + expect(handler(code, `/src/utils${extension}`).code).toContain('$dd_probes'); + }, + ); + + it('should apply the extension filter independently of a broad include glob', () => { + const handler = getHandler( + makeOptions({ + include: ['**/src/**'], + exclude: [], + }), + ); + const code = 'function f() { return 1; }'; + + expect(handler(code, '/project/src/styles.css')).toEqual({ code }); + expect(handler(code, '/project/src/utils.ts').code).toContain('$dd_probes'); + }); + + it('should process a custom file extension', () => { + const handler = getHandler( + makeOptions({ + include: ['**/src/**'], + exclude: [], + fileExtensions: ['.vue'], + }), + ); + const code = 'function f() { return 1; }'; + + expect(handler(code, '/project/src/component.vue').code).toContain('$dd_probes'); + expect(handler(code, '/project/src/utils.ts')).toEqual({ code }); + }); + + it('should process any file extension when configured with "all"', () => { + const handler = getHandler( + makeOptions({ + include: ['**/src/**'], + exclude: [], + fileExtensions: 'all', + }), + ); + const code = 'function f() { return 1; }'; + + expect(handler(code, '/project/src/anything.xyz').code).toContain('$dd_probes'); + }); + + it('should match file extensions case-insensitively before an ID query', () => { const handler = getHandler(makeOptions({ include: [], exclude: [] })); const code = 'function f() { return 1; }'; - // With no include patterns, all file types pass through - expect(handler(code, '/src/anything.xyz').code).toContain('$dd_probes'); + expect(handler(code, '/src/component.TSX?loader=transformed').code).toContain( + '$dd_probes', + ); }); it('should exclude files matching an exclude pattern even if included', () => { @@ -189,25 +241,27 @@ describe('getLiveDebuggerPlugin', () => { expect(handler(code, '/node_modules/dep/index.ts')).toEqual({ code }); }); - it('should support string include patterns', () => { - const handler = getHandler(makeOptions({ include: ['src/'], exclude: [] })); + it('should support glob include patterns', () => { + const handler = getHandler(makeOptions({ include: ['src/**'], exclude: [] })); const code = 'function f() { return 1; }'; + const projectRoot = process.cwd(); - expect(handler(code, '/project/src/utils.ts').code).toContain('$dd_probes'); - expect(handler(code, '/project/vendor/lib.ts')).toEqual({ code }); + expect(handler(code, `${projectRoot}/src/utils.ts`).code).toContain('$dd_probes'); + expect(handler(code, `${projectRoot}/vendor/lib.ts`)).toEqual({ code }); }); - it('should support string exclude patterns', () => { + it('should support glob exclude patterns', () => { const handler = getHandler( makeOptions({ include: [], - exclude: ['vendor/'], + exclude: ['vendor/**'], }), ); const code = 'function f() { return 1; }'; + const projectRoot = process.cwd(); - expect(handler(code, '/project/src/utils.ts').code).toContain('$dd_probes'); - expect(handler(code, '/project/vendor/lib.ts')).toEqual({ code }); + expect(handler(code, `${projectRoot}/src/utils.ts`).code).toContain('$dd_probes'); + expect(handler(code, `${projectRoot}/vendor/lib.ts`)).toEqual({ code }); }); }); diff --git a/packages/plugins/live-debugger/src/index.ts b/packages/plugins/live-debugger/src/index.ts index 88f90a3e4..cbdbe3f77 100644 --- a/packages/plugins/live-debugger/src/index.ts +++ b/packages/plugins/live-debugger/src/index.ts @@ -10,6 +10,7 @@ import type { SourceMap } from 'magic-string'; import type { SourceMapCompact, UnpluginBuildContext } from 'unplugin'; import { CONFIG_KEY, PLUGIN_NAME } from './constants'; +import { createFileExtensionPattern, createFileFilter } from './filter'; import { getRuntimeBootstrap } from './runtime-bootstrap'; import { transformCode } from './transform'; import type { LiveDebuggerOptions, LiveDebuggerOptionsWithDefaults } from './types'; @@ -29,6 +30,9 @@ export const getLiveDebuggerPlugin = ( context: GlobalContext, ): PluginOptions => { const log = context.getLogger(PLUGIN_NAME); + const shouldInstrumentFile = createFileFilter(pluginOptions); + const fileExtensionPattern = createFileExtensionPattern(pluginOptions.fileExtensions); + const nativeInclude = fileExtensionPattern ? [fileExtensionPattern] : pluginOptions.include; let instrumentedCount = 0; let failedCount = 0; @@ -48,29 +52,16 @@ export const getLiveDebuggerPlugin = ( transform: { filter: { id: { - include: pluginOptions.include, + include: nativeInclude, exclude: pluginOptions.exclude, }, }, handler(code, id) { - // Enforce include/exclude patterns at runtime because unplugin's + // Enforce all file filters at runtime because unplugin's // native filter is not applied in bundler child compilations // (e.g., web worker bundles in rspack/webpack). - if (pluginOptions.include.length > 0) { - const included = pluginOptions.include.some((pattern) => - typeof pattern === 'string' ? id.includes(pattern) : pattern.test(id), - ); - if (!included) { - return { code }; - } - } - - for (const pattern of pluginOptions.exclude) { - const excluded = - typeof pattern === 'string' ? id.includes(pattern) : pattern.test(id); - if (excluded) { - return { code }; - } + if (!shouldInstrumentFile(id)) { + return { code }; } if (totalFilesWithFunctions >= DD_LD_LIMIT) { diff --git a/packages/plugins/live-debugger/src/types.ts b/packages/plugins/live-debugger/src/types.ts index f6d69074a..f29315dfb 100644 --- a/packages/plugins/live-debugger/src/types.ts +++ b/packages/plugins/live-debugger/src/types.ts @@ -12,11 +12,13 @@ export const VALID_FUNCTION_KINDS = [ ] as const; export type FunctionKind = (typeof VALID_FUNCTION_KINDS)[number]; +export type FileExtensions = string[] | 'all'; export type LiveDebuggerOptions = { enable?: boolean; include?: (string | RegExp)[]; exclude?: (string | RegExp)[]; + fileExtensions?: FileExtensions; honorSkipComments?: boolean; functionTypes?: FunctionKind[]; namedOnly?: boolean; @@ -26,6 +28,7 @@ export type LiveDebuggerOptionsWithDefaults = { version: string | undefined; include: (string | RegExp)[]; exclude: (string | RegExp)[]; + fileExtensions: FileExtensions; honorSkipComments: boolean; functionTypes: FunctionKind[] | undefined; namedOnly: boolean; diff --git a/packages/plugins/live-debugger/src/validate.test.ts b/packages/plugins/live-debugger/src/validate.test.ts index 09a282a58..01ef6d4ac 100644 --- a/packages/plugins/live-debugger/src/validate.test.ts +++ b/packages/plugins/live-debugger/src/validate.test.ts @@ -5,7 +5,7 @@ import type { BuildMetadata, Options } from '@dd/core/types'; import { getMockLogger } from '@dd/tests/_jest/helpers/mocks'; -import { PLUGIN_NAME } from './constants'; +import { DEFAULT_FILE_EXTENSIONS, PLUGIN_NAME } from './constants'; import type { LiveDebuggerOptions, LiveDebuggerOptionsWithDefaults } from './types'; import { validateOptions } from './validate'; @@ -34,8 +34,9 @@ describe('validateOptions', () => { input: makeConfig(undefined), expected: { version: undefined, - include: [/\.[jt]sx?$/], + include: [], exclude: expect.arrayContaining([/\/node_modules\//]), + fileExtensions: [...DEFAULT_FILE_EXTENSIONS], honorSkipComments: true, functionTypes: undefined, namedOnly: false, @@ -46,8 +47,9 @@ describe('validateOptions', () => { input: makeConfig({}), expected: { version: undefined, - include: [/\.[jt]sx?$/], + include: [], exclude: expect.arrayContaining([/\/node_modules\//]), + fileExtensions: [...DEFAULT_FILE_EXTENSIONS], honorSkipComments: true, functionTypes: undefined, namedOnly: false, @@ -58,8 +60,9 @@ describe('validateOptions', () => { input: makeConfig({}, { version: '1.0.0' }), expected: { version: '1.0.0', - include: [/\.[jt]sx?$/], + include: [], exclude: expect.arrayContaining([/\/node_modules\//]), + fileExtensions: [...DEFAULT_FILE_EXTENSIONS], honorSkipComments: true, functionTypes: undefined, namedOnly: false, @@ -119,6 +122,21 @@ describe('validateOptions', () => { input: makeConfig({ exclude: [/node_modules/] }), expected: expect.objectContaining({ exclude: [/node_modules/] }), }, + { + description: 'accept custom file extensions', + input: makeConfig({ fileExtensions: ['.js', '.vue'] }), + expected: expect.objectContaining({ fileExtensions: ['.js', '.vue'] }), + }, + { + description: 'normalize custom file extensions to lowercase and remove duplicates', + input: makeConfig({ fileExtensions: ['.JS', '.Vue', '.js', '.VUE'] }), + expected: expect.objectContaining({ fileExtensions: ['.js', '.vue'] }), + }, + { + description: 'accept all file extensions', + input: makeConfig({ fileExtensions: 'all' }), + expected: expect.objectContaining({ fileExtensions: 'all' }), + }, { description: 'accept honorSkipComments as true', input: makeConfig({ honorSkipComments: true }), @@ -240,6 +258,43 @@ describe('validateOptions', () => { }); }); + describe('invalid fileExtensions', () => { + const cases = [ + { + description: 'reject fileExtensions when not an array or "all"', + input: makeInvalidConfig({ fileExtensions: '.ts' }), + errorPattern: /fileExtensions.*must be an array of strings or "all"/, + }, + { + description: 'reject an empty file extension array', + input: makeInvalidConfig({ fileExtensions: [] }), + errorPattern: /fileExtensions.*must contain at least one extension/, + }, + { + description: 'reject a non-string file extension', + input: makeInvalidConfig({ fileExtensions: ['.ts', 42] }), + errorPattern: /fileExtensions.*values must begin with/, + }, + { + description: 'reject a file extension without a leading dot', + input: makeInvalidConfig({ fileExtensions: ['ts'] }), + errorPattern: /fileExtensions.*values must begin with/, + }, + { + description: 'reject a file extension containing a query separator', + input: makeInvalidConfig({ fileExtensions: ['.vue?script'] }), + errorPattern: /fileExtensions.*values must begin with/, + }, + ]; + + test.each(cases)('should $description', ({ input, errorPattern }) => { + expect(() => validateOptions(input, mockLogger)).toThrow( + `Invalid configuration for ${PLUGIN_NAME}.`, + ); + expect(mockError).toHaveBeenCalledWith(expect.stringMatching(errorPattern)); + }); + }); + describe('invalid honorSkipComments', () => { const cases = [ { @@ -311,6 +366,7 @@ describe('validateOptions', () => { const input = makeInvalidConfig({ include: 'bad', exclude: 'bad', + fileExtensions: 'bad', honorSkipComments: 42, functionTypes: 'bad', namedOnly: 42, @@ -323,6 +379,7 @@ describe('validateOptions', () => { const errorMessage = mockError.mock.calls[0][0]; expect(errorMessage).toMatch(/include/); expect(errorMessage).toMatch(/exclude/); + expect(errorMessage).toMatch(/fileExtensions/); expect(errorMessage).toMatch(/honorSkipComments/); expect(errorMessage).toMatch(/functionTypes/); expect(errorMessage).toMatch(/namedOnly/); diff --git a/packages/plugins/live-debugger/src/validate.ts b/packages/plugins/live-debugger/src/validate.ts index eb8020792..a0024fe50 100644 --- a/packages/plugins/live-debugger/src/validate.ts +++ b/packages/plugins/live-debugger/src/validate.ts @@ -5,11 +5,17 @@ import type { Logger, Options } from '@dd/core/types'; import chalk from 'chalk'; -import { CONFIG_KEY, PLUGIN_NAME } from './constants'; +import { CONFIG_KEY, DEFAULT_FILE_EXTENSIONS, PLUGIN_NAME } from './constants'; import type { LiveDebuggerOptions, LiveDebuggerOptionsWithDefaults } from './types'; import { VALID_FUNCTION_KINDS } from './types'; const red = chalk.bold.red; +const INVALID_FILE_EXTENSION_PATTERN = /[\\/?#]/; + +const normalizeFileExtensions = (fileExtensions: readonly string[]): string[] => { + const lowercaseExtensions = fileExtensions.map((extension) => extension.toLowerCase()); + return [...new Set(lowercaseExtensions)]; +}; export const validateOptions = (config: Options, log: Logger): LiveDebuggerOptionsWithDefaults => { const pluginConfig: LiveDebuggerOptions = config[CONFIG_KEY] || {}; @@ -44,6 +50,29 @@ export const validateOptions = (config: Options, log: Logger): LiveDebuggerOptio } } + // Validate fileExtensions option + if (pluginConfig.fileExtensions !== undefined && pluginConfig.fileExtensions !== 'all') { + if (!Array.isArray(pluginConfig.fileExtensions)) { + errors.push(`${red('fileExtensions')} must be an array of strings or "all"`); + } else if (pluginConfig.fileExtensions.length === 0) { + errors.push(`${red('fileExtensions')} must contain at least one extension`); + } else { + for (const extension of pluginConfig.fileExtensions) { + if ( + typeof extension !== 'string' || + extension.length < 2 || + !extension.startsWith('.') || + INVALID_FILE_EXTENSION_PATTERN.test(extension) + ) { + errors.push( + `${red('fileExtensions')} values must begin with "." and contain no path or query separators`, + ); + break; + } + } + } + } + // Validate honorSkipComments option if ( pluginConfig.honorSkipComments !== undefined && @@ -79,10 +108,16 @@ export const validateOptions = (config: Options, log: Logger): LiveDebuggerOptio throw new Error(`Invalid configuration for ${PLUGIN_NAME}.`); } + const configuredFileExtensions = pluginConfig.fileExtensions ?? DEFAULT_FILE_EXTENSIONS; + const fileExtensions = + configuredFileExtensions === 'all' + ? configuredFileExtensions + : normalizeFileExtensions(configuredFileExtensions); + // Build the final configuration with defaults return { version: metadataVersion, - include: pluginConfig.include || [/\.[jt]sx?$/], // .js, .jsx, .ts, .tsx + include: pluginConfig.include ?? [], exclude: pluginConfig.exclude || [ /\/node_modules\//, /\.min\.js$/, @@ -94,6 +129,7 @@ export const validateOptions = (config: Options, log: Logger): LiveDebuggerOptio /@datadog\/browser-/, // Datadog browser SDK packages (when npm linked) /browser-sdk\/packages\//, // Datadog browser SDK source files ], + fileExtensions, honorSkipComments: pluginConfig.honorSkipComments ?? true, functionTypes: pluginConfig.functionTypes, namedOnly: pluginConfig.namedOnly ?? false, diff --git a/packages/published/esbuild-plugin/package.json b/packages/published/esbuild-plugin/package.json index 19d645369..309f40de1 100644 --- a/packages/published/esbuild-plugin/package.json +++ b/packages/published/esbuild-plugin/package.json @@ -62,6 +62,7 @@ "magic-string": "0.30.21", "outdent": "0.8.0", "p-queue": "6.6.2", + "picomatch": "4.0.3", "pretty-bytes": "5.6.0", "rollup": "4.45.1", "simple-git": "3.36.0", diff --git a/packages/published/rollup-plugin/package.json b/packages/published/rollup-plugin/package.json index db71afe0e..35c6a597c 100644 --- a/packages/published/rollup-plugin/package.json +++ b/packages/published/rollup-plugin/package.json @@ -65,6 +65,7 @@ "magic-string": "0.30.21", "outdent": "0.8.0", "p-queue": "6.6.2", + "picomatch": "4.0.3", "pretty-bytes": "5.6.0", "rollup": "4.45.1", "simple-git": "3.36.0", diff --git a/packages/published/rspack-plugin/package.json b/packages/published/rspack-plugin/package.json index d4e92ecba..b4e286404 100644 --- a/packages/published/rspack-plugin/package.json +++ b/packages/published/rspack-plugin/package.json @@ -62,6 +62,7 @@ "magic-string": "0.30.21", "outdent": "0.8.0", "p-queue": "6.6.2", + "picomatch": "4.0.3", "pretty-bytes": "5.6.0", "rollup": "4.45.1", "simple-git": "3.36.0", diff --git a/packages/published/vite-plugin/package.json b/packages/published/vite-plugin/package.json index 1149aae11..29c049967 100644 --- a/packages/published/vite-plugin/package.json +++ b/packages/published/vite-plugin/package.json @@ -62,6 +62,7 @@ "magic-string": "0.30.21", "outdent": "0.8.0", "p-queue": "6.6.2", + "picomatch": "4.0.3", "pretty-bytes": "5.6.0", "rollup": "4.45.1", "simple-git": "3.36.0", diff --git a/packages/published/webpack-plugin/package.json b/packages/published/webpack-plugin/package.json index 8faf9cb9a..cfe94ef31 100644 --- a/packages/published/webpack-plugin/package.json +++ b/packages/published/webpack-plugin/package.json @@ -62,6 +62,7 @@ "magic-string": "0.30.21", "outdent": "0.8.0", "p-queue": "6.6.2", + "picomatch": "4.0.3", "pretty-bytes": "5.6.0", "rollup": "4.45.1", "simple-git": "3.36.0", diff --git a/yarn.lock b/yarn.lock index 03495766f..7299a768d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1708,6 +1708,7 @@ __metadata: magic-string: "npm:0.30.21" outdent: "npm:0.8.0" p-queue: "npm:6.6.2" + picomatch: "npm:4.0.3" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" rollup-plugin-esbuild: "npm:6.1.1" @@ -1766,6 +1767,7 @@ __metadata: magic-string: "npm:0.30.21" outdent: "npm:0.8.0" p-queue: "npm:6.6.2" + picomatch: "npm:4.0.3" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" rollup-plugin-esbuild: "npm:6.1.1" @@ -1817,6 +1819,7 @@ __metadata: magic-string: "npm:0.30.21" outdent: "npm:0.8.0" p-queue: "npm:6.6.2" + picomatch: "npm:4.0.3" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" rollup-plugin-esbuild: "npm:6.1.1" @@ -1871,6 +1874,7 @@ __metadata: magic-string: "npm:0.30.21" outdent: "npm:0.8.0" p-queue: "npm:6.6.2" + picomatch: "npm:4.0.3" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" rollup-plugin-esbuild: "npm:6.1.1" @@ -1922,6 +1926,7 @@ __metadata: magic-string: "npm:0.30.21" outdent: "npm:0.8.0" p-queue: "npm:6.6.2" + picomatch: "npm:4.0.3" pretty-bytes: "npm:5.6.0" rollup: "npm:4.45.1" rollup-plugin-esbuild: "npm:6.1.1" @@ -2107,8 +2112,10 @@ __metadata: "@dd/core": "workspace:*" "@jridgewell/remapping": "npm:2.3.5" "@jridgewell/trace-mapping": "npm:0.3.31" + "@types/picomatch": "npm:4.0.2" chalk: "npm:2.3.1" magic-string: "npm:0.30.21" + picomatch: "npm:4.0.3" typescript: "npm:5.4.3" peerDependencies: "@babel/parser": ^7.24.5 @@ -4186,6 +4193,13 @@ __metadata: languageName: node linkType: hard +"@types/picomatch@npm:4.0.2": + version: 4.0.2 + resolution: "@types/picomatch@npm:4.0.2" + checksum: 10/7b74f860c4c2bf30f7952254717df2dd78441b0add79858dffe7f6a30c9dd6da6c3d7769d2a9f0c499bc5ffe820c629bffc7c5128948febfc2b935800169b1c3 + languageName: node + linkType: hard + "@types/resolve@npm:1.20.2": version: 1.20.2 resolution: "@types/resolve@npm:1.20.2" @@ -9502,6 +9516,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:4.0.3, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -9516,13 +9537,6 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 - languageName: node - linkType: hard - "pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7"