@sentry/wizard 6.2.0 → 6.4.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/e2e-tests/tests/angular-17.test.js +5 -0
  3. package/dist/e2e-tests/tests/angular-17.test.js.map +1 -1
  4. package/dist/e2e-tests/tests/angular-19.test.js +5 -0
  5. package/dist/e2e-tests/tests/angular-19.test.js.map +1 -1
  6. package/dist/e2e-tests/tests/expo.test.js +9 -2
  7. package/dist/e2e-tests/tests/expo.test.js.map +1 -1
  8. package/dist/e2e-tests/tests/flutter.test.js +18 -4
  9. package/dist/e2e-tests/tests/flutter.test.js.map +1 -1
  10. package/dist/e2e-tests/tests/nextjs-14.test.js +4 -3
  11. package/dist/e2e-tests/tests/nextjs-14.test.js.map +1 -1
  12. package/dist/e2e-tests/tests/nextjs-15.test.js +17 -4
  13. package/dist/e2e-tests/tests/nextjs-15.test.js.map +1 -1
  14. package/dist/e2e-tests/tests/nuxt-3.test.js +9 -2
  15. package/dist/e2e-tests/tests/nuxt-3.test.js.map +1 -1
  16. package/dist/e2e-tests/tests/nuxt-4.test.js +9 -2
  17. package/dist/e2e-tests/tests/nuxt-4.test.js.map +1 -1
  18. package/dist/e2e-tests/tests/react-native.test.js +8 -1
  19. package/dist/e2e-tests/tests/react-native.test.js.map +1 -1
  20. package/dist/e2e-tests/tests/remix.test.js +16 -2
  21. package/dist/e2e-tests/tests/remix.test.js.map +1 -1
  22. package/dist/e2e-tests/tests/{sveltekit.test.js → sveltekit-hooks.test.js} +26 -8
  23. package/dist/e2e-tests/tests/sveltekit-hooks.test.js.map +1 -0
  24. package/dist/e2e-tests/tests/sveltekit-tracing.test.d.ts +1 -0
  25. package/dist/e2e-tests/tests/sveltekit-tracing.test.js +203 -0
  26. package/dist/e2e-tests/tests/sveltekit-tracing.test.js.map +1 -0
  27. package/dist/src/android/android-wizard.js +3 -0
  28. package/dist/src/android/android-wizard.js.map +1 -1
  29. package/dist/src/angular/angular-wizard.js +3 -0
  30. package/dist/src/angular/angular-wizard.js.map +1 -1
  31. package/dist/src/apple/apple-wizard.js +3 -0
  32. package/dist/src/apple/apple-wizard.js.map +1 -1
  33. package/dist/src/flutter/flutter-wizard.js +3 -0
  34. package/dist/src/flutter/flutter-wizard.js.map +1 -1
  35. package/dist/src/nextjs/nextjs-wizard.js +3 -0
  36. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  37. package/dist/src/nuxt/nuxt-wizard.js +3 -0
  38. package/dist/src/nuxt/nuxt-wizard.js.map +1 -1
  39. package/dist/src/react-native/react-native-wizard.js +3 -0
  40. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  41. package/dist/src/remix/remix-wizard.d.ts +4 -0
  42. package/dist/src/remix/remix-wizard.js +7 -0
  43. package/dist/src/remix/remix-wizard.js.map +1 -1
  44. package/dist/src/sveltekit/sdk-example.d.ts +2 -2
  45. package/dist/src/sveltekit/sdk-example.js.map +1 -1
  46. package/dist/src/sveltekit/sdk-setup/setup.d.ts +3 -0
  47. package/dist/src/sveltekit/{sdk-setup.js → sdk-setup/setup.js} +113 -157
  48. package/dist/src/sveltekit/sdk-setup/setup.js.map +1 -0
  49. package/dist/src/sveltekit/sdk-setup/svelte-config.d.ts +25 -0
  50. package/dist/src/sveltekit/sdk-setup/svelte-config.js +291 -0
  51. package/dist/src/sveltekit/sdk-setup/svelte-config.js.map +1 -0
  52. package/dist/src/sveltekit/sdk-setup/types.d.ts +7 -0
  53. package/dist/src/sveltekit/sdk-setup/types.js +3 -0
  54. package/dist/src/sveltekit/sdk-setup/types.js.map +1 -0
  55. package/dist/src/sveltekit/sdk-setup/utils.d.ts +6 -0
  56. package/dist/src/sveltekit/sdk-setup/utils.js +45 -0
  57. package/dist/src/sveltekit/sdk-setup/utils.js.map +1 -0
  58. package/dist/src/sveltekit/sdk-setup/vite.d.ts +2 -0
  59. package/dist/src/sveltekit/sdk-setup/vite.js +120 -0
  60. package/dist/src/sveltekit/sdk-setup/vite.js.map +1 -0
  61. package/dist/src/sveltekit/sveltekit-wizard.js +48 -5
  62. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  63. package/dist/src/sveltekit/templates.d.ts +4 -0
  64. package/dist/src/sveltekit/templates.js +33 -10
  65. package/dist/src/sveltekit/templates.js.map +1 -1
  66. package/dist/src/sveltekit/utils.d.ts +2 -1
  67. package/dist/src/sveltekit/utils.js +7 -2
  68. package/dist/src/sveltekit/utils.js.map +1 -1
  69. package/dist/src/utils/clack/mcp-config.d.ts +7 -0
  70. package/dist/src/utils/clack/mcp-config.js +427 -0
  71. package/dist/src/utils/clack/mcp-config.js.map +1 -0
  72. package/dist/src/version.d.ts +1 -1
  73. package/dist/src/version.js +1 -1
  74. package/dist/src/version.js.map +1 -1
  75. package/dist/test/angular/angular-wizard.test.js +3 -0
  76. package/dist/test/angular/angular-wizard.test.js.map +1 -1
  77. package/dist/test/apple/templates.test.js +3 -0
  78. package/dist/test/apple/templates.test.js.map +1 -1
  79. package/dist/test/flutter/templates.test.js +3 -0
  80. package/dist/test/flutter/templates.test.js.map +1 -1
  81. package/dist/test/nextjs/wizard-double-wrap-prevention.test.js +3 -0
  82. package/dist/test/nextjs/wizard-double-wrap-prevention.test.js.map +1 -1
  83. package/dist/test/nuxt/templates.test.js +3 -0
  84. package/dist/test/nuxt/templates.test.js.map +1 -1
  85. package/dist/test/react-native/metro.test.js +3 -0
  86. package/dist/test/react-native/metro.test.js.map +1 -1
  87. package/dist/test/remix/root.test.js +3 -0
  88. package/dist/test/remix/root.test.js.map +1 -1
  89. package/dist/test/sveltekit/sdk-setup/svelte-config.test.d.ts +1 -0
  90. package/dist/test/sveltekit/sdk-setup/svelte-config.test.js +529 -0
  91. package/dist/test/sveltekit/sdk-setup/svelte-config.test.js.map +1 -0
  92. package/dist/test/sveltekit/templates.test.js +106 -10
  93. package/dist/test/sveltekit/templates.test.js.map +1 -1
  94. package/dist/test/utils/clack/mcp-config.test.d.ts +1 -0
  95. package/dist/test/utils/clack/mcp-config.test.js +520 -0
  96. package/dist/test/utils/clack/mcp-config.test.js.map +1 -0
  97. package/package.json +1 -1
  98. package/dist/e2e-tests/tests/sveltekit.test.js.map +0 -1
  99. package/dist/src/sveltekit/sdk-setup.d.ts +0 -21
  100. package/dist/src/sveltekit/sdk-setup.js.map +0 -1
  101. /package/dist/e2e-tests/tests/{sveltekit.test.d.ts → sveltekit-hooks.test.d.ts} +0 -0
@@ -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