Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(nuxt): Add entrypointWrappedFunctions to define async wrapped server functions#14104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
f2f75d76a29eba9ed9471b8a73ca62e95b2File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
| import { consoleSandbox, flatten } from '@sentry/utils'; | ||
| /** | ||
| * Find the default SDK init file for the given type (client or server). | ||
| @@ -26,7 +27,8 @@ export function findDefaultSdkInitFile(type: 'server' | 'client'): string | unde | ||
| } | ||
| export const SENTRY_WRAPPED_ENTRY = '?sentry-query-wrapped-entry'; | ||
| export const SENTRY_FUNCTIONS_REEXPORT = '?sentry-query-functions-reexport='; | ||
| 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'; | ||
| /** | ||
| @@ -42,42 +44,110 @@ export function removeSentryQueryFromPath(url: string): string { | ||
| } | ||
| /** | ||
| * Extracts and sanitizes function re-export query parameters from a query string. | ||
| * If it is a default export, it is not considered for re-exporting. This function is mostly relevant for re-exporting | ||
| * serverless `handler` functions. | ||
| * 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): string[] { | ||
| 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 regex = new RegExp(`\\${SENTRY_FUNCTIONS_REEXPORT}(.*?)\\${QUERY_END_INDICATOR}`); | ||
| const match = query.match(regex); | ||
| return match && match[1] | ||
| ? match[1] | ||
| .split(',') | ||
| .filter(param => param !== '') | ||
| // Sanitize, as code could be injected with another rollup plugin | ||
| .map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) | ||
| : []; | ||
| 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 && wrapMatch[1] | ||
| ? wrapMatch[1] | ||
| .split(',') | ||
| .filter(param => param !== '') | ||
| // Sanitize, as code could be injected with another rollup plugin | ||
| .map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) | ||
s1gr1d marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| : []; | ||
| const reexport = | ||
| reexportMatch && reexportMatch[1] | ||
| ? reexportMatch[1] | ||
| .split(',') | ||
| .filter(param => param !== '' && param !== 'default') | ||
| // Sanitize, as code could be injected with another rollup plugin | ||
| .map((str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) | ||
s1gr1d marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| : []; | ||
| 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 { | ||
| // `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. | ||
| const functionsToExport = flatten(Object.values(exportedBindings || {})).reduce( | ||
| (functions, currFunctionName) => { | ||
| if (entrypointWrappedFunctions.includes(currFunctionName)) { | ||
| functions.wrap.push(currFunctionName); | ||
| } else { | ||
| functions.reexport.push(currFunctionName); | ||
| } | ||
| return functions; | ||
| }, | ||
| { wrap: [], reexport: [] } as { wrap: string[]; reexport: string[] }, | ||
| ); | ||
| 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.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) | ||
| * 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 functionNames = extractFunctionReexportQueryParameters(pathWithQuery); | ||
| return functionNames.reduce( | ||
| (functionsCode, currFunctionName) => | ||
| functionsCode.concat( | ||
| 'async function reExport(...args) {\n' + | ||
| ` const res = await import(${JSON.stringify(entryId)});\n` + | ||
| ` return res.${currFunctionName}.call(this, ...args);\n` + | ||
| '}\n' + | ||
| `export { reExport as ${currFunctionName} };\n`, | ||
| 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)};`), | ||
| '', | ||
| ), | ||
| '', | ||
| ); | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.