@sentry/tanstackstart-react 10.53.1 → 10.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/build/cjs/client/index.js +5 -26
- package/build/cjs/client/index.js.map +1 -1
- package/build/cjs/client/sdk.js +12 -9
- package/build/cjs/client/sdk.js.map +1 -1
- package/build/cjs/client/tunnelRoute.js +2 -13
- package/build/cjs/client/tunnelRoute.js.map +1 -1
- package/build/cjs/index.server.js +1 -0
- package/build/cjs/index.server.js.map +1 -1
- package/build/cjs/server/globalMiddleware.js +38 -22
- package/build/cjs/server/globalMiddleware.js.map +1 -1
- package/build/cjs/server/index.js +16 -29
- package/build/cjs/server/index.js.map +1 -1
- package/build/cjs/server/middleware.js +10 -66
- package/build/cjs/server/middleware.js.map +1 -1
- package/build/cjs/server/routeParametrization.js +47 -0
- package/build/cjs/server/routeParametrization.js.map +1 -0
- package/build/cjs/server/sdk.js +9 -7
- package/build/cjs/server/sdk.js.map +1 -1
- package/build/cjs/server/tunnelRoute.js +11 -31
- package/build/cjs/server/tunnelRoute.js.map +1 -1
- package/build/cjs/server/utils.js +4 -21
- package/build/cjs/server/utils.js.map +1 -1
- package/build/cjs/server/wrapFetchWithSentry.js +88 -44
- package/build/cjs/server/wrapFetchWithSentry.js.map +1 -1
- package/build/cjs/vite/autoInstrumentMiddleware.js +15 -97
- package/build/cjs/vite/autoInstrumentMiddleware.js.map +1 -1
- package/build/cjs/vite/routePatterns.js +60 -0
- package/build/cjs/vite/routePatterns.js.map +1 -0
- package/build/cjs/vite/sentryTanstackStart.js +8 -44
- package/build/cjs/vite/sentryTanstackStart.js.map +1 -1
- package/build/cjs/vite/sourceMaps.js +34 -75
- package/build/cjs/vite/sourceMaps.js.map +1 -1
- package/build/cjs/vite/tunnelRoute.js +30 -63
- package/build/cjs/vite/tunnelRoute.js.map +1 -1
- package/build/esm/client/index.js +5 -26
- package/build/esm/client/index.js.map +1 -1
- package/build/esm/client/sdk.js +12 -9
- package/build/esm/client/sdk.js.map +1 -1
- package/build/esm/client/tunnelRoute.js +2 -13
- package/build/esm/client/tunnelRoute.js.map +1 -1
- package/build/esm/index.server.js +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/server/globalMiddleware.js +39 -23
- package/build/esm/server/globalMiddleware.js.map +1 -1
- package/build/esm/server/index.js +16 -30
- package/build/esm/server/index.js.map +1 -1
- package/build/esm/server/middleware.js +10 -66
- package/build/esm/server/middleware.js.map +1 -1
- package/build/esm/server/routeParametrization.js +44 -0
- package/build/esm/server/routeParametrization.js.map +1 -0
- package/build/esm/server/sdk.js +9 -7
- package/build/esm/server/sdk.js.map +1 -1
- package/build/esm/server/tunnelRoute.js +11 -31
- package/build/esm/server/tunnelRoute.js.map +1 -1
- package/build/esm/server/utils.js +5 -21
- package/build/esm/server/utils.js.map +1 -1
- package/build/esm/server/wrapFetchWithSentry.js +90 -46
- package/build/esm/server/wrapFetchWithSentry.js.map +1 -1
- package/build/esm/vite/autoInstrumentMiddleware.js +15 -97
- package/build/esm/vite/autoInstrumentMiddleware.js.map +1 -1
- package/build/esm/vite/routePatterns.js +57 -0
- package/build/esm/vite/routePatterns.js.map +1 -0
- package/build/esm/vite/sentryTanstackStart.js +8 -44
- package/build/esm/vite/sentryTanstackStart.js.map +1 -1
- package/build/esm/vite/sourceMaps.js +34 -75
- package/build/esm/vite/sourceMaps.js.map +1 -1
- package/build/esm/vite/tunnelRoute.js +30 -63
- package/build/esm/vite/tunnelRoute.js.map +1 -1
- package/build/types/client/sdk.d.ts.map +1 -1
- package/build/types/index.types.d.ts +1 -0
- package/build/types/index.types.d.ts.map +1 -1
- package/build/types/server/globalMiddleware.d.ts.map +1 -1
- package/build/types/server/index.d.ts +5 -0
- package/build/types/server/index.d.ts.map +1 -1
- package/build/types/server/routeParametrization.d.ts +12 -0
- package/build/types/server/routeParametrization.d.ts.map +1 -0
- package/build/types/server/sdk.d.ts.map +1 -1
- package/build/types/server/utils.d.ts +0 -9
- package/build/types/server/utils.d.ts.map +1 -1
- package/build/types/server/wrapFetchWithSentry.d.ts.map +1 -1
- package/build/types/vite/routePatterns.d.ts +18 -0
- package/build/types/vite/routePatterns.d.ts.map +1 -0
- package/build/types/vite/sentryTanstackStart.d.ts +17 -0
- package/build/types/vite/sentryTanstackStart.d.ts.map +1 -1
- package/build/types/vite/sourceMaps.d.ts +4 -4
- package/build/types/vite/sourceMaps.d.ts.map +1 -1
- package/build/types-ts3.8/index.types.d.ts +1 -0
- package/build/types-ts3.8/server/index.d.ts +5 -0
- package/build/types-ts3.8/server/routeParametrization.d.ts +12 -0
- package/build/types-ts3.8/server/utils.d.ts +0 -9
- package/build/types-ts3.8/vite/routePatterns.d.ts +18 -0
- package/build/types-ts3.8/vite/sentryTanstackStart.d.ts +17 -0
- package/build/types-ts3.8/vite/sourceMaps.d.ts +4 -4
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routeParametrization.js","sources":["../../../src/server/routeParametrization.ts"],"sourcesContent":["import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';\nimport {\n escapeStringForRegex,\n getActiveSpan,\n getCurrentScope,\n getRootSpan,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n updateSpanName,\n} from '@sentry/core';\n\nfunction patternToRegex(pattern: string): RegExp {\n const segments = pattern\n .split('/')\n .map(segment => {\n if (segment.startsWith('$')) {\n return '[^/]+';\n }\n return escapeStringForRegex(segment);\n })\n .join('/');\n return new RegExp(`^${segments}$`);\n}\n\n/**\n * Matches a URL pathname against a list of TanStack Start route patterns.\n * Patterns use `$param` syntax for dynamic segments (e.g., `/users/$id`).\n *\n * Patterns are expected to be pre-sorted by specificity (more segments first, static before dynamic).\n */\nexport function matchUrlToRoutePattern(pathname: string, patterns: string[]): string | undefined {\n const normalizedPathname = pathname.length > 1 ? pathname.replace(/\\/$/, '') : pathname;\n for (const pattern of patterns) {\n if (patternToRegex(pattern).test(normalizedPathname)) {\n return pattern;\n }\n }\n return undefined;\n}\n\n/**\n * Updates the active root span with a parametrized route name.\n */\nexport function updateSpanWithRouteParametrization(method: string, pathname: string, patterns: string[]): void {\n const matchedPattern = matchUrlToRoutePattern(pathname, patterns);\n if (!matchedPattern) {\n return;\n }\n\n const activeSpan = getActiveSpan();\n if (!activeSpan) {\n return;\n }\n\n const rootSpan = getRootSpan(activeSpan);\n const rootSpanData = spanToJSON(rootSpan).data;\n if (rootSpanData?.[ATTR_HTTP_ROUTE]) {\n return;\n }\n\n const transactionName = `${method} ${matchedPattern}`;\n updateSpanName(rootSpan, transactionName);\n rootSpan.setAttribute(ATTR_HTTP_ROUTE, matchedPattern);\n rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');\n getCurrentScope().setTransactionName(transactionName);\n}\n"],"names":[],"mappings":";;;AAWA,SAAS,eAAe,OAAA,EAAyB;AAC/C,EAAA,MAAM,WAAW,OAAA,CACd,KAAA,CAAM,GAAG,CAAA,CACT,IAAI,CAAA,OAAA,KAAW;AACd,IAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3B,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,OAAO,qBAAqB,OAAO,CAAA;AAAA,EACrC,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AACX,EAAA,OAAO,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA,CAAG,CAAA;AACnC;AAQO,SAAS,sBAAA,CAAuB,UAAkB,QAAA,EAAwC;AAC/F,EAAA,MAAM,kBAAA,GAAqB,SAAS,MAAA,GAAS,CAAA,GAAI,SAAS,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAAI,QAAA;AAC/E,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,cAAA,CAAe,OAAO,CAAA,CAAE,IAAA,CAAK,kBAAkB,CAAA,EAAG;AACpD,MAAA,OAAO,OAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAKO,SAAS,kCAAA,CAAmC,MAAA,EAAgB,QAAA,EAAkB,QAAA,EAA0B;AAC7G,EAAA,MAAM,cAAA,GAAiB,sBAAA,CAAuB,QAAA,EAAU,QAAQ,CAAA;AAChE,EAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,aAAa,aAAA,EAAc;AACjC,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAAW,YAAY,UAAU,CAAA;AACvC,EAAA,MAAM,YAAA,GAAe,UAAA,CAAW,QAAQ,CAAA,CAAE,IAAA;AAC1C,EAAA,IAAI,YAAA,GAAe,eAAe,CAAA,EAAG;AACnC,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,cAAc,CAAA,CAAA;AACnD,EAAA,cAAA,CAAe,UAAU,eAAe,CAAA;AACxC,EAAA,QAAA,CAAS,YAAA,CAAa,iBAAiB,cAAc,CAAA;AACrD,EAAA,QAAA,CAAS,YAAA,CAAa,kCAAkC,OAAO,CAAA;AAC/D,EAAA,eAAA,EAAgB,CAAE,mBAAmB,eAAe,CAAA;AACtD;;;;"}
|
package/build/esm/server/sdk.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { applySdkMetadata } from '@sentry/core';
|
|
2
2
|
import { getDefaultIntegrations, init as init$1 } from '@sentry/node';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Initializes the server side of the TanStack Start React SDK
|
|
6
|
-
*/
|
|
7
4
|
function init(options) {
|
|
8
5
|
const sentryOptions = {
|
|
9
6
|
defaultIntegrations: [...getDefaultIntegrations(options)],
|
|
10
|
-
...options
|
|
7
|
+
...options
|
|
11
8
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
applySdkMetadata(sentryOptions, "tanstackstart-react", ["tanstackstart-react", "node"]);
|
|
10
|
+
sentryOptions.ignoreSpans = [
|
|
11
|
+
...sentryOptions.ignoreSpans || [],
|
|
12
|
+
/\/node_modules\//,
|
|
13
|
+
/\/@id\//,
|
|
14
|
+
/\/@react-refresh/,
|
|
15
|
+
/\/@vite\//
|
|
16
|
+
];
|
|
15
17
|
return init$1(sentryOptions);
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.js","sources":["../../../src/server/sdk.ts"],"sourcesContent":["import { applySdkMetadata } from '@sentry/core';\nimport type { NodeClient, NodeOptions } from '@sentry/node';\nimport { getDefaultIntegrations as getDefaultNodeIntegrations, init as initNodeSdk } from '@sentry/node';\n\n/**\n * Initializes the server side of the TanStack Start React SDK\n */\nexport function init(options: NodeOptions): NodeClient | undefined {\n const sentryOptions: NodeOptions = {\n defaultIntegrations: [...getDefaultNodeIntegrations(options)],\n ...options,\n };\n\n applySdkMetadata(sentryOptions, 'tanstackstart-react', ['tanstackstart-react', 'node']);\n\n return initNodeSdk(sentryOptions);\n}\n"],"names":["getDefaultNodeIntegrations","initNodeSdk"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"sdk.js","sources":["../../../src/server/sdk.ts"],"sourcesContent":["import { applySdkMetadata } from '@sentry/core';\nimport type { NodeClient, NodeOptions } from '@sentry/node';\nimport { getDefaultIntegrations as getDefaultNodeIntegrations, init as initNodeSdk } from '@sentry/node';\n\n/**\n * Initializes the server side of the TanStack Start React SDK\n */\nexport function init(options: NodeOptions): NodeClient | undefined {\n const sentryOptions: NodeOptions = {\n defaultIntegrations: [...getDefaultNodeIntegrations(options)],\n ...options,\n };\n\n applySdkMetadata(sentryOptions, 'tanstackstart-react', ['tanstackstart-react', 'node']);\n\n sentryOptions.ignoreSpans = [\n ...(sentryOptions.ignoreSpans || []),\n /\\/node_modules\\//,\n /\\/@id\\//,\n /\\/@react-refresh/,\n /\\/@vite\\//,\n ];\n\n return initNodeSdk(sentryOptions);\n}\n"],"names":["getDefaultNodeIntegrations","initNodeSdk"],"mappings":";;;AAOO,SAAS,KAAK,OAAA,EAA8C;AACjE,EAAA,MAAM,aAAA,GAA6B;AAAA,IACjC,mBAAA,EAAqB,CAAC,GAAGA,sBAAA,CAA2B,OAAO,CAAC,CAAA;AAAA,IAC5D,GAAG;AAAA,GACL;AAEA,EAAA,gBAAA,CAAiB,aAAA,EAAe,qBAAA,EAAuB,CAAC,qBAAA,EAAuB,MAAM,CAAC,CAAA;AAEtF,EAAA,aAAA,CAAc,WAAA,GAAc;AAAA,IAC1B,GAAI,aAAA,CAAc,WAAA,IAAe,EAAC;AAAA,IAClC,kBAAA;AAAA,IACA,SAAA;AAAA,IACA,kBAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAOC,OAAY,aAAa,CAAA;AAClC;;;;"}
|
|
@@ -1,47 +1,27 @@
|
|
|
1
1
|
import { getClient, dsnToString, handleTunnelRequest } from '@sentry/core';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Creates a TanStack Start server route configuration for tunneling Sentry envelopes.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```ts
|
|
8
|
-
* import { createFileRoute } from '@tanstack/react-router';
|
|
9
|
-
* import * as Sentry from '@sentry/tanstackstart-react';
|
|
10
|
-
*
|
|
11
|
-
* export const Route = createFileRoute('/monitoring')({
|
|
12
|
-
* server: Sentry.createSentryTunnelRoute({
|
|
13
|
-
* allowedDsns: ['https://public@o0.ingest.sentry.io/0'],
|
|
14
|
-
* }),
|
|
15
|
-
* });
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
3
|
function createSentryTunnelRoute(options) {
|
|
19
4
|
return {
|
|
20
5
|
handlers: {
|
|
21
6
|
POST: async ({ request }) => {
|
|
22
|
-
const allowedDsnsFromOptions = options.allowedDsns?.length ? options.allowedDsns :
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
const dsn = client?.getDsn();
|
|
29
|
-
return dsn ? [dsnToString(dsn)] : undefined;
|
|
30
|
-
})();
|
|
31
|
-
|
|
7
|
+
const allowedDsnsFromOptions = options.allowedDsns?.length ? options.allowedDsns : void 0;
|
|
8
|
+
const allowedDsns = allowedDsnsFromOptions ?? (() => {
|
|
9
|
+
const client = getClient();
|
|
10
|
+
const dsn = client?.getDsn();
|
|
11
|
+
return dsn ? [dsnToString(dsn)] : void 0;
|
|
12
|
+
})();
|
|
32
13
|
if (!allowedDsns) {
|
|
33
14
|
return new Response(
|
|
34
|
-
|
|
35
|
-
{ status: 500 }
|
|
15
|
+
"Tunnel route requires Sentry server SDK initialized with a DSN, or pass allowedDsns explicitly.",
|
|
16
|
+
{ status: 500 }
|
|
36
17
|
);
|
|
37
18
|
}
|
|
38
|
-
|
|
39
19
|
return handleTunnelRequest({
|
|
40
20
|
request,
|
|
41
|
-
allowedDsns
|
|
21
|
+
allowedDsns
|
|
42
22
|
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
45
25
|
};
|
|
46
26
|
}
|
|
47
27
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tunnelRoute.js","sources":["../../../src/server/tunnelRoute.ts"],"sourcesContent":["import { dsnToString, getClient, handleTunnelRequest } from '@sentry/core';\n\nexport interface CreateSentryTunnelRouteOptions {\n allowedDsns?: string[];\n}\n\ntype SentryTunnelRouteHandlerContext = {\n request: Request;\n};\n\ntype SentryTunnelRoute = {\n handlers: {\n POST: (context: SentryTunnelRouteHandlerContext) => Promise<Response>;\n };\n};\n\n/**\n * Creates a TanStack Start server route configuration for tunneling Sentry envelopes.\n *\n * @example\n * ```ts\n * import { createFileRoute } from '@tanstack/react-router';\n * import * as Sentry from '@sentry/tanstackstart-react';\n *\n * export const Route = createFileRoute('/monitoring')({\n * server: Sentry.createSentryTunnelRoute({\n * allowedDsns: ['https://public@o0.ingest.sentry.io/0'],\n * }),\n * });\n * ```\n */\nexport function createSentryTunnelRoute(options: CreateSentryTunnelRouteOptions): SentryTunnelRoute {\n return {\n handlers: {\n POST: async ({ request }) => {\n const allowedDsnsFromOptions = options.allowedDsns?.length ? options.allowedDsns : undefined;\n\n const allowedDsns =\n allowedDsnsFromOptions ??\n (() => {\n const client = getClient();\n const dsn = client?.getDsn();\n return dsn ? [dsnToString(dsn)] : undefined;\n })();\n\n if (!allowedDsns) {\n return new Response(\n 'Tunnel route requires Sentry server SDK initialized with a DSN, or pass allowedDsns explicitly.',\n { status: 500 },\n );\n }\n\n return handleTunnelRequest({\n request,\n allowedDsns,\n });\n },\n },\n };\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"tunnelRoute.js","sources":["../../../src/server/tunnelRoute.ts"],"sourcesContent":["import { dsnToString, getClient, handleTunnelRequest } from '@sentry/core';\n\nexport interface CreateSentryTunnelRouteOptions {\n allowedDsns?: string[];\n}\n\ntype SentryTunnelRouteHandlerContext = {\n request: Request;\n};\n\ntype SentryTunnelRoute = {\n handlers: {\n POST: (context: SentryTunnelRouteHandlerContext) => Promise<Response>;\n };\n};\n\n/**\n * Creates a TanStack Start server route configuration for tunneling Sentry envelopes.\n *\n * @example\n * ```ts\n * import { createFileRoute } from '@tanstack/react-router';\n * import * as Sentry from '@sentry/tanstackstart-react';\n *\n * export const Route = createFileRoute('/monitoring')({\n * server: Sentry.createSentryTunnelRoute({\n * allowedDsns: ['https://public@o0.ingest.sentry.io/0'],\n * }),\n * });\n * ```\n */\nexport function createSentryTunnelRoute(options: CreateSentryTunnelRouteOptions): SentryTunnelRoute {\n return {\n handlers: {\n POST: async ({ request }) => {\n const allowedDsnsFromOptions = options.allowedDsns?.length ? options.allowedDsns : undefined;\n\n const allowedDsns =\n allowedDsnsFromOptions ??\n (() => {\n const client = getClient();\n const dsn = client?.getDsn();\n return dsn ? [dsnToString(dsn)] : undefined;\n })();\n\n if (!allowedDsns) {\n return new Response(\n 'Tunnel route requires Sentry server SDK initialized with a DSN, or pass allowedDsns explicitly.',\n { status: 500 },\n );\n }\n\n return handleTunnelRequest({\n request,\n allowedDsns,\n });\n },\n },\n };\n}\n"],"names":[],"mappings":";;AA+BO,SAAS,wBAAwB,OAAA,EAA4D;AAClG,EAAA,OAAO;AAAA,IACL,QAAA,EAAU;AAAA,MACR,IAAA,EAAM,OAAO,EAAE,OAAA,EAAQ,KAAM;AAC3B,QAAA,MAAM,sBAAA,GAAyB,OAAA,CAAQ,WAAA,EAAa,MAAA,GAAS,QAAQ,WAAA,GAAc,MAAA;AAEnF,QAAA,MAAM,WAAA,GACJ,2BACC,MAAM;AACL,UAAA,MAAM,SAAS,SAAA,EAAU;AACzB,UAAA,MAAM,GAAA,GAAM,QAAQ,MAAA,EAAO;AAC3B,UAAA,OAAO,GAAA,GAAM,CAAC,WAAA,CAAY,GAAG,CAAC,CAAA,GAAI,MAAA;AAAA,QACpC,CAAA,GAAG;AAEL,QAAA,IAAI,CAAC,WAAA,EAAa;AAChB,UAAA,OAAO,IAAI,QAAA;AAAA,YACT,iGAAA;AAAA,YACA,EAAE,QAAQ,GAAA;AAAI,WAChB;AAAA,QACF;AAEA,QAAA,OAAO,mBAAA,CAAoB;AAAA,UACzB,OAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA,MACH;AAAA;AACF,GACF;AACF;;;;"}
|
|
@@ -1,31 +1,15 @@
|
|
|
1
1
|
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/node';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Extracts the SHA-256 hash from a server function pathname.
|
|
5
|
-
* Server function pathnames are structured as `/_serverFn/<hash>`.
|
|
6
|
-
* This function matches the pattern and returns the hash if found.
|
|
7
|
-
*
|
|
8
|
-
* @param pathname - the pathname of the server function
|
|
9
|
-
* @returns the sha256 of the server function
|
|
10
|
-
*/
|
|
11
|
-
function extractServerFunctionSha256(pathname) {
|
|
12
|
-
const serverFnMatch = pathname.match(/\/_serverFn\/([a-f0-9]{64})/i);
|
|
13
|
-
return serverFnMatch?.[1] ?? 'unknown';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Returns span options for TanStack Start middleware spans.
|
|
18
|
-
*/
|
|
19
3
|
function getMiddlewareSpanOptions(name) {
|
|
20
4
|
return {
|
|
21
|
-
op:
|
|
5
|
+
op: "middleware.tanstackstart",
|
|
22
6
|
name,
|
|
23
7
|
attributes: {
|
|
24
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:
|
|
25
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]:
|
|
26
|
-
}
|
|
8
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.middleware.tanstackstart",
|
|
9
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "middleware.tanstackstart"
|
|
10
|
+
}
|
|
27
11
|
};
|
|
28
12
|
}
|
|
29
13
|
|
|
30
|
-
export {
|
|
14
|
+
export { getMiddlewareSpanOptions };
|
|
31
15
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../../src/server/utils.ts"],"sourcesContent":["import type { StartSpanOptions } from '@sentry/core';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/node';\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/server/utils.ts"],"sourcesContent":["import type { StartSpanOptions } from '@sentry/core';\nimport { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/node';\n\n/**\n * Returns span options for TanStack Start middleware spans.\n */\nexport function getMiddlewareSpanOptions(name: string): StartSpanOptions {\n return {\n op: 'middleware.tanstackstart',\n name,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.middleware.tanstackstart',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'middleware.tanstackstart',\n },\n };\n}\n"],"names":[],"mappings":";;AAMO,SAAS,yBAAyB,IAAA,EAAgC;AACvE,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,0BAAA;AAAA,IACJ,IAAA;AAAA,IACA,UAAA,EAAY;AAAA,MACV,CAAC,gCAAgC,GAAG,+BAAA;AAAA,MACpC,CAAC,4BAA4B,GAAG;AAAA;AAClC,GACF;AACF;;;;"}
|
|
@@ -1,31 +1,80 @@
|
|
|
1
|
-
import { flushIfServerless } from '@sentry/core';
|
|
2
|
-
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
|
|
3
|
-
import {
|
|
1
|
+
import { flushIfServerless, getTraceMetaTags } from '@sentry/core';
|
|
2
|
+
import { startSpan, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, captureException } from '@sentry/node';
|
|
3
|
+
import { updateSpanWithRouteParametrization } from './routeParametrization.js';
|
|
4
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
|
-
|
|
5
|
+
function addMetaTagToHead(htmlChunk, metaTagsStr) {
|
|
6
|
+
if (typeof htmlChunk !== "string" || !metaTagsStr) {
|
|
7
|
+
return htmlChunk;
|
|
8
|
+
}
|
|
9
|
+
if (htmlChunk.includes('"sentry-trace"')) {
|
|
10
|
+
return htmlChunk;
|
|
11
|
+
}
|
|
12
|
+
let replaced = false;
|
|
13
|
+
return htmlChunk.replace(/"[^"]*"|'[^']*'|(<head>)/g, (match, headTag) => {
|
|
14
|
+
if (headTag && !replaced) {
|
|
15
|
+
replaced = true;
|
|
16
|
+
return `<head>${metaTagsStr}`;
|
|
17
|
+
}
|
|
18
|
+
return match;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function injectMetaTagsInResponse(originalResponse) {
|
|
22
|
+
try {
|
|
23
|
+
const contentType = originalResponse.headers.get("content-type");
|
|
24
|
+
const isPageloadRequest = contentType?.startsWith("text/html");
|
|
25
|
+
if (!isPageloadRequest) {
|
|
26
|
+
return originalResponse;
|
|
27
|
+
}
|
|
28
|
+
const originalBody = originalResponse.body;
|
|
29
|
+
if (!originalBody) {
|
|
30
|
+
return originalResponse;
|
|
31
|
+
}
|
|
32
|
+
const metaTagsStr = getTraceMetaTags();
|
|
33
|
+
const decoder = new TextDecoder();
|
|
34
|
+
const newResponseStream = new ReadableStream({
|
|
35
|
+
start: async (controller) => {
|
|
36
|
+
const body = originalBody;
|
|
37
|
+
async function* bodyReporter() {
|
|
38
|
+
try {
|
|
39
|
+
for await (const chunk of body) {
|
|
40
|
+
yield chunk;
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
captureException(e, {
|
|
44
|
+
mechanism: { type: "auto.http.tanstackstart", handled: false }
|
|
45
|
+
});
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
let errored = false;
|
|
50
|
+
try {
|
|
51
|
+
for await (const chunk of bodyReporter()) {
|
|
52
|
+
const html = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
53
|
+
const modifiedHtml = addMetaTagToHead(html, metaTagsStr);
|
|
54
|
+
controller.enqueue(new TextEncoder().encode(modifiedHtml));
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
errored = true;
|
|
58
|
+
controller.error(e);
|
|
59
|
+
} finally {
|
|
60
|
+
if (!errored) {
|
|
61
|
+
controller.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return new Response(newResponseStream, {
|
|
67
|
+
status: originalResponse.status,
|
|
68
|
+
statusText: originalResponse.statusText,
|
|
69
|
+
headers: new Headers(originalResponse.headers)
|
|
70
|
+
});
|
|
71
|
+
} catch (e) {
|
|
72
|
+
captureException(e, {
|
|
73
|
+
mechanism: { type: "auto.http.tanstackstart", handled: false }
|
|
74
|
+
});
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
29
78
|
function wrapFetchWithSentry(serverEntry) {
|
|
30
79
|
if (serverEntry.fetch) {
|
|
31
80
|
serverEntry.fetch = new Proxy(serverEntry.fetch, {
|
|
@@ -33,36 +82,31 @@ function wrapFetchWithSentry(serverEntry) {
|
|
|
33
82
|
try {
|
|
34
83
|
const request = args[0];
|
|
35
84
|
const url = new URL(request.url);
|
|
36
|
-
const method = request.method ||
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (url.pathname.includes('_serverFn') || url.pathname.includes('createServerFn')) {
|
|
40
|
-
const functionSha256 = extractServerFunctionSha256(url.pathname);
|
|
41
|
-
const op = 'function.tanstackstart';
|
|
42
|
-
|
|
43
|
-
const serverFunctionSpanAttributes = {
|
|
44
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.tanstackstart.server',
|
|
45
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
|
|
46
|
-
'tanstackstart.function.hash.sha256': functionSha256,
|
|
47
|
-
};
|
|
48
|
-
|
|
85
|
+
const method = request.method || "GET";
|
|
86
|
+
if (url.pathname.includes("_serverFn") || url.pathname.includes("createServerFn")) {
|
|
87
|
+
const op = "function.tanstackstart";
|
|
49
88
|
return await startSpan(
|
|
50
89
|
{
|
|
51
|
-
op
|
|
90
|
+
op,
|
|
52
91
|
name: `${method} ${url.pathname}`,
|
|
53
|
-
attributes:
|
|
92
|
+
attributes: {
|
|
93
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.function.tanstackstart.server",
|
|
94
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op
|
|
95
|
+
}
|
|
54
96
|
},
|
|
55
97
|
async () => {
|
|
56
98
|
return target.apply(thisArg, args);
|
|
57
|
-
}
|
|
99
|
+
}
|
|
58
100
|
);
|
|
59
101
|
}
|
|
60
|
-
|
|
61
|
-
|
|
102
|
+
if (typeof __SENTRY_ROUTE_PATTERNS__ !== "undefined") {
|
|
103
|
+
updateSpanWithRouteParametrization(method, url.pathname, __SENTRY_ROUTE_PATTERNS__);
|
|
104
|
+
}
|
|
105
|
+
return injectMetaTagsInResponse(await target.apply(thisArg, args));
|
|
62
106
|
} finally {
|
|
63
107
|
await flushIfServerless();
|
|
64
108
|
}
|
|
65
|
-
}
|
|
109
|
+
}
|
|
66
110
|
});
|
|
67
111
|
}
|
|
68
112
|
return serverEntry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapFetchWithSentry.js","sources":["../../../src/server/wrapFetchWithSentry.ts"],"sourcesContent":["import { flushIfServerless } from '@sentry/core';\nimport {
|
|
1
|
+
{"version":3,"file":"wrapFetchWithSentry.js","sources":["../../../src/server/wrapFetchWithSentry.ts"],"sourcesContent":["import { flushIfServerless, getTraceMetaTags } from '@sentry/core';\nimport {\n captureException,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n startSpan,\n} from '@sentry/node';\nimport { updateSpanWithRouteParametrization } from './routeParametrization';\n\ndeclare const __SENTRY_ROUTE_PATTERNS__: string[] | undefined;\n\nexport type ServerEntry = {\n fetch: (request: Request, opts?: unknown) => Promise<Response> | Response;\n};\n\n/**\n * This function optimistically assumes that the HTML coming in chunks will not be split\n * within the <head> tag. If this still happens, we simply won't replace anything.\n */\nfunction addMetaTagToHead(htmlChunk: string, metaTagsStr: string): string {\n if (typeof htmlChunk !== 'string' || !metaTagsStr) {\n return htmlChunk;\n }\n\n if (htmlChunk.includes('\"sentry-trace\"')) {\n return htmlChunk;\n }\n\n // Skip quoted attribute values so we don't match <head> inside e.g. data-code=\"...<head>...\"\n let replaced = false;\n return htmlChunk.replace(/\"[^\"]*\"|'[^']*'|(<head>)/g, (match, headTag) => {\n if (headTag && !replaced) {\n replaced = true;\n return `<head>${metaTagsStr}`;\n }\n return match;\n });\n}\n\nfunction injectMetaTagsInResponse(originalResponse: Response): Response {\n try {\n const contentType = originalResponse.headers.get('content-type');\n\n const isPageloadRequest = contentType?.startsWith('text/html');\n if (!isPageloadRequest) {\n return originalResponse;\n }\n\n // Type case necessary b/c the body's ReadableStream type doesn't include\n // the async iterator that is actually available in Node\n // We later on use the async iterator to read the body chunks\n // see https://github.com/microsoft/TypeScript/issues/39051\n const originalBody = originalResponse.body as NodeJS.ReadableStream | null;\n if (!originalBody) {\n return originalResponse;\n }\n\n const metaTagsStr = getTraceMetaTags();\n const decoder = new TextDecoder();\n\n const newResponseStream = new ReadableStream({\n start: async controller => {\n // Assign to a new variable to avoid TS losing the narrower type checked above.\n const body = originalBody;\n\n async function* bodyReporter(): AsyncGenerator<string | Buffer> {\n try {\n for await (const chunk of body) {\n yield chunk;\n }\n } catch (e) {\n captureException(e, {\n mechanism: { type: 'auto.http.tanstackstart', handled: false },\n });\n throw e;\n }\n }\n\n let errored = false;\n try {\n for await (const chunk of bodyReporter()) {\n const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });\n const modifiedHtml = addMetaTagToHead(html, metaTagsStr);\n controller.enqueue(new TextEncoder().encode(modifiedHtml));\n }\n } catch (e) {\n errored = true;\n controller.error(e);\n } finally {\n if (!errored) {\n controller.close();\n }\n }\n },\n });\n\n return new Response(newResponseStream, {\n status: originalResponse.status,\n statusText: originalResponse.statusText,\n headers: new Headers(originalResponse.headers),\n });\n } catch (e) {\n captureException(e, {\n mechanism: { type: 'auto.http.tanstackstart', handled: false },\n });\n throw e;\n }\n}\n\n/**\n * This function can be used to wrap the server entry request handler to add tracing to server-side functionality.\n * You must explicitly define a server entry point in your application for this to work. This is done by passing the request handler to the `createServerEntry` function.\n * For more information about the server entry point, see the [TanStack Start documentation](https://tanstack.com/start/docs/server-entry).\n *\n * @example\n * ```ts\n * import { wrapFetchWithSentry } from '@sentry/tanstackstart-react';\n *\n * import handler, { createServerEntry } from '@tanstack/react-start/server-entry';\n * import type { ServerEntry } from '@tanstack/react-start/server-entry';\n *\n * const requestHandler: ServerEntry = wrapFetchWithSentry({\n * fetch(request: Request) {\n * return handler.fetch(request);\n * },\n * });\n *\n * export default serverEntry = createServerEntry(requestHandler);\n * ```\n *\n * @param serverEntry - request handler to wrap\n * @returns - wrapped request handler\n */\nexport function wrapFetchWithSentry(serverEntry: ServerEntry): ServerEntry {\n if (serverEntry.fetch) {\n serverEntry.fetch = new Proxy<typeof serverEntry.fetch>(serverEntry.fetch, {\n async apply(target, thisArg, args) {\n try {\n const request: Request = args[0];\n const url = new URL(request.url);\n const method = request.method || 'GET';\n\n // instrument server functions\n if (url.pathname.includes('_serverFn') || url.pathname.includes('createServerFn')) {\n const op = 'function.tanstackstart';\n\n return await startSpan(\n {\n op,\n name: `${method} ${url.pathname}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.tanstackstart.server',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,\n },\n },\n async () => {\n return target.apply(thisArg, args);\n },\n );\n }\n\n if (typeof __SENTRY_ROUTE_PATTERNS__ !== 'undefined') {\n updateSpanWithRouteParametrization(method, url.pathname, __SENTRY_ROUTE_PATTERNS__);\n }\n\n return injectMetaTagsInResponse(await target.apply(thisArg, args));\n } finally {\n await flushIfServerless();\n }\n },\n });\n }\n return serverEntry;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,116 +1,68 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core function that wraps middleware arrays matching the given regex.
|
|
3
|
-
*/
|
|
4
1
|
function wrapMiddlewareArrays(code, id, debug, regex) {
|
|
5
2
|
const skipped = [];
|
|
6
3
|
let didWrap = false;
|
|
7
|
-
|
|
8
4
|
const transformed = code.replace(regex, (match, key, contents) => {
|
|
9
5
|
const objContents = arrayToObjectShorthand(contents);
|
|
10
6
|
if (objContents) {
|
|
11
7
|
didWrap = true;
|
|
12
8
|
if (debug) {
|
|
13
|
-
// eslint-disable-next-line no-console
|
|
14
9
|
console.log(`[Sentry] Auto-wrapping ${key} in ${id}`);
|
|
15
10
|
}
|
|
16
|
-
|
|
17
|
-
if (key.endsWith('(')) {
|
|
11
|
+
if (key.endsWith("(")) {
|
|
18
12
|
return `${key}wrapMiddlewaresWithSentry(${objContents}))`;
|
|
19
13
|
}
|
|
20
14
|
return `${key}: wrapMiddlewaresWithSentry(${objContents})`;
|
|
21
15
|
}
|
|
22
|
-
// Track middlewares that couldn't be auto-wrapped
|
|
23
|
-
// Skip if we matched whitespace only
|
|
24
16
|
if (contents.trim()) {
|
|
25
17
|
skipped.push(key);
|
|
26
18
|
}
|
|
27
19
|
return match;
|
|
28
20
|
});
|
|
29
|
-
|
|
30
21
|
return { code: transformed, didWrap, skipped };
|
|
31
22
|
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Wraps global middleware arrays (requestMiddleware, functionMiddleware) in createStart() files.
|
|
35
|
-
*/
|
|
36
23
|
function wrapGlobalMiddleware(code, id, debug) {
|
|
37
24
|
return wrapMiddlewareArrays(code, id, debug, /(requestMiddleware|functionMiddleware)\s*:\s*\[([^\]]*)\]/g);
|
|
38
25
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Wraps route middleware arrays in createFileRoute() files.
|
|
42
|
-
*/
|
|
43
26
|
function wrapRouteMiddleware(code, id, debug) {
|
|
44
27
|
return wrapMiddlewareArrays(code, id, debug, /(middleware)\s*:\s*\[([^\]]*)\]/g);
|
|
45
28
|
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Wraps middleware arrays in createServerFn().middleware([...]) calls.
|
|
49
|
-
*/
|
|
50
29
|
function wrapServerFnMiddleware(code, id, debug) {
|
|
51
30
|
return wrapMiddlewareArrays(code, id, debug, /(\.middleware\s*\()\s*\[([^\]]*)\]\s*\)/g);
|
|
52
31
|
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Applies a wrap function to the current state and returns the updated state.
|
|
56
|
-
*/
|
|
57
|
-
function applyWrap(
|
|
58
|
-
state,
|
|
59
|
-
wrapFn,
|
|
60
|
-
id,
|
|
61
|
-
debug,
|
|
62
|
-
) {
|
|
32
|
+
function applyWrap(state, wrapFn, id, debug) {
|
|
63
33
|
const result = wrapFn(state.code, id, debug);
|
|
64
34
|
return {
|
|
65
35
|
code: result.code,
|
|
66
36
|
needsImport: state.needsImport || result.didWrap,
|
|
67
|
-
skippedMiddlewares: [...state.skippedMiddlewares, ...result.skipped]
|
|
37
|
+
skippedMiddlewares: [...state.skippedMiddlewares, ...result.skipped]
|
|
68
38
|
};
|
|
69
39
|
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A Vite plugin that automatically instruments TanStack Start middlewares:
|
|
73
|
-
* - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`
|
|
74
|
-
* - `middleware` arrays in `createFileRoute()` route definitions
|
|
75
|
-
*/
|
|
76
40
|
function makeAutoInstrumentMiddlewarePlugin(options = {}) {
|
|
77
41
|
const { enabled = true, debug = false } = options;
|
|
78
|
-
|
|
79
42
|
return {
|
|
80
|
-
name:
|
|
81
|
-
enforce:
|
|
82
|
-
|
|
43
|
+
name: "sentry-tanstack-middleware-auto-instrument",
|
|
44
|
+
enforce: "pre",
|
|
83
45
|
transform(code, id) {
|
|
84
46
|
if (!enabled) {
|
|
85
47
|
return null;
|
|
86
48
|
}
|
|
87
|
-
|
|
88
|
-
// Skip if not a TS/JS file
|
|
89
49
|
if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {
|
|
90
50
|
return null;
|
|
91
51
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const isRouteFile = code.includes('createFileRoute(') && /middleware\s*:\s*\[/.test(code);
|
|
96
|
-
const isServerFnFile = code.includes('createServerFn') && /\.middleware\s*\(\s*\[/.test(code);
|
|
97
|
-
|
|
52
|
+
const isStartFile = id.includes("start") && code.includes("createStart(");
|
|
53
|
+
const isRouteFile = code.includes("createFileRoute(") && /middleware\s*:\s*\[/.test(code);
|
|
54
|
+
const isServerFnFile = code.includes("createServerFn") && /\.middleware\s*\(\s*\[/.test(code);
|
|
98
55
|
if (!isStartFile && !isRouteFile && !isServerFnFile) {
|
|
99
56
|
return null;
|
|
100
57
|
}
|
|
101
|
-
|
|
102
|
-
// Skip if the user already did some manual wrapping
|
|
103
|
-
if (code.includes('wrapMiddlewaresWithSentry')) {
|
|
58
|
+
if (code.includes("wrapMiddlewaresWithSentry")) {
|
|
104
59
|
return null;
|
|
105
60
|
}
|
|
106
|
-
|
|
107
61
|
let fileTransformState = {
|
|
108
62
|
code,
|
|
109
63
|
needsImport: false,
|
|
110
|
-
skippedMiddlewares: []
|
|
64
|
+
skippedMiddlewares: []
|
|
111
65
|
};
|
|
112
|
-
|
|
113
|
-
// Wrap middlewares
|
|
114
66
|
if (isStartFile) {
|
|
115
67
|
fileTransformState = applyWrap(fileTransformState, wrapGlobalMiddleware, id, debug);
|
|
116
68
|
}
|
|
@@ -120,70 +72,36 @@ function makeAutoInstrumentMiddlewarePlugin(options = {}) {
|
|
|
120
72
|
if (isServerFnFile) {
|
|
121
73
|
fileTransformState = applyWrap(fileTransformState, wrapServerFnMiddleware, id, debug);
|
|
122
74
|
}
|
|
123
|
-
|
|
124
|
-
// Warn about middlewares that couldn't be auto-wrapped
|
|
125
75
|
if (fileTransformState.skippedMiddlewares.length > 0) {
|
|
126
|
-
// eslint-disable-next-line no-console
|
|
127
76
|
console.warn(
|
|
128
|
-
`[Sentry] Could not auto-instrument ${fileTransformState.skippedMiddlewares.join(
|
|
129
|
-
'To instrument these middlewares, use wrapMiddlewaresWithSentry() manually. ',
|
|
77
|
+
`[Sentry] Could not auto-instrument ${fileTransformState.skippedMiddlewares.join(" and ")} in ${id}. To instrument these middlewares, use wrapMiddlewaresWithSentry() manually. `
|
|
130
78
|
);
|
|
131
79
|
}
|
|
132
|
-
|
|
133
|
-
// We didn't wrap any middlewares, so we don't need to import the wrapMiddlewaresWithSentry function
|
|
134
80
|
if (!fileTransformState.needsImport) {
|
|
135
81
|
return null;
|
|
136
82
|
}
|
|
137
|
-
|
|
138
83
|
return { code: addSentryImport(fileTransformState.code), map: null };
|
|
139
|
-
}
|
|
84
|
+
}
|
|
140
85
|
};
|
|
141
86
|
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Convert array contents to object shorthand syntax.
|
|
145
|
-
* e.g., "foo, bar, baz" → "{ foo, bar, baz }"
|
|
146
|
-
*
|
|
147
|
-
* Returns null if contents contain non-identifier expressions (function calls, etc.)
|
|
148
|
-
* which cannot be converted to object shorthand.
|
|
149
|
-
*/
|
|
150
87
|
function arrayToObjectShorthand(contents) {
|
|
151
|
-
const items = contents
|
|
152
|
-
|
|
153
|
-
.map(s => s.trim())
|
|
154
|
-
.filter(Boolean);
|
|
155
|
-
|
|
156
|
-
// Only convert if all items are valid identifiers (no complex expressions)
|
|
157
|
-
const allIdentifiers = items.every(item => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(item));
|
|
88
|
+
const items = contents.split(",").map((s) => s.trim()).filter(Boolean);
|
|
89
|
+
const allIdentifiers = items.every((item) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(item));
|
|
158
90
|
if (!allIdentifiers || items.length === 0) {
|
|
159
91
|
return null;
|
|
160
92
|
}
|
|
161
|
-
|
|
162
|
-
// Deduplicate to avoid invalid syntax like { foo, foo }
|
|
163
93
|
const uniqueItems = [...new Set(items)];
|
|
164
|
-
|
|
165
|
-
return `{ ${uniqueItems.join(', ')} }`;
|
|
94
|
+
return `{ ${uniqueItems.join(", ")} }`;
|
|
166
95
|
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Adds the wrapMiddlewaresWithSentry import to the code.
|
|
170
|
-
* Handles 'use client' and 'use server' directives by inserting the import after them.
|
|
171
|
-
*/
|
|
172
96
|
function addSentryImport(code) {
|
|
173
97
|
const sentryImport = "import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';\n";
|
|
174
|
-
|
|
175
|
-
// Don't add the import if it already exists
|
|
176
98
|
if (code.includes(sentryImport.trimEnd())) {
|
|
177
99
|
return code;
|
|
178
100
|
}
|
|
179
|
-
|
|
180
|
-
// Check for 'use server' or 'use client' directives, these need to be before any imports
|
|
181
101
|
const directiveMatch = code.match(/^(['"])use (client|server)\1;?\s*\n?/);
|
|
182
|
-
|
|
183
102
|
if (!directiveMatch) {
|
|
184
103
|
return sentryImport + code;
|
|
185
104
|
}
|
|
186
|
-
|
|
187
105
|
const directive = directiveMatch[0];
|
|
188
106
|
return directive + sentryImport + code.slice(directive.length);
|
|
189
107
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoInstrumentMiddleware.js","sources":["../../../src/vite/autoInstrumentMiddleware.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\ntype AutoInstrumentMiddlewareOptions = {\n enabled?: boolean;\n debug?: boolean;\n};\n\ntype WrapResult = {\n code: string;\n didWrap: boolean;\n skipped: string[];\n};\n\ntype FileTransformState = {\n code: string;\n needsImport: boolean;\n skippedMiddlewares: string[];\n};\n\n/**\n * Core function that wraps middleware arrays matching the given regex.\n */\nfunction wrapMiddlewareArrays(code: string, id: string, debug: boolean, regex: RegExp): WrapResult {\n const skipped: string[] = [];\n let didWrap = false;\n\n const transformed = code.replace(regex, (match: string, key: string, contents: string) => {\n const objContents = arrayToObjectShorthand(contents);\n if (objContents) {\n didWrap = true;\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(`[Sentry] Auto-wrapping ${key} in ${id}`);\n }\n // Handle method call syntax like `.middleware([...])` vs object property syntax like `middleware: [...]`\n if (key.endsWith('(')) {\n return `${key}wrapMiddlewaresWithSentry(${objContents}))`;\n }\n return `${key}: wrapMiddlewaresWithSentry(${objContents})`;\n }\n // Track middlewares that couldn't be auto-wrapped\n // Skip if we matched whitespace only\n if (contents.trim()) {\n skipped.push(key);\n }\n return match;\n });\n\n return { code: transformed, didWrap, skipped };\n}\n\n/**\n * Wraps global middleware arrays (requestMiddleware, functionMiddleware) in createStart() files.\n */\nexport function wrapGlobalMiddleware(code: string, id: string, debug: boolean): WrapResult {\n return wrapMiddlewareArrays(code, id, debug, /(requestMiddleware|functionMiddleware)\\s*:\\s*\\[([^\\]]*)\\]/g);\n}\n\n/**\n * Wraps route middleware arrays in createFileRoute() files.\n */\nexport function wrapRouteMiddleware(code: string, id: string, debug: boolean): WrapResult {\n return wrapMiddlewareArrays(code, id, debug, /(middleware)\\s*:\\s*\\[([^\\]]*)\\]/g);\n}\n\n/**\n * Wraps middleware arrays in createServerFn().middleware([...]) calls.\n */\nexport function wrapServerFnMiddleware(code: string, id: string, debug: boolean): WrapResult {\n return wrapMiddlewareArrays(code, id, debug, /(\\.middleware\\s*\\()\\s*\\[([^\\]]*)\\]\\s*\\)/g);\n}\n\n/**\n * Applies a wrap function to the current state and returns the updated state.\n */\nfunction applyWrap(\n state: FileTransformState,\n wrapFn: (code: string, id: string, debug: boolean) => WrapResult,\n id: string,\n debug: boolean,\n): FileTransformState {\n const result = wrapFn(state.code, id, debug);\n return {\n code: result.code,\n needsImport: state.needsImport || result.didWrap,\n skippedMiddlewares: [...state.skippedMiddlewares, ...result.skipped],\n };\n}\n\n/**\n * A Vite plugin that automatically instruments TanStack Start middlewares:\n * - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`\n * - `middleware` arrays in `createFileRoute()` route definitions\n */\nexport function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {\n const { enabled = true, debug = false } = options;\n\n return {\n name: 'sentry-tanstack-middleware-auto-instrument',\n enforce: 'pre',\n\n transform(code, id) {\n if (!enabled) {\n return null;\n }\n\n // Skip if not a TS/JS file\n if (!/\\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {\n return null;\n }\n\n // Detect file types that should be instrumented\n const isStartFile = id.includes('start') && code.includes('createStart(');\n const isRouteFile = code.includes('createFileRoute(') && /middleware\\s*:\\s*\\[/.test(code);\n const isServerFnFile = code.includes('createServerFn') && /\\.middleware\\s*\\(\\s*\\[/.test(code);\n\n if (!isStartFile && !isRouteFile && !isServerFnFile) {\n return null;\n }\n\n // Skip if the user already did some manual wrapping\n if (code.includes('wrapMiddlewaresWithSentry')) {\n return null;\n }\n\n let fileTransformState: FileTransformState = {\n code,\n needsImport: false,\n skippedMiddlewares: [],\n };\n\n // Wrap middlewares\n if (isStartFile) {\n fileTransformState = applyWrap(fileTransformState, wrapGlobalMiddleware, id, debug);\n }\n if (isRouteFile) {\n fileTransformState = applyWrap(fileTransformState, wrapRouteMiddleware, id, debug);\n }\n if (isServerFnFile) {\n fileTransformState = applyWrap(fileTransformState, wrapServerFnMiddleware, id, debug);\n }\n\n // Warn about middlewares that couldn't be auto-wrapped\n if (fileTransformState.skippedMiddlewares.length > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[Sentry] Could not auto-instrument ${fileTransformState.skippedMiddlewares.join(' and ')} in ${id}. ` +\n 'To instrument these middlewares, use wrapMiddlewaresWithSentry() manually. ',\n );\n }\n\n // We didn't wrap any middlewares, so we don't need to import the wrapMiddlewaresWithSentry function\n if (!fileTransformState.needsImport) {\n return null;\n }\n\n return { code: addSentryImport(fileTransformState.code), map: null };\n },\n };\n}\n\n/**\n * Convert array contents to object shorthand syntax.\n * e.g., \"foo, bar, baz\" → \"{ foo, bar, baz }\"\n *\n * Returns null if contents contain non-identifier expressions (function calls, etc.)\n * which cannot be converted to object shorthand.\n */\nexport function arrayToObjectShorthand(contents: string): string | null {\n const items = contents\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n\n // Only convert if all items are valid identifiers (no complex expressions)\n const allIdentifiers = items.every(item => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(item));\n if (!allIdentifiers || items.length === 0) {\n return null;\n }\n\n // Deduplicate to avoid invalid syntax like { foo, foo }\n const uniqueItems = [...new Set(items)];\n\n return `{ ${uniqueItems.join(', ')} }`;\n}\n\n/**\n * Adds the wrapMiddlewaresWithSentry import to the code.\n * Handles 'use client' and 'use server' directives by inserting the import after them.\n */\nexport function addSentryImport(code: string): string {\n const sentryImport = \"import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';\\n\";\n\n // Don't add the import if it already exists\n if (code.includes(sentryImport.trimEnd())) {\n return code;\n }\n\n // Check for 'use server' or 'use client' directives, these need to be before any imports\n const directiveMatch = code.match(/^(['\"])use (client|server)\\1;?\\s*\\n?/);\n\n if (!directiveMatch) {\n return sentryImport + code;\n }\n\n const directive = directiveMatch[0];\n return directive + sentryImport + code.slice(directive.length);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"autoInstrumentMiddleware.js","sources":["../../../src/vite/autoInstrumentMiddleware.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\ntype AutoInstrumentMiddlewareOptions = {\n enabled?: boolean;\n debug?: boolean;\n};\n\ntype WrapResult = {\n code: string;\n didWrap: boolean;\n skipped: string[];\n};\n\ntype FileTransformState = {\n code: string;\n needsImport: boolean;\n skippedMiddlewares: string[];\n};\n\n/**\n * Core function that wraps middleware arrays matching the given regex.\n */\nfunction wrapMiddlewareArrays(code: string, id: string, debug: boolean, regex: RegExp): WrapResult {\n const skipped: string[] = [];\n let didWrap = false;\n\n const transformed = code.replace(regex, (match: string, key: string, contents: string) => {\n const objContents = arrayToObjectShorthand(contents);\n if (objContents) {\n didWrap = true;\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(`[Sentry] Auto-wrapping ${key} in ${id}`);\n }\n // Handle method call syntax like `.middleware([...])` vs object property syntax like `middleware: [...]`\n if (key.endsWith('(')) {\n return `${key}wrapMiddlewaresWithSentry(${objContents}))`;\n }\n return `${key}: wrapMiddlewaresWithSentry(${objContents})`;\n }\n // Track middlewares that couldn't be auto-wrapped\n // Skip if we matched whitespace only\n if (contents.trim()) {\n skipped.push(key);\n }\n return match;\n });\n\n return { code: transformed, didWrap, skipped };\n}\n\n/**\n * Wraps global middleware arrays (requestMiddleware, functionMiddleware) in createStart() files.\n */\nexport function wrapGlobalMiddleware(code: string, id: string, debug: boolean): WrapResult {\n return wrapMiddlewareArrays(code, id, debug, /(requestMiddleware|functionMiddleware)\\s*:\\s*\\[([^\\]]*)\\]/g);\n}\n\n/**\n * Wraps route middleware arrays in createFileRoute() files.\n */\nexport function wrapRouteMiddleware(code: string, id: string, debug: boolean): WrapResult {\n return wrapMiddlewareArrays(code, id, debug, /(middleware)\\s*:\\s*\\[([^\\]]*)\\]/g);\n}\n\n/**\n * Wraps middleware arrays in createServerFn().middleware([...]) calls.\n */\nexport function wrapServerFnMiddleware(code: string, id: string, debug: boolean): WrapResult {\n return wrapMiddlewareArrays(code, id, debug, /(\\.middleware\\s*\\()\\s*\\[([^\\]]*)\\]\\s*\\)/g);\n}\n\n/**\n * Applies a wrap function to the current state and returns the updated state.\n */\nfunction applyWrap(\n state: FileTransformState,\n wrapFn: (code: string, id: string, debug: boolean) => WrapResult,\n id: string,\n debug: boolean,\n): FileTransformState {\n const result = wrapFn(state.code, id, debug);\n return {\n code: result.code,\n needsImport: state.needsImport || result.didWrap,\n skippedMiddlewares: [...state.skippedMiddlewares, ...result.skipped],\n };\n}\n\n/**\n * A Vite plugin that automatically instruments TanStack Start middlewares:\n * - `requestMiddleware` and `functionMiddleware` arrays in `createStart()`\n * - `middleware` arrays in `createFileRoute()` route definitions\n */\nexport function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin {\n const { enabled = true, debug = false } = options;\n\n return {\n name: 'sentry-tanstack-middleware-auto-instrument',\n enforce: 'pre',\n\n transform(code, id) {\n if (!enabled) {\n return null;\n }\n\n // Skip if not a TS/JS file\n if (!/\\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) {\n return null;\n }\n\n // Detect file types that should be instrumented\n const isStartFile = id.includes('start') && code.includes('createStart(');\n const isRouteFile = code.includes('createFileRoute(') && /middleware\\s*:\\s*\\[/.test(code);\n const isServerFnFile = code.includes('createServerFn') && /\\.middleware\\s*\\(\\s*\\[/.test(code);\n\n if (!isStartFile && !isRouteFile && !isServerFnFile) {\n return null;\n }\n\n // Skip if the user already did some manual wrapping\n if (code.includes('wrapMiddlewaresWithSentry')) {\n return null;\n }\n\n let fileTransformState: FileTransformState = {\n code,\n needsImport: false,\n skippedMiddlewares: [],\n };\n\n // Wrap middlewares\n if (isStartFile) {\n fileTransformState = applyWrap(fileTransformState, wrapGlobalMiddleware, id, debug);\n }\n if (isRouteFile) {\n fileTransformState = applyWrap(fileTransformState, wrapRouteMiddleware, id, debug);\n }\n if (isServerFnFile) {\n fileTransformState = applyWrap(fileTransformState, wrapServerFnMiddleware, id, debug);\n }\n\n // Warn about middlewares that couldn't be auto-wrapped\n if (fileTransformState.skippedMiddlewares.length > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[Sentry] Could not auto-instrument ${fileTransformState.skippedMiddlewares.join(' and ')} in ${id}. ` +\n 'To instrument these middlewares, use wrapMiddlewaresWithSentry() manually. ',\n );\n }\n\n // We didn't wrap any middlewares, so we don't need to import the wrapMiddlewaresWithSentry function\n if (!fileTransformState.needsImport) {\n return null;\n }\n\n return { code: addSentryImport(fileTransformState.code), map: null };\n },\n };\n}\n\n/**\n * Convert array contents to object shorthand syntax.\n * e.g., \"foo, bar, baz\" → \"{ foo, bar, baz }\"\n *\n * Returns null if contents contain non-identifier expressions (function calls, etc.)\n * which cannot be converted to object shorthand.\n */\nexport function arrayToObjectShorthand(contents: string): string | null {\n const items = contents\n .split(',')\n .map(s => s.trim())\n .filter(Boolean);\n\n // Only convert if all items are valid identifiers (no complex expressions)\n const allIdentifiers = items.every(item => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(item));\n if (!allIdentifiers || items.length === 0) {\n return null;\n }\n\n // Deduplicate to avoid invalid syntax like { foo, foo }\n const uniqueItems = [...new Set(items)];\n\n return `{ ${uniqueItems.join(', ')} }`;\n}\n\n/**\n * Adds the wrapMiddlewaresWithSentry import to the code.\n * Handles 'use client' and 'use server' directives by inserting the import after them.\n */\nexport function addSentryImport(code: string): string {\n const sentryImport = \"import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';\\n\";\n\n // Don't add the import if it already exists\n if (code.includes(sentryImport.trimEnd())) {\n return code;\n }\n\n // Check for 'use server' or 'use client' directives, these need to be before any imports\n const directiveMatch = code.match(/^(['\"])use (client|server)\\1;?\\s*\\n?/);\n\n if (!directiveMatch) {\n return sentryImport + code;\n }\n\n const directive = directiveMatch[0];\n return directive + sentryImport + code.slice(directive.length);\n}\n"],"names":[],"mappings":"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"}
|