@sentry/wizard 6.1.2 → 6.3.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/CHANGELOG.md +25 -0
- package/dist/e2e-tests/tests/angular-17.test.js +5 -0
- package/dist/e2e-tests/tests/angular-17.test.js.map +1 -1
- package/dist/e2e-tests/tests/angular-19.test.js +5 -0
- package/dist/e2e-tests/tests/angular-19.test.js.map +1 -1
- package/dist/e2e-tests/tests/expo.test.js +9 -2
- package/dist/e2e-tests/tests/expo.test.js.map +1 -1
- package/dist/e2e-tests/tests/flutter.test.js +32 -4
- package/dist/e2e-tests/tests/flutter.test.js.map +1 -1
- package/dist/e2e-tests/tests/nextjs-14.test.js +4 -3
- package/dist/e2e-tests/tests/nextjs-14.test.js.map +1 -1
- package/dist/e2e-tests/tests/nextjs-15.test.js +17 -4
- package/dist/e2e-tests/tests/nextjs-15.test.js.map +1 -1
- package/dist/e2e-tests/tests/nuxt-3.test.js +9 -2
- package/dist/e2e-tests/tests/nuxt-3.test.js.map +1 -1
- package/dist/e2e-tests/tests/nuxt-4.test.js +9 -2
- package/dist/e2e-tests/tests/nuxt-4.test.js.map +1 -1
- package/dist/e2e-tests/tests/react-native.test.js +8 -1
- package/dist/e2e-tests/tests/react-native.test.js.map +1 -1
- package/dist/e2e-tests/tests/remix.test.js +19 -4
- package/dist/e2e-tests/tests/remix.test.js.map +1 -1
- package/dist/e2e-tests/tests/sveltekit.test.js +16 -2
- package/dist/e2e-tests/tests/sveltekit.test.js.map +1 -1
- package/dist/lib/Steps/Integrations/Electron.js +4 -0
- package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
- package/dist/src/android/android-wizard.js +3 -0
- package/dist/src/android/android-wizard.js.map +1 -1
- package/dist/src/angular/angular-wizard.js +3 -0
- package/dist/src/angular/angular-wizard.js.map +1 -1
- package/dist/src/apple/apple-wizard.js +13 -0
- package/dist/src/apple/apple-wizard.js.map +1 -1
- package/dist/src/apple/code-tools.d.ts +1 -1
- package/dist/src/apple/code-tools.js +3 -3
- package/dist/src/apple/code-tools.js.map +1 -1
- package/dist/src/apple/inject-code-snippet.d.ts +2 -1
- package/dist/src/apple/inject-code-snippet.js +2 -2
- package/dist/src/apple/inject-code-snippet.js.map +1 -1
- package/dist/src/apple/templates.d.ts +2 -2
- package/dist/src/apple/templates.js +22 -6
- package/dist/src/apple/templates.js.map +1 -1
- package/dist/src/flutter/code-tools.d.ts +1 -0
- package/dist/src/flutter/code-tools.js +6 -0
- package/dist/src/flutter/code-tools.js.map +1 -1
- package/dist/src/flutter/flutter-wizard.js +3 -0
- package/dist/src/flutter/flutter-wizard.js.map +1 -1
- package/dist/src/flutter/templates.d.ts +1 -0
- package/dist/src/flutter/templates.js +5 -0
- package/dist/src/flutter/templates.js.map +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +14 -7
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/utils.d.ts +8 -0
- package/dist/src/nextjs/utils.js +36 -1
- package/dist/src/nextjs/utils.js.map +1 -1
- package/dist/src/nuxt/nuxt-wizard.js +3 -0
- package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
- package/dist/src/react-native/expo-metro.d.ts +2 -2
- package/dist/src/react-native/expo-metro.js +32 -27
- package/dist/src/react-native/expo-metro.js.map +1 -1
- package/dist/src/react-native/metro.d.ts +4 -4
- package/dist/src/react-native/metro.js +39 -17
- package/dist/src/react-native/metro.js.map +1 -1
- package/dist/src/react-native/react-native-wizard.js +3 -0
- package/dist/src/react-native/react-native-wizard.js.map +1 -1
- package/dist/src/remix/codemods/root.d.ts +1 -0
- package/dist/src/remix/codemods/root.js +30 -2
- package/dist/src/remix/codemods/root.js.map +1 -1
- package/dist/src/remix/remix-wizard.d.ts +4 -0
- package/dist/src/remix/remix-wizard.js +7 -0
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.js +3 -0
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/utils/clack/mcp-config.d.ts +7 -0
- package/dist/src/utils/clack/mcp-config.js +427 -0
- package/dist/src/utils/clack/mcp-config.js.map +1 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/test/angular/angular-wizard.test.js +3 -0
- package/dist/test/angular/angular-wizard.test.js.map +1 -1
- package/dist/test/apple/code-tools.test.js +62 -14
- package/dist/test/apple/code-tools.test.js.map +1 -1
- package/dist/test/apple/templates.test.js +71 -2
- package/dist/test/apple/templates.test.js.map +1 -1
- package/dist/test/flutter/code-tools.test.js +1 -0
- package/dist/test/flutter/code-tools.test.js.map +1 -1
- package/dist/test/flutter/templates.test.js +31 -1
- package/dist/test/flutter/templates.test.js.map +1 -1
- package/dist/test/nextjs/wizard-double-wrap-prevention.test.d.ts +1 -0
- package/dist/test/nextjs/wizard-double-wrap-prevention.test.js +269 -0
- package/dist/test/nextjs/wizard-double-wrap-prevention.test.js.map +1 -0
- package/dist/test/nuxt/templates.test.js +3 -0
- package/dist/test/nuxt/templates.test.js.map +1 -1
- package/dist/test/react-native/expo-metro.test.js +3 -3
- package/dist/test/react-native/expo-metro.test.js.map +1 -1
- package/dist/test/react-native/metro.test.js +76 -15
- package/dist/test/react-native/metro.test.js.map +1 -1
- package/dist/test/remix/root.test.js +229 -0
- package/dist/test/remix/root.test.js.map +1 -1
- package/dist/test/sveltekit/templates.test.js +3 -0
- package/dist/test/sveltekit/templates.test.js.map +1 -1
- package/dist/test/utils/clack/mcp-config.test.d.ts +1 -0
- package/dist/test/utils/clack/mcp-config.test.js +520 -0
- package/dist/test/utils/clack/mcp-config.test.js.map +1 -0
- package/package.json +1 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.test.js","sourceRoot":"","sources":["../../../test/remix/root.test.ts"],"names":[],"mappings":";;AAAA,kFAAkF;AAClF,uCAAuC;AACvC,mCAA8C;AAC9C,wDAAkE;AAElE,IAAA,iBAAQ,EAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,kCAAkC;QAClC,MAAM,eAAe,GAAG,IAAA,sBAAW,EAAC;;;;;;KAMnC,CAAC,CAAC;QAEH,IAAA,wBAAiB,EAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC;QAE/C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,qBAAqB,CAAC;;;;;;;;;KASpC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { parseModule } from 'magicast';\nimport { describe, expect, it } from 'vitest';\nimport { wrapAppWithSentry } from '../../src/remix/codemods/root';\n\ndescribe('wrapAppWithSentry', () => {\n it('should wrap the app with Sentry', () => {\n // Empty root.tsx file for testing\n const originalRootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n wrapAppWithSentry(originalRootAst, 'root.tsx');\n\n const result = originalRootAst.generate().code;\n\n expect(result).toMatchInlineSnapshot(`\n \"import {withSentry} from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n export default withSentry(App);\"\n `);\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"root.test.js","sourceRoot":"","sources":["../../../test/remix/root.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { parseModule } from 'magicast';\nimport { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n wrapAppWithSentry,\n isWithSentryAlreadyUsed,\n instrumentRoot,\n} from '../../src/remix/codemods/root';\n\nvi.mock('@clack/prompts', () => {\n const mock = {\n log: {\n warn: vi.fn(),\n info: vi.fn(),\n success: vi.fn(),\n },\n };\n return {\n default: mock,\n ...mock,\n };\n});\n\nvi.mock('../../src/utils/clack/mcp-config', () => ({\n offerProjectScopedMcpConfig: vi.fn().mockResolvedValue(undefined),\n}));\n\ndescribe('wrapAppWithSentry', () => {\n it('should wrap the app with Sentry', () => {\n // Empty root.tsx file for testing\n const originalRootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n wrapAppWithSentry(originalRootAst, 'root.tsx');\n\n const result = originalRootAst.generate().code;\n\n expect(result).toMatchInlineSnapshot(`\n \"import {withSentry} from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n export default withSentry(App);\"\n `);\n });\n});\n\ndescribe('isWithSentryAlreadyUsed', () => {\n it('should return false when withSentry is not used', () => {\n const rootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(false);\n });\n\n it('should return true when withSentry is used in default export', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n export default withSentry(App);\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(true);\n });\n\n it('should return true when withSentry is used in a variable assignment', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n const WrappedApp = withSentry(App);\n export default WrappedApp;\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(true);\n });\n\n it('should return true when withSentry is used inside a function', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n function App() {\n return <Outlet />;\n }\n\n function createApp() {\n return withSentry(App);\n }\n\n export default createApp();\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(true);\n });\n\n it('should return false when withSentry is imported but not used', () => {\n const rootAst = parseModule(`\n import { withSentry } from '@sentry/remix';\n import { Outlet } from '@remix-run/react';\n\n export default function App() {\n return <Outlet />;\n }\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(false);\n });\n\n it('should return false when a different function with similar name is used', () => {\n const rootAst = parseModule(`\n import { Outlet } from '@remix-run/react';\n\n function withSentryLike() {\n return null;\n }\n\n export default function App() {\n withSentryLike();\n return <Outlet />;\n }\n `);\n\n expect(isWithSentryAlreadyUsed(rootAst)).toBe(false);\n });\n});\n\ndescribe('instrumentRoot', () => {\n const fixturesDir = path.join(__dirname, 'fixtures');\n const tmpDir = path.join(fixturesDir, 'tmp');\n\n beforeEach(() => {\n vi.clearAllMocks();\n // Mock process.cwd() to return the fixtures directory\n vi.spyOn(process, 'cwd').mockReturnValue(fixturesDir);\n });\n\n afterEach(() => {\n // Clean up any temporary files\n if (fs.existsSync(tmpDir)) {\n try {\n // Remove files first, then directory\n const appDir = path.join(tmpDir, 'app');\n if (fs.existsSync(appDir)) {\n const files = fs.readdirSync(appDir);\n files.forEach((file) => {\n fs.unlinkSync(path.join(appDir, file));\n });\n fs.rmdirSync(appDir);\n }\n\n const files = fs.readdirSync(tmpDir);\n files.forEach((file) => {\n const filePath = path.join(tmpDir, file);\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n fs.rmdirSync(filePath);\n } else {\n fs.unlinkSync(filePath);\n }\n });\n } catch (error) {\n // Ignore cleanup errors in tests\n }\n }\n });\n\n it('should add ErrorBoundary and wrap app with Sentry when no ErrorBoundary exists and withSentry is not used', async () => {\n // Copy fixture to tmp directory for testing\n const srcFile = path.join(fixturesDir, 'root-no-error-boundary.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n // Create app directory and copy file\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n // Mock process.cwd() to return tmpDir\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n // Check that the file was modified correctly\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n 'import { captureRemixErrorBoundaryError, withSentry } from \"@sentry/remix\";',\n );\n expect(modifiedContent).toContain(\n \"import { Outlet, useRouteError } from '@remix-run/react';\",\n );\n expect(modifiedContent).toContain('withSentry(App)');\n expect(modifiedContent).toContain('const ErrorBoundary = () => {');\n });\n\n it('should wrap app with Sentry when ErrorBoundary exists but no Sentry content', async () => {\n const srcFile = path.join(fixturesDir, 'root-with-error-boundary.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n 'import { captureRemixErrorBoundaryError, withSentry } from \"@sentry/remix\";',\n );\n expect(modifiedContent).toContain('withSentry(App)');\n });\n\n it('should wrap app with Sentry when ErrorBoundary exists with Sentry content but withSentry is not used', async () => {\n const srcFile = path.join(\n fixturesDir,\n 'root-with-sentry-error-boundary.tsx',\n );\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n \"import { captureRemixErrorBoundaryError, withSentry } from '@sentry/remix';\",\n );\n expect(modifiedContent).toContain('withSentry(App)');\n });\n\n it('should not wrap app when withSentry is already used', async () => {\n const srcFile = path.join(fixturesDir, 'root-already-wrapped.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n // Should not add withSentry import or wrap again\n expect(modifiedContent).toContain('withSentry(App)');\n\n // Count occurrences of withSentry to ensure it's not duplicated\n const withSentryOccurrences = (modifiedContent.match(/withSentry/g) || [])\n .length;\n expect(withSentryOccurrences).toBe(2); // One import, one usage\n\n // The content should remain largely the same since withSentry is already used\n expect(modifiedContent).toContain('export default withSentry(App)');\n });\n\n it('should handle ErrorBoundary as variable declaration', async () => {\n const srcFile = path.join(fixturesDir, 'root-variable-error-boundary.tsx');\n const appDir = path.join(tmpDir, 'app');\n\n fs.mkdirSync(appDir, { recursive: true });\n fs.copyFileSync(srcFile, path.join(appDir, 'root.tsx'));\n\n vi.spyOn(process, 'cwd').mockReturnValue(tmpDir);\n\n await instrumentRoot('root.tsx');\n\n const modifiedContent = fs.readFileSync(\n path.join(appDir, 'root.tsx'),\n 'utf8',\n );\n\n expect(modifiedContent).toContain(\n 'import { captureRemixErrorBoundaryError, withSentry } from \"@sentry/remix\";',\n );\n expect(modifiedContent).toContain('withSentry(App)');\n });\n});\n"]}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const vitest_1 = require("vitest");
|
|
4
4
|
const templates_1 = require("../../src/sveltekit/templates");
|
|
5
|
+
vitest_1.vi.mock('../../src/utils/clack/mcp-config', () => ({
|
|
6
|
+
offerProjectScopedMcpConfig: vitest_1.vi.fn().mockResolvedValue(undefined),
|
|
7
|
+
}));
|
|
5
8
|
(0, vitest_1.describe)('getClientHooksTemplate', () => {
|
|
6
9
|
(0, vitest_1.it)('should generate client hooks template with all features enabled', () => {
|
|
7
10
|
const result = (0, templates_1.getClientHooksTemplate)('https://sentry.io/123', {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.test.js","sourceRoot":"","sources":["../../../test/sveltekit/templates.test.ts"],"names":[],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"file":"templates.test.js","sourceRoot":"","sources":["../../../test/sveltekit/templates.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it, vi } from 'vitest';\nimport {\n getClientHooksTemplate,\n getServerHooksTemplate,\n} from '../../src/sveltekit/templates';\n\nvi.mock('../../src/utils/clack/mcp-config', () => ({\n offerProjectScopedMcpConfig: vi.fn().mockResolvedValue(undefined),\n}));\n\ndescribe('getClientHooksTemplate', () => {\n it('should generate client hooks template with all features enabled', () => {\n const result = getClientHooksTemplate('https://sentry.io/123', {\n performance: true,\n replay: true,\n logs: true,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { handleErrorWithSentry, replayIntegration } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n tracesSampleRate: 1.0,\n\n // Enable logs to be sent to Sentry\n enableLogs: true,\n\n // This sets the sample rate to be 10%. You may want this to be 100% while\n // in development and sample at a lower rate in production\n replaysSessionSampleRate: 0.1,\n\n // If the entire session is not sampled, use the below sample rate to sample\n // sessions when an error occurs.\n replaysOnErrorSampleRate: 1.0,\n\n // If you don't want to use Session Replay, just remove the line below:\n integrations: [replayIntegration()],\n });\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n\n it('should generate client hooks template when performance disabled', () => {\n const result = getClientHooksTemplate('https://sentry.io/123', {\n performance: false,\n replay: true,\n logs: false,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { handleErrorWithSentry, replayIntegration } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n\n // This sets the sample rate to be 10%. You may want this to be 100% while\n // in development and sample at a lower rate in production\n replaysSessionSampleRate: 0.1,\n\n // If the entire session is not sampled, use the below sample rate to sample\n // sessions when an error occurs.\n replaysOnErrorSampleRate: 1.0,\n\n // If you don't want to use Session Replay, just remove the line below:\n integrations: [replayIntegration()],\n });\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n\n it('should generate client hooks template when replay disabled', () => {\n const result = getClientHooksTemplate('https://sentry.io/123', {\n performance: true,\n replay: false,\n logs: false,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { handleErrorWithSentry, replayIntegration } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n tracesSampleRate: 1.0,\n\n\n\n });\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n\n it('should generate client hooks template with only logs enabled', () => {\n const result = getClientHooksTemplate('https://sentry.io/123', {\n performance: false,\n replay: false,\n logs: true,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { handleErrorWithSentry, replayIntegration } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n // Enable logs to be sent to Sentry\n enableLogs: true,\n\n\n });\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n});\n\ndescribe('getServerHooksTemplate', () => {\n it('should generate server hooks template with all features enabled', () => {\n const result = getServerHooksTemplate('https://sentry.io/123', {\n performance: true,\n replay: true,\n logs: true,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { sequence } from \"@sveltejs/kit/hooks\";\n import { handleErrorWithSentry, sentryHandle } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n tracesSampleRate: 1.0,\n\n // Enable logs to be sent to Sentry\n enableLogs: true,\n\n // uncomment the line below to enable Spotlight (https://spotlightjs.com)\n // spotlight: import.meta.env.DEV,\n });\n\n // If you have custom handlers, make sure to place them after \\`sentryHandle()\\` in the \\`sequence\\` function.\n export const handle = sequence(sentryHandle());\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n\n it('should generate server hooks template when performance disabled', () => {\n const result = getServerHooksTemplate('https://sentry.io/123', {\n performance: false,\n replay: true,\n logs: false,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { sequence } from \"@sveltejs/kit/hooks\";\n import { handleErrorWithSentry, sentryHandle } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n\n // uncomment the line below to enable Spotlight (https://spotlightjs.com)\n // spotlight: import.meta.env.DEV,\n });\n\n // If you have custom handlers, make sure to place them after \\`sentryHandle()\\` in the \\`sequence\\` function.\n export const handle = sequence(sentryHandle());\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n\n it('should generate server hooks template with only logs enabled', () => {\n const result = getServerHooksTemplate('https://sentry.io/123', {\n performance: false,\n replay: false,\n logs: true,\n });\n\n expect(result).toMatchInlineSnapshot(`\n \"import { sequence } from \"@sveltejs/kit/hooks\";\n import { handleErrorWithSentry, sentryHandle } from \"@sentry/sveltekit\";\n import * as Sentry from '@sentry/sveltekit';\n\n Sentry.init({\n dsn: 'https://sentry.io/123',\n\n // Enable logs to be sent to Sentry\n enableLogs: true,\n\n // uncomment the line below to enable Spotlight (https://spotlightjs.com)\n // spotlight: import.meta.env.DEV,\n });\n\n // If you have custom handlers, make sure to place them after \\`sentryHandle()\\` in the \\`sequence\\` function.\n export const handle = sequence(sentryHandle());\n\n // If you have a custom error handler, pass it to \\`handleErrorWithSentry\\`\n export const handleError = handleErrorWithSentry();\n \"\n `);\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const vitest_1 = require("vitest");
|
|
27
|
+
const fs = __importStar(require("node:fs"));
|
|
28
|
+
const path = __importStar(require("node:path"));
|
|
29
|
+
const childProcess = __importStar(require("node:child_process"));
|
|
30
|
+
const mcp_config_1 = require("../../../src/utils/clack/mcp-config");
|
|
31
|
+
// Mock the clack utils which wrap the prompts
|
|
32
|
+
vitest_1.vi.mock('../../../src/utils/clack', () => ({
|
|
33
|
+
abortIfCancelled: vitest_1.vi.fn((value) => Promise.resolve(value)),
|
|
34
|
+
showCopyPasteInstructions: vitest_1.vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
// Mock the external dependencies
|
|
37
|
+
vitest_1.vi.mock('@clack/prompts', () => ({
|
|
38
|
+
confirm: vitest_1.vi.fn(),
|
|
39
|
+
select: vitest_1.vi.fn(),
|
|
40
|
+
isCancel: vitest_1.vi.fn(() => false),
|
|
41
|
+
cancel: vitest_1.vi.fn(),
|
|
42
|
+
log: {
|
|
43
|
+
success: vitest_1.vi.fn(),
|
|
44
|
+
info: vitest_1.vi.fn(),
|
|
45
|
+
warn: vitest_1.vi.fn(),
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
vitest_1.vi.mock('node:fs');
|
|
49
|
+
vitest_1.vi.mock('node:child_process');
|
|
50
|
+
(0, vitest_1.describe)('mcp-config', () => {
|
|
51
|
+
const getMocks = async () => {
|
|
52
|
+
const clack = await vitest_1.vi.importMock('@clack/prompts');
|
|
53
|
+
const clackUtils = await vitest_1.vi.importMock('../../../src/utils/clack');
|
|
54
|
+
return { clack, clackUtils };
|
|
55
|
+
};
|
|
56
|
+
(0, vitest_1.beforeEach)(() => {
|
|
57
|
+
vitest_1.vi.clearAllMocks();
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.afterEach)(() => {
|
|
60
|
+
vitest_1.vi.restoreAllMocks();
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.describe)('offerProjectScopedMcpConfig', () => {
|
|
63
|
+
(0, vitest_1.it)('should return early if user declines MCP config', async () => {
|
|
64
|
+
const { clack, clackUtils } = await getMocks();
|
|
65
|
+
vitest_1.vi.mocked(clack.select).mockResolvedValue('no');
|
|
66
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
67
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
68
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
69
|
+
message: vitest_1.expect.stringContaining('Optionally add a project-scoped MCP server configuration'),
|
|
70
|
+
options: vitest_1.expect.arrayContaining([
|
|
71
|
+
vitest_1.expect.objectContaining({ value: 'yes' }),
|
|
72
|
+
vitest_1.expect.objectContaining({ value: 'no' }),
|
|
73
|
+
vitest_1.expect.objectContaining({ value: 'explain' }),
|
|
74
|
+
]),
|
|
75
|
+
initialValue: 'yes',
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
(0, vitest_1.it)('should configure for Cursor when selected', async () => {
|
|
79
|
+
const { clack, clackUtils } = await getMocks();
|
|
80
|
+
vitest_1.vi.mocked(clack.select)
|
|
81
|
+
.mockResolvedValueOnce('yes')
|
|
82
|
+
.mockResolvedValueOnce('cursor');
|
|
83
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
84
|
+
const mockReadFile = vitest_1.vi
|
|
85
|
+
.fn()
|
|
86
|
+
.mockRejectedValue(new Error('File not found'));
|
|
87
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
88
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
89
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
90
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
91
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
92
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
93
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledTimes(2);
|
|
94
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenNthCalledWith(2, vitest_1.expect.objectContaining({
|
|
95
|
+
message: 'Which editor do you want to configure?',
|
|
96
|
+
options: vitest_1.expect.arrayContaining([
|
|
97
|
+
vitest_1.expect.objectContaining({ value: 'cursor' }),
|
|
98
|
+
vitest_1.expect.objectContaining({ value: 'vscode' }),
|
|
99
|
+
vitest_1.expect.objectContaining({ value: 'claudeCode' }),
|
|
100
|
+
]),
|
|
101
|
+
}));
|
|
102
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.cursor/mcp.json'), vitest_1.expect.stringContaining('"mcpServers"'), 'utf8');
|
|
103
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.cursor/mcp.json'));
|
|
104
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith('Added project-scoped Sentry MCP configuration.');
|
|
105
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('reload your editor'));
|
|
106
|
+
});
|
|
107
|
+
(0, vitest_1.it)('should configure for VS Code when selected', async () => {
|
|
108
|
+
const { clack, clackUtils } = await getMocks();
|
|
109
|
+
vitest_1.vi.mocked(clack.select)
|
|
110
|
+
.mockResolvedValueOnce('yes')
|
|
111
|
+
.mockResolvedValueOnce('vscode');
|
|
112
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
113
|
+
const mockReadFile = vitest_1.vi
|
|
114
|
+
.fn()
|
|
115
|
+
.mockRejectedValue(new Error('File not found'));
|
|
116
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
117
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
118
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
119
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
120
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
121
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
122
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.vscode/mcp.json'), vitest_1.expect.stringContaining('"servers"'), 'utf8');
|
|
123
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.vscode/mcp.json'));
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.it)('should configure for Claude Code when selected', async () => {
|
|
126
|
+
const { clack, clackUtils } = await getMocks();
|
|
127
|
+
vitest_1.vi.mocked(clack.select)
|
|
128
|
+
.mockResolvedValueOnce('yes')
|
|
129
|
+
.mockResolvedValueOnce('claudeCode');
|
|
130
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
131
|
+
const mockReadFile = vitest_1.vi
|
|
132
|
+
.fn()
|
|
133
|
+
.mockRejectedValue(new Error('File not found'));
|
|
134
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
135
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
136
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
137
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
138
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
139
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
140
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.mcp.json'), vitest_1.expect.stringContaining('"mcpServers"'), 'utf8');
|
|
141
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.mcp.json'));
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.it)('should update existing Cursor config file', async () => {
|
|
144
|
+
const { clack, clackUtils } = await getMocks();
|
|
145
|
+
vitest_1.vi.mocked(clack.select)
|
|
146
|
+
.mockResolvedValueOnce('yes')
|
|
147
|
+
.mockResolvedValueOnce('cursor');
|
|
148
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
149
|
+
const existingConfig = JSON.stringify({
|
|
150
|
+
mcpServers: {
|
|
151
|
+
OtherServer: {
|
|
152
|
+
url: 'https://other.example.com',
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
const mockReadFile = vitest_1.vi.fn().mockResolvedValue(existingConfig);
|
|
157
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
158
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
159
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
160
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
161
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
162
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
163
|
+
(0, vitest_1.expect)(mockReadFile).toHaveBeenCalled();
|
|
164
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalledWith(vitest_1.expect.stringContaining('.cursor/mcp.json'), vitest_1.expect.stringContaining('Sentry'), 'utf8');
|
|
165
|
+
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
166
|
+
(0, vitest_1.expect)(writtenContent.mcpServers).toHaveProperty('OtherServer');
|
|
167
|
+
(0, vitest_1.expect)(writtenContent.mcpServers).toHaveProperty('Sentry');
|
|
168
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith('Updated .cursor/mcp.json');
|
|
169
|
+
});
|
|
170
|
+
(0, vitest_1.it)('should update existing VS Code config file', async () => {
|
|
171
|
+
const { clack, clackUtils } = await getMocks();
|
|
172
|
+
vitest_1.vi.mocked(clack.select)
|
|
173
|
+
.mockResolvedValueOnce('yes')
|
|
174
|
+
.mockResolvedValueOnce('vscode');
|
|
175
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
176
|
+
const existingConfig = JSON.stringify({
|
|
177
|
+
servers: {
|
|
178
|
+
OtherServer: {
|
|
179
|
+
url: 'https://other.example.com',
|
|
180
|
+
type: 'http',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
const mockReadFile = vitest_1.vi.fn().mockResolvedValue(existingConfig);
|
|
185
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
186
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
187
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
188
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
189
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
190
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
191
|
+
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
192
|
+
(0, vitest_1.expect)(writtenContent.servers).toHaveProperty('OtherServer');
|
|
193
|
+
(0, vitest_1.expect)(writtenContent.servers).toHaveProperty('Sentry');
|
|
194
|
+
(0, vitest_1.expect)(writtenContent.servers?.Sentry).toHaveProperty('type', 'http');
|
|
195
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith('Updated .vscode/mcp.json');
|
|
196
|
+
});
|
|
197
|
+
(0, vitest_1.it)('should update existing Claude Code config file', async () => {
|
|
198
|
+
const { clack, clackUtils } = await getMocks();
|
|
199
|
+
vitest_1.vi.mocked(clack.select)
|
|
200
|
+
.mockResolvedValueOnce('yes')
|
|
201
|
+
.mockResolvedValueOnce('claudeCode');
|
|
202
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
203
|
+
const existingConfig = JSON.stringify({
|
|
204
|
+
mcpServers: {
|
|
205
|
+
OtherServer: {
|
|
206
|
+
url: 'https://other.example.com',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
const mockReadFile = vitest_1.vi.fn().mockResolvedValue(existingConfig);
|
|
211
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
212
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
213
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
214
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
215
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
216
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
217
|
+
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
218
|
+
(0, vitest_1.expect)(writtenContent.mcpServers).toHaveProperty('OtherServer');
|
|
219
|
+
(0, vitest_1.expect)(writtenContent.mcpServers).toHaveProperty('Sentry');
|
|
220
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith('Updated .mcp.json');
|
|
221
|
+
});
|
|
222
|
+
(0, vitest_1.it)('should handle file write errors gracefully for Cursor', async () => {
|
|
223
|
+
const { clack, clackUtils } = await getMocks();
|
|
224
|
+
vitest_1.vi.mocked(clack.select)
|
|
225
|
+
.mockResolvedValueOnce('yes')
|
|
226
|
+
.mockResolvedValueOnce('cursor');
|
|
227
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
228
|
+
const mockReadFile = vitest_1.vi
|
|
229
|
+
.fn()
|
|
230
|
+
.mockRejectedValue(new Error('File not found'));
|
|
231
|
+
const mockWriteFile = vitest_1.vi
|
|
232
|
+
.fn()
|
|
233
|
+
.mockRejectedValue(new Error('Permission denied'));
|
|
234
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
235
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
236
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
237
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
238
|
+
await (0, vitest_1.expect)((0, mcp_config_1.offerProjectScopedMcpConfig)()).resolves.toBeUndefined();
|
|
239
|
+
(0, vitest_1.expect)(clack.log.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to write MCP config automatically'));
|
|
240
|
+
(0, vitest_1.expect)(clackUtils.showCopyPasteInstructions).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
241
|
+
filename: path.join('.cursor', 'mcp.json'),
|
|
242
|
+
codeSnippet: vitest_1.expect.stringContaining('mcpServers'),
|
|
243
|
+
hint: 'create the file if it does not exist',
|
|
244
|
+
}));
|
|
245
|
+
});
|
|
246
|
+
(0, vitest_1.it)('should handle file write errors gracefully for VS Code', async () => {
|
|
247
|
+
const { clack, clackUtils } = await getMocks();
|
|
248
|
+
vitest_1.vi.mocked(clack.select)
|
|
249
|
+
.mockResolvedValueOnce('yes')
|
|
250
|
+
.mockResolvedValueOnce('vscode');
|
|
251
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
252
|
+
const mockReadFile = vitest_1.vi
|
|
253
|
+
.fn()
|
|
254
|
+
.mockRejectedValue(new Error('File not found'));
|
|
255
|
+
const mockWriteFile = vitest_1.vi
|
|
256
|
+
.fn()
|
|
257
|
+
.mockRejectedValue(new Error('Permission denied'));
|
|
258
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
259
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
260
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
261
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
262
|
+
await (0, vitest_1.expect)((0, mcp_config_1.offerProjectScopedMcpConfig)()).resolves.toBeUndefined();
|
|
263
|
+
(0, vitest_1.expect)(clackUtils.showCopyPasteInstructions).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
264
|
+
filename: path.join('.vscode', 'mcp.json'),
|
|
265
|
+
codeSnippet: vitest_1.expect.stringContaining('servers'),
|
|
266
|
+
hint: 'create the file if it does not exist',
|
|
267
|
+
}));
|
|
268
|
+
});
|
|
269
|
+
(0, vitest_1.it)('should handle file write errors gracefully for Claude Code', async () => {
|
|
270
|
+
const { clack, clackUtils } = await getMocks();
|
|
271
|
+
vitest_1.vi.mocked(clack.select)
|
|
272
|
+
.mockResolvedValueOnce('yes')
|
|
273
|
+
.mockResolvedValueOnce('claudeCode');
|
|
274
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
275
|
+
const mockReadFile = vitest_1.vi
|
|
276
|
+
.fn()
|
|
277
|
+
.mockRejectedValue(new Error('File not found'));
|
|
278
|
+
const mockWriteFile = vitest_1.vi
|
|
279
|
+
.fn()
|
|
280
|
+
.mockRejectedValue(new Error('Permission denied'));
|
|
281
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
282
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
283
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
284
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
285
|
+
await (0, vitest_1.expect)((0, mcp_config_1.offerProjectScopedMcpConfig)()).resolves.toBeUndefined();
|
|
286
|
+
(0, vitest_1.expect)(clackUtils.showCopyPasteInstructions).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
287
|
+
filename: '.mcp.json',
|
|
288
|
+
codeSnippet: vitest_1.expect.stringContaining('mcpServers'),
|
|
289
|
+
hint: 'create the file if it does not exist',
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
(0, vitest_1.it)('should handle update errors and show copy-paste instructions', async () => {
|
|
293
|
+
const { clack, clackUtils } = await getMocks();
|
|
294
|
+
vitest_1.vi.mocked(clack.select)
|
|
295
|
+
.mockResolvedValueOnce('yes')
|
|
296
|
+
.mockResolvedValueOnce('cursor');
|
|
297
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
298
|
+
// Mock existing file and simulate write error during update
|
|
299
|
+
const existingConfig = JSON.stringify({
|
|
300
|
+
mcpServers: {
|
|
301
|
+
OtherServer: {
|
|
302
|
+
url: 'https://other.example.com',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const mockReadFile = vitest_1.vi.fn().mockResolvedValue(existingConfig);
|
|
307
|
+
const mockWriteFile = vitest_1.vi
|
|
308
|
+
.fn()
|
|
309
|
+
.mockRejectedValue(new Error('Write failed during update'));
|
|
310
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
311
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
312
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
313
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
314
|
+
await (0, vitest_1.expect)((0, mcp_config_1.offerProjectScopedMcpConfig)()).resolves.toBeUndefined();
|
|
315
|
+
(0, vitest_1.expect)(clack.log.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to write MCP config automatically'));
|
|
316
|
+
(0, vitest_1.expect)(clackUtils.showCopyPasteInstructions).toHaveBeenCalled();
|
|
317
|
+
});
|
|
318
|
+
(0, vitest_1.it)('should handle mkdirSync errors', async () => {
|
|
319
|
+
const { clack, clackUtils } = await getMocks();
|
|
320
|
+
vitest_1.vi.mocked(clack.select)
|
|
321
|
+
.mockResolvedValueOnce('yes')
|
|
322
|
+
.mockResolvedValueOnce('cursor');
|
|
323
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
324
|
+
const mockReadFile = vitest_1.vi
|
|
325
|
+
.fn()
|
|
326
|
+
.mockRejectedValue(new Error('File not found'));
|
|
327
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
328
|
+
const mockMkdirSync = vitest_1.vi.fn().mockImplementation(() => {
|
|
329
|
+
throw new Error('Permission denied');
|
|
330
|
+
});
|
|
331
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
332
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
333
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
334
|
+
await (0, vitest_1.expect)((0, mcp_config_1.offerProjectScopedMcpConfig)()).resolves.toBeUndefined();
|
|
335
|
+
(0, vitest_1.expect)(clack.log.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to write MCP config automatically'));
|
|
336
|
+
(0, vitest_1.expect)(clackUtils.showCopyPasteInstructions).toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
(0, vitest_1.it)('should create config with empty servers/mcpServers when existing config lacks them', async () => {
|
|
339
|
+
const { clack, clackUtils } = await getMocks();
|
|
340
|
+
vitest_1.vi.mocked(clack.select)
|
|
341
|
+
.mockResolvedValueOnce('yes')
|
|
342
|
+
.mockResolvedValueOnce('vscode');
|
|
343
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
344
|
+
const existingConfig = JSON.stringify({
|
|
345
|
+
otherProperty: 'value',
|
|
346
|
+
});
|
|
347
|
+
const mockReadFile = vitest_1.vi.fn().mockResolvedValue(existingConfig);
|
|
348
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
349
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
350
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
351
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
352
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
353
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
354
|
+
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
355
|
+
(0, vitest_1.expect)(writtenContent).toHaveProperty('otherProperty', 'value');
|
|
356
|
+
(0, vitest_1.expect)(writtenContent).toHaveProperty('servers');
|
|
357
|
+
(0, vitest_1.expect)(writtenContent.servers).toHaveProperty('Sentry');
|
|
358
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith('Updated .vscode/mcp.json');
|
|
359
|
+
});
|
|
360
|
+
(0, vitest_1.it)('should show config for JetBrains IDEs with clipboard copy', async () => {
|
|
361
|
+
const { clack, clackUtils } = await getMocks();
|
|
362
|
+
vitest_1.vi.mocked(clack.select)
|
|
363
|
+
.mockResolvedValueOnce('yes')
|
|
364
|
+
.mockResolvedValueOnce('jetbrains')
|
|
365
|
+
.mockResolvedValueOnce(true); // For the clipboard copy prompt
|
|
366
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
367
|
+
// Mock clipboard copy
|
|
368
|
+
const mockSpawn = vitest_1.vi.fn().mockReturnValue({
|
|
369
|
+
stdin: {
|
|
370
|
+
write: vitest_1.vi.fn(),
|
|
371
|
+
end: vitest_1.vi.fn(),
|
|
372
|
+
},
|
|
373
|
+
on: vitest_1.vi.fn((event, callback) => {
|
|
374
|
+
if (event === 'close')
|
|
375
|
+
callback(0);
|
|
376
|
+
}),
|
|
377
|
+
});
|
|
378
|
+
vitest_1.vi.spyOn(childProcess, 'spawn').mockImplementation(mockSpawn);
|
|
379
|
+
// Mock console.log to capture output
|
|
380
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
381
|
+
const consoleSpy = vitest_1.vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
382
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
383
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('JetBrains IDEs'));
|
|
384
|
+
(0, vitest_1.expect)(consoleSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining('mcpServers'));
|
|
385
|
+
// Should ask to copy to clipboard
|
|
386
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
387
|
+
message: 'Copy configuration to clipboard?',
|
|
388
|
+
}));
|
|
389
|
+
(0, vitest_1.expect)(clack.log.success).toHaveBeenCalledWith('Configuration copied to clipboard!');
|
|
390
|
+
consoleSpy.mockRestore();
|
|
391
|
+
});
|
|
392
|
+
(0, vitest_1.it)('should show generic config for unsupported IDEs with clipboard copy', async () => {
|
|
393
|
+
const { clack, clackUtils } = await getMocks();
|
|
394
|
+
vitest_1.vi.mocked(clack.select)
|
|
395
|
+
.mockResolvedValueOnce('yes')
|
|
396
|
+
.mockResolvedValueOnce('other')
|
|
397
|
+
.mockResolvedValueOnce(true); // For the clipboard copy prompt
|
|
398
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
399
|
+
// Mock clipboard copy failure to test fallback
|
|
400
|
+
const mockSpawn = vitest_1.vi.fn().mockReturnValue({
|
|
401
|
+
stdin: {
|
|
402
|
+
write: vitest_1.vi.fn(),
|
|
403
|
+
end: vitest_1.vi.fn(),
|
|
404
|
+
},
|
|
405
|
+
on: vitest_1.vi.fn((event, callback) => {
|
|
406
|
+
if (event === 'error')
|
|
407
|
+
callback();
|
|
408
|
+
}),
|
|
409
|
+
});
|
|
410
|
+
vitest_1.vi.spyOn(childProcess, 'spawn').mockImplementation(mockSpawn);
|
|
411
|
+
// Mock console.log to capture output
|
|
412
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
413
|
+
const consoleSpy = vitest_1.vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
414
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
415
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Generic MCP configuration'));
|
|
416
|
+
(0, vitest_1.expect)(consoleSpy).toHaveBeenCalledWith(vitest_1.expect.stringContaining('mcpServers'));
|
|
417
|
+
// Should ask to copy to clipboard
|
|
418
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
419
|
+
message: 'Copy configuration to clipboard?',
|
|
420
|
+
}));
|
|
421
|
+
// Since clipboard copy failed, should show warning
|
|
422
|
+
(0, vitest_1.expect)(clack.log.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to copy to clipboard'));
|
|
423
|
+
consoleSpy.mockRestore();
|
|
424
|
+
});
|
|
425
|
+
(0, vitest_1.it)('should handle clipboard copy failure gracefully for JetBrains', async () => {
|
|
426
|
+
const { clack, clackUtils } = await getMocks();
|
|
427
|
+
vitest_1.vi.mocked(clack.select)
|
|
428
|
+
.mockResolvedValueOnce('yes')
|
|
429
|
+
.mockResolvedValueOnce('jetbrains')
|
|
430
|
+
.mockResolvedValueOnce(true); // For clipboard copy prompt
|
|
431
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
432
|
+
// Mock clipboard copy to throw error
|
|
433
|
+
const mockSpawn = vitest_1.vi.fn().mockImplementation(() => {
|
|
434
|
+
throw new Error('Clipboard not available');
|
|
435
|
+
});
|
|
436
|
+
vitest_1.vi.spyOn(childProcess, 'spawn').mockImplementation(mockSpawn);
|
|
437
|
+
// Mock console.log to capture output
|
|
438
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
439
|
+
const consoleSpy = vitest_1.vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
440
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
441
|
+
// Should ask to copy to clipboard
|
|
442
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
443
|
+
message: 'Copy configuration to clipboard?',
|
|
444
|
+
}));
|
|
445
|
+
// Should show warning when clipboard fails
|
|
446
|
+
(0, vitest_1.expect)(clack.log.warn).toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to copy to clipboard'));
|
|
447
|
+
consoleSpy.mockRestore();
|
|
448
|
+
});
|
|
449
|
+
(0, vitest_1.it)('should show MCP explanation when user selects "What is MCP?"', async () => {
|
|
450
|
+
const { clack, clackUtils } = await getMocks();
|
|
451
|
+
vitest_1.vi.mocked(clack.select)
|
|
452
|
+
.mockResolvedValueOnce('explain') // User selects "What is MCP?"
|
|
453
|
+
.mockResolvedValueOnce(true) // User selects "Yes" after explanation
|
|
454
|
+
.mockResolvedValueOnce('cursor'); // User selects Cursor
|
|
455
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
456
|
+
const mockReadFile = vitest_1.vi
|
|
457
|
+
.fn()
|
|
458
|
+
.mockRejectedValue(new Error('File not found'));
|
|
459
|
+
const mockWriteFile = vitest_1.vi.fn().mockResolvedValue(undefined);
|
|
460
|
+
const mockMkdirSync = vitest_1.vi.fn();
|
|
461
|
+
vitest_1.vi.spyOn(fs.promises, 'readFile').mockImplementation(mockReadFile);
|
|
462
|
+
vitest_1.vi.spyOn(fs.promises, 'writeFile').mockImplementation(mockWriteFile);
|
|
463
|
+
vitest_1.vi.spyOn(fs, 'mkdirSync').mockImplementation(mockMkdirSync);
|
|
464
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
465
|
+
// Should show MCP explanation
|
|
466
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('What is MCP'));
|
|
467
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('AI assistants'));
|
|
468
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('https://docs.sentry.io/product/sentry-mcp/'));
|
|
469
|
+
// Should ask again after explanation
|
|
470
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
471
|
+
message: 'Would you like to configure MCP for your IDE now?',
|
|
472
|
+
}));
|
|
473
|
+
// Should proceed with normal flow
|
|
474
|
+
(0, vitest_1.expect)(mockWriteFile).toHaveBeenCalled();
|
|
475
|
+
});
|
|
476
|
+
(0, vitest_1.it)('should respect user choice not to copy to clipboard', async () => {
|
|
477
|
+
const { clack, clackUtils } = await getMocks();
|
|
478
|
+
vitest_1.vi.mocked(clack.select)
|
|
479
|
+
.mockResolvedValueOnce('yes')
|
|
480
|
+
.mockResolvedValueOnce('jetbrains')
|
|
481
|
+
.mockResolvedValueOnce(false); // User declines to copy to clipboard
|
|
482
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
483
|
+
const mockSpawn = vitest_1.vi.fn();
|
|
484
|
+
vitest_1.vi.spyOn(childProcess, 'spawn').mockImplementation(mockSpawn);
|
|
485
|
+
// Mock console.log to capture output
|
|
486
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
487
|
+
const consoleSpy = vitest_1.vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
488
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
489
|
+
// Should ask to copy to clipboard
|
|
490
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
491
|
+
message: 'Copy configuration to clipboard?',
|
|
492
|
+
}));
|
|
493
|
+
// Should NOT attempt to copy when user declines
|
|
494
|
+
(0, vitest_1.expect)(mockSpawn).not.toHaveBeenCalled();
|
|
495
|
+
// Should NOT show success or warning messages
|
|
496
|
+
(0, vitest_1.expect)(clack.log.success).not.toHaveBeenCalledWith('Configuration copied to clipboard!');
|
|
497
|
+
(0, vitest_1.expect)(clack.log.warn).not.toHaveBeenCalledWith(vitest_1.expect.stringContaining('Failed to copy to clipboard'));
|
|
498
|
+
consoleSpy.mockRestore();
|
|
499
|
+
});
|
|
500
|
+
(0, vitest_1.it)('should exit if user declines after MCP explanation', async () => {
|
|
501
|
+
const { clack, clackUtils } = await getMocks();
|
|
502
|
+
vitest_1.vi.mocked(clack.select)
|
|
503
|
+
.mockResolvedValueOnce('explain') // User selects "What is MCP?"
|
|
504
|
+
.mockResolvedValueOnce(false); // User selects "No" after explanation
|
|
505
|
+
vitest_1.vi.mocked(clackUtils.abortIfCancelled).mockImplementation((value) => Promise.resolve(value));
|
|
506
|
+
await (0, mcp_config_1.offerProjectScopedMcpConfig)();
|
|
507
|
+
// Should show MCP explanation
|
|
508
|
+
(0, vitest_1.expect)(clack.log.info).toHaveBeenCalledWith(vitest_1.expect.stringContaining('What is MCP'));
|
|
509
|
+
// Should ask again after explanation
|
|
510
|
+
(0, vitest_1.expect)(clack.select).toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
511
|
+
message: 'Would you like to configure MCP for your IDE now?',
|
|
512
|
+
}));
|
|
513
|
+
// Should NOT proceed with editor selection
|
|
514
|
+
(0, vitest_1.expect)(clack.select).not.toHaveBeenCalledWith(vitest_1.expect.objectContaining({
|
|
515
|
+
message: 'Which editor do you want to configure?',
|
|
516
|
+
}));
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
//# sourceMappingURL=mcp-config.test.js.map
|