-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathutils.ts
More file actions
248 lines (220 loc) · 9.12 KB
/
utils.ts
File metadata and controls
248 lines (220 loc) · 9.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import { consoleSandbox } from '@sentry/core';
import * as fs from 'fs';
import type { Nuxt } from 'nuxt/schema';
import * as path from 'path';
import type { SentryNuxtModuleOptions } from '../common/types';
import type { PluginMode } from './sourceMaps';
/**
* Determines whether source maps upload should be disabled in the bundler plugin.
*
* The logic follows a clear precedence:
* 1. Plugin mode check: If mode is `'release-injection-only'`, always disable upload
* (upload is handled by the build-end hook instead)
* 2. When mode is `'full'`, check user options with precedence:
* a. New option: `moduleOptions.sourcemaps.disable` (takes precedence)
* b. Deprecated option: `sourceMapsUploadOptions.enabled` (inverted, used as fallback)
* c. Default: `false` (upload enabled when mode is 'full' and no user option set)
*
* Only exported for testing.
*/
export function shouldDisableSourceMapsUpload(
moduleOptions: SentryNuxtModuleOptions,
pluginMode: PluginMode = 'release-injection-only',
): boolean {
// Step 1: If plugin mode is 'release-injection-only', always disable upload
if (pluginMode !== 'full') {
return true;
}
// Step 2: Plugin mode is 'full' - check user options
// Note: disable can be boolean or 'disable-upload' - both truthy values mean disable upload
const disableOption = moduleOptions.sourcemaps?.disable;
if (disableOption !== undefined) {
// true or 'disable-upload' -> disable upload; false -> enable upload
return disableOption !== false;
}
// Priority 2: Deprecated option
// eslint-disable-next-line deprecation/deprecation
const deprecatedEnabled = moduleOptions.sourceMapsUploadOptions?.enabled;
if (deprecatedEnabled !== undefined) {
return !deprecatedEnabled;
}
// Default: upload enabled when plugin mode is 'full'
return false;
}
/**
* Find the default SDK init file for the given type (client or server).
* The sentry.server.config file is prioritized over the instrument.server file.
*/
export function findDefaultSdkInitFile(type: 'server' | 'client', nuxt?: Nuxt): string | undefined {
const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'];
const relativePaths: string[] = [];
if (type === 'server') {
for (const ext of possibleFileExtensions) {
relativePaths.push(`sentry.${type}.config.${ext}`);
relativePaths.push(path.join('public', `instrument.${type}.${ext}`));
}
} else {
for (const ext of possibleFileExtensions) {
relativePaths.push(`sentry.${type}.config.${ext}`);
}
}
// Get layers from highest priority to lowest
const layers = [...(nuxt?.options._layers ?? [])].reverse();
for (const layer of layers) {
for (const relativePath of relativePaths) {
const fullPath = path.resolve(layer.cwd, relativePath);
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
}
// As a fallback, also check CWD (left for pure compatibility)
const cwd = process.cwd();
for (const relativePath of relativePaths) {
const fullPath = path.resolve(cwd, relativePath);
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
return undefined;
}
/**
* Extracts the filename from a node command with a path.
*/
export function getFilenameFromNodeStartCommand(nodeCommand: string): string | null {
const regex = /[^/\\]+\.[^/\\]+$/;
const match = nodeCommand.match(regex);
return match ? match[0] : null;
}
export const SENTRY_WRAPPED_ENTRY = '?sentry-query-wrapped-entry';
export const SENTRY_WRAPPED_FUNCTIONS = '?sentry-query-wrapped-functions=';
export const SENTRY_REEXPORTED_FUNCTIONS = '?sentry-query-reexported-functions=';
export const QUERY_END_INDICATOR = 'SENTRY-QUERY-END';
/**
* Strips the Sentry query part from a path.
* Example: example/path?sentry-query-wrapped-entry?sentry-query-functions-reexport=foo,SENTRY-QUERY-END -> /example/path
*
* Only exported for testing.
*/
export function removeSentryQueryFromPath(url: string): string {
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
const regex = new RegExp(`\\${SENTRY_WRAPPED_ENTRY}.*?\\${QUERY_END_INDICATOR}`);
return url.replace(regex, '');
}
/**
* Extracts and sanitizes function re-export and function wrap query parameters from a query string.
* If it is a default export, it is not considered for re-exporting.
*
* Only exported for testing.
*/
export function extractFunctionReexportQueryParameters(query: string): { wrap: string[]; reexport: string[] } {
// Regex matches the comma-separated params between the functions query
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
const wrapRegex = new RegExp(
`\\${SENTRY_WRAPPED_FUNCTIONS}(.*?)(\\${QUERY_END_INDICATOR}|\\${SENTRY_REEXPORTED_FUNCTIONS})`,
);
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor
const reexportRegex = new RegExp(`\\${SENTRY_REEXPORTED_FUNCTIONS}(.*?)(\\${QUERY_END_INDICATOR})`);
const wrapMatch = query.match(wrapRegex);
const reexportMatch = query.match(reexportRegex);
const wrap =
wrapMatch?.[1]
?.split(',')
.filter(param => param !== '')
// Sanitize, as code could be injected with another rollup plugin
.map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) || [];
const reexport =
reexportMatch?.[1]
?.split(',')
.filter(param => param !== '' && param !== 'default')
// Sanitize, as code could be injected with another rollup plugin
.map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) || [];
return { wrap, reexport };
}
/**
* Constructs a comma-separated string with all functions that need to be re-exported later from the server entry.
* It uses Rollup's `exportedBindings` to determine the functions to re-export. Functions which should be wrapped
* (e.g. serverless handlers) are wrapped by Sentry.
*/
export function constructWrappedFunctionExportQuery(
exportedBindings: Record<string, string[]> | null,
entrypointWrappedFunctions: string[],
debug?: boolean,
): string {
const functionsToExport: { wrap: string[]; reexport: string[] } = {
wrap: [],
reexport: [],
};
// `exportedBindings` can look like this: `{ '.': [ 'handler' ] }` or `{ '.': [], './firebase-gen-1.mjs': [ 'server' ] }`
// The key `.` refers to exports within the current file, while other keys show from where exports were imported first.
Object.values(exportedBindings || {}).forEach(functions =>
functions.forEach(fn => {
if (entrypointWrappedFunctions.includes(fn)) {
functionsToExport.wrap.push(fn);
} else {
functionsToExport.reexport.push(fn);
}
}),
);
if (debug && functionsToExport.wrap.length === 0) {
consoleSandbox(() =>
// eslint-disable-next-line no-console
console.warn(
"[Sentry] No functions found to wrap. In case the server needs to export async functions other than `handler` or `server`, consider adding the name(s) to Sentry's build options `sentry.experimental_entrypointWrappedFunctions` in `nuxt.config.ts`.",
),
);
}
const wrapQuery = functionsToExport.wrap.length
? `${SENTRY_WRAPPED_FUNCTIONS}${functionsToExport.wrap.join(',')}`
: '';
const reexportQuery = functionsToExport.reexport.length
? `${SENTRY_REEXPORTED_FUNCTIONS}${functionsToExport.reexport.join(',')}`
: '';
return [wrapQuery, reexportQuery].join('');
}
/**
* Constructs a code snippet with function reexports (can be used in Rollup plugins as a return value for `load()`)
*/
export function constructFunctionReExport(pathWithQuery: string, entryId: string): string {
const { wrap: wrapFunctions, reexport: reexportFunctions } = extractFunctionReexportQueryParameters(pathWithQuery);
return wrapFunctions
.reduce(
(functionsCode, currFunctionName) =>
functionsCode.concat(
`async function ${currFunctionName}_sentryWrapped(...args) {\n` +
` const res = await import(${JSON.stringify(entryId)});\n` +
` return res.${currFunctionName}.call(this, ...args);\n` +
'}\n' +
`export { ${currFunctionName}_sentryWrapped as ${currFunctionName} };\n`,
),
'',
)
.concat(
reexportFunctions.reduce(
(functionsCode, currFunctionName) =>
functionsCode.concat(`export { ${currFunctionName} } from ${JSON.stringify(entryId)};`),
'',
),
);
}
/**
* Sets up alias to work around OpenTelemetry's incomplete ESM imports.
* https://github.com/getsentry/sentry-javascript/issues/15204
*
* OpenTelemetry's @opentelemetry/resources package has incomplete imports missing
* the .js file extensions (like execAsync for machine-id detection). This causes module resolution
* errors in certain Nuxt configurations, particularly when local Nuxt modules in Nuxt 4 are present.
*
* @see https://nuxt.com/docs/guide/concepts/esm#aliasing-libraries
*/
export function addOTelCommonJSImportAlias(nuxt: Nuxt): void {
if (!nuxt.options.dev) {
return;
}
if (!nuxt.options.alias) {
nuxt.options.alias = {};
}
if (!nuxt.options.alias['@opentelemetry/resources']) {
nuxt.options.alias['@opentelemetry/resources'] = '@opentelemetry/resources/build/src/index.js';
}
}