UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

274 lines (254 loc) 17.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); // @ts-expect-error - magicast is ESM and TS complains about that. It works though const magicast_1 = require("magicast"); const templates_1 = require("../../src/nextjs/templates"); const utils_1 = require("../../src/nextjs/utils"); vitest_1.vi.mock('../../src/utils/clack/mcp-config', () => ({ offerProjectScopedMcpConfig: vitest_1.vi.fn().mockResolvedValue(undefined), })); (0, vitest_1.describe)('Next.js wizard double wrap prevention', () => { const mockWithSentryConfigOptionsTemplate = (0, templates_1.getWithSentryConfigOptionsTemplate)({ orgSlug: 'test-org', projectSlug: 'test-project', selfHosted: false, sentryUrl: 'https://sentry.io', tunnelRoute: false, }); (0, vitest_1.describe)('unwrapSentryConfigAst utility function', () => { (0, vitest_1.describe)('AST-based expression unwrapping', () => { (0, vitest_1.it)('keeps code without withSentryConfig', () => { const mod = (0, magicast_1.parseModule)('export default nextConfig'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const originalAST = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const resultAST = (0, utils_1.unwrapSentryConfigAst)(originalAST); (0, vitest_1.expect)(resultAST).toBe(originalAST); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST }); (0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"nextConfig"`); }); (0, vitest_1.it)('should handle plain object literal exports', () => { const mod = (0, magicast_1.parseModule)(`export default { nextConfig: { randomValue: true } }`); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const originalAST = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const resultAST = (0, utils_1.unwrapSentryConfigAst)(originalAST); (0, vitest_1.expect)(resultAST).toBe(originalAST); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(resultAST.type).toBe('ObjectExpression'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(resultAST.properties).toHaveLength(1); }); (0, vitest_1.it)('should unwrap withSentryConfig with options', () => { const mod = (0, magicast_1.parseModule)('export default withSentryConfig(nextConfig, { org: "test" })'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const wrappedAst = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST }); (0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"nextConfig"`); }); (0, vitest_1.it)('should unwrap withSentryConfig without options', () => { const mod = (0, magicast_1.parseModule)('export default withSentryConfig(nextConfig)'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const wrappedAst = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST }); (0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"nextConfig"`); }); (0, vitest_1.it)('should handle nested withSentryConfig calls', () => { const mod = (0, magicast_1.parseModule)('export default withSentryConfig(withSentryConfig(nextConfig, { org: "inner" }), { org: "outer" })'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const wrappedAst = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst); // Should unwrap one level and return the inner withSentryConfig call // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(resultAST.type).toBe('CallExpression'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(resultAST.callee.name).toBe('withSentryConfig'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST }); (0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"withSentryConfig(nextConfig, { org: "inner" })"`); }); (0, vitest_1.it)('should handle complex expressions', () => { const mod = (0, magicast_1.parseModule)('export default withSentryConfig(someComplexExpression.withMethods())'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const wrappedAst = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST }); (0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"someComplexExpression.withMethods()"`); }); (0, vitest_1.it)('should handle object literals', () => { const mod = (0, magicast_1.parseModule)('export default withSentryConfig({ next: "config", obj: { next: "nested" } }, { org: "test" })'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const wrappedAst = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const resultAST = (0, utils_1.unwrapSentryConfigAst)(wrappedAst); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const { code: exportDefaultCode } = (0, magicast_1.generateCode)({ $ast: resultAST }); (0, vitest_1.expect)(exportDefaultCode).toMatchInlineSnapshot(`"{ next: "config", obj: { next: "nested" } }"`); }); (0, vitest_1.it)('should return unchanged if not a withSentryConfig call', () => { const mod = (0, magicast_1.parseModule)('export default someOtherFunction(nextConfig)'); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const originalAST = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const resultAST = (0, utils_1.unwrapSentryConfigAst)(originalAST); (0, vitest_1.expect)(resultAST).toBe(originalAST); }); }); }); (0, vitest_1.describe)('MJS/TS files', () => { (0, vitest_1.it)('should unwrap existing withSentryConfig and re-wrap with new config using AST', () => { const existingMjsContent = `import { withSentryConfig } from "@sentry/nextjs"; const nextConfig = {}; export default withSentryConfig(nextConfig, { org: "old-org", project: "old-project", });`; const mod = (0, magicast_1.parseModule)(existingMjsContent); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const originalAST = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const unwrappedAst = (0, utils_1.unwrapSentryConfigAst)(originalAST); // Verify it returns the first argument (nextConfig identifier) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(unwrappedAst.type).toBe('Identifier'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(unwrappedAst.name).toBe('nextConfig'); // Create a fresh module to simulate the re-wrapping process const freshMod = (0, magicast_1.parseModule)(`import { withSentryConfig } from "@sentry/nextjs"; const nextConfig = {}; export default nextConfig;`); // Apply wrapping // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment freshMod.exports.default = (0, utils_1.wrapWithSentryConfig)(freshMod.exports.default, mockWithSentryConfigOptionsTemplate); const newCode = freshMod.generate().code; // Verify only one withSentryConfig call exists const withSentryConfigMatches = newCode.match(/withSentryConfig\s*\(/g); (0, vitest_1.expect)(withSentryConfigMatches).toHaveLength(1); (0, vitest_1.expect)(newCode).toContain('test-org'); (0, vitest_1.expect)(newCode).toContain('test-project'); (0, vitest_1.expect)(newCode).not.toContain('old-org'); (0, vitest_1.expect)(newCode).not.toContain('old-project'); }); (0, vitest_1.it)('should handle complex nested configurations using AST', () => { const existingMjsContent = `import { withSentryConfig } from "@sentry/nextjs"; const nextConfig = { experimental: { appDir: true } }; export default withSentryConfig( withSentryConfig(nextConfig, { org: "nested-org" }), { org: "outer-org" } );`; const mod = (0, magicast_1.parseModule)(existingMjsContent); // First unwrap ---- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const firstUnwrapAST = (0, utils_1.unwrapSentryConfigAst)(mod.exports.default.$ast); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(firstUnwrapAST.type).toBe('CallExpression'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(firstUnwrapAST.callee.name).toBe('withSentryConfig'); const { code: exportDefaultCode1 } = (0, magicast_1.generateCode)({ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment $ast: firstUnwrapAST, }); (0, vitest_1.expect)(exportDefaultCode1).toMatchInlineSnapshot(`"withSentryConfig(nextConfig, { org: "nested-org" })"`); // Second unwrap ---- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const secondUnwrapAST = (0, utils_1.unwrapSentryConfigAst)(firstUnwrapAST); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(secondUnwrapAST.type).toBe('Identifier'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(secondUnwrapAST.name).toBe('nextConfig'); const { code: exportDefaultCode2 } = (0, magicast_1.generateCode)({ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment $ast: secondUnwrapAST, }); (0, vitest_1.expect)(exportDefaultCode2).toMatchInlineSnapshot(`"nextConfig"`); }); (0, vitest_1.it)('should handle simple export without existing withSentryConfig using AST', () => { const simpleMjsContent = `const nextConfig = {}; export default nextConfig;`; const mod = (0, magicast_1.parseModule)(simpleMjsContent); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const originalAST = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const unwrappedAst = (0, utils_1.unwrapSentryConfigAst)(originalAST); (0, vitest_1.expect)(unwrappedAst).toBe(originalAST); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment mod.exports.default = (0, utils_1.wrapWithSentryConfig)(mod.exports.default, mockWithSentryConfigOptionsTemplate); const newCode = mod.generate().code; // Should have exactly one withSentryConfig call const withSentryConfigMatches = newCode.match(/withSentryConfig\s*\(/g); (0, vitest_1.expect)(withSentryConfigMatches).toHaveLength(1); (0, vitest_1.expect)(newCode).toMatchInlineSnapshot(` "const nextConfig = {}; export default withSentryConfig(nextConfig, ${sentryOptionsSnapshot});" `); }); (0, vitest_1.it)('should handle withSentryConfig(nextConfig) without options using AST', () => { const existingMjsContent = `import { withSentryConfig } from "@sentry/nextjs"; const nextConfig = {}; export default withSentryConfig(nextConfig);`; const mod = (0, magicast_1.parseModule)(existingMjsContent); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access const originalAST = mod.exports.default.$ast; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const unwrappedAst = (0, utils_1.unwrapSentryConfigAst)(originalAST); // Verify it returns the first argument (nextConfig identifier) // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(unwrappedAst.type).toBe('Identifier'); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (0, vitest_1.expect)(unwrappedAst.name).toBe('nextConfig'); // Simulate the re-wrapping process const freshMod = (0, magicast_1.parseModule)(`import { withSentryConfig } from "@sentry/nextjs"; const nextConfig = {}; export default nextConfig;`); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment freshMod.exports.default = (0, utils_1.wrapWithSentryConfig)(freshMod.exports.default, mockWithSentryConfigOptionsTemplate); const newCode = freshMod.generate().code; // Should have exactly one withSentryConfig call const withSentryConfigMatches = newCode.match(/withSentryConfig\s*\(/g); (0, vitest_1.expect)(withSentryConfigMatches).toHaveLength(1); (0, vitest_1.expect)(newCode).not.toContain('withSentryConfig(withSentryConfig('); (0, vitest_1.expect)(newCode).toMatch(/withSentryConfig\s*\(\s*nextConfig\s*,/); }); }); }); const sentryOptionsSnapshot = `{ // For all available options, see: // https://www.npmjs.com/package/@sentry/webpack-plugin#options org: "test-org", project: "test-project", // Only print logs for uploading source maps in CI silent: !process.env.CI, // For all available options, see: // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ // Upload a larger set of source maps for prettier stack traces (increases build time) widenClientFileUpload: true, // Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. // This can increase your server load as well as your hosting bill. // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client- // side errors will fail. // tunnelRoute: "/monitoring", webpack: { // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.) // See the following for more information: // https://docs.sentry.io/product/crons/ // https://vercel.com/docs/cron-jobs automaticVercelMonitors: true, // Tree-shaking options for reducing bundle size treeshake: { // Automatically tree-shake Sentry logger statements to reduce bundle size removeDebugLogging: true, }, } }`; //# sourceMappingURL=wizard-double-wrap-prevention.test.js.map