@sentry/wizard 3.32.0 → 3.33.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 (50) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -1
  3. package/bin.ts +59 -27
  4. package/dist/bin.js +97 -70
  5. package/dist/bin.js.map +1 -1
  6. package/dist/e2e-tests/jest.config.d.ts +15 -0
  7. package/dist/e2e-tests/jest.config.js +17 -0
  8. package/dist/e2e-tests/jest.config.js.map +1 -0
  9. package/dist/e2e-tests/tests/remix.test.d.ts +1 -0
  10. package/dist/e2e-tests/tests/remix.test.js +187 -0
  11. package/dist/e2e-tests/tests/remix.test.js.map +1 -0
  12. package/dist/e2e-tests/utils/index.d.ts +117 -0
  13. package/dist/e2e-tests/utils/index.js +353 -0
  14. package/dist/e2e-tests/utils/index.js.map +1 -0
  15. package/dist/lib/Steps/SentryProjectSelector.js +1 -1
  16. package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
  17. package/dist/package.json +7 -3
  18. package/dist/src/remix/sdk-example.js +5 -1
  19. package/dist/src/remix/sdk-example.js.map +1 -1
  20. package/dist/src/run.d.ts +12 -1
  21. package/dist/src/run.js +27 -1
  22. package/dist/src/run.js.map +1 -1
  23. package/dist/src/sourcemaps/sourcemaps-wizard.d.ts +2 -2
  24. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  25. package/dist/src/sourcemaps/tools/remix.d.ts +2 -2
  26. package/dist/src/sourcemaps/tools/remix.js.map +1 -1
  27. package/dist/src/utils/clack-utils.js +22 -17
  28. package/dist/src/utils/clack-utils.js.map +1 -1
  29. package/dist/src/utils/types.d.ts +6 -14
  30. package/dist/src/utils/types.js.map +1 -1
  31. package/e2e-tests/jest.config.ts +14 -0
  32. package/e2e-tests/test-applications/remix-test-app/app/entry.client.tsx +18 -0
  33. package/e2e-tests/test-applications/remix-test-app/app/entry.server.tsx +140 -0
  34. package/e2e-tests/test-applications/remix-test-app/app/root.tsx +30 -0
  35. package/e2e-tests/test-applications/remix-test-app/app/routes/_index.tsx +48 -0
  36. package/e2e-tests/test-applications/remix-test-app/app/tailwind.css +3 -0
  37. package/e2e-tests/test-applications/remix-test-app/package.json +37 -0
  38. package/e2e-tests/test-applications/remix-test-app/postcss.config.js +6 -0
  39. package/e2e-tests/test-applications/remix-test-app/tailwind.config.ts +9 -0
  40. package/e2e-tests/test-applications/remix-test-app/vite.config.ts +16 -0
  41. package/e2e-tests/tests/remix.test.ts +163 -0
  42. package/e2e-tests/utils/index.ts +302 -0
  43. package/lib/Steps/SentryProjectSelector.ts +1 -1
  44. package/package.json +7 -3
  45. package/src/remix/sdk-example.ts +6 -0
  46. package/src/run.ts +45 -5
  47. package/src/sourcemaps/sourcemaps-wizard.ts +4 -3
  48. package/src/sourcemaps/tools/remix.ts +2 -2
  49. package/src/utils/clack-utils.ts +17 -8
  50. package/src/utils/types.ts +8 -14
@@ -0,0 +1,48 @@
1
+ import type { MetaFunction } from "@remix-run/node";
2
+
3
+ export const meta: MetaFunction = () => {
4
+ return [
5
+ { title: "New Remix App" },
6
+ { name: "description", content: "Welcome to Remix!" },
7
+ ];
8
+ };
9
+
10
+ export default function Index() {
11
+ return (
12
+ <div className="font-sans p-4">
13
+ <h1 className="text-3xl">Welcome to Remix</h1>
14
+ <ul className="list-disc mt-4 pl-6 space-y-2">
15
+ <li>
16
+ <a
17
+ className="text-blue-700 underline visited:text-purple-900"
18
+ target="_blank"
19
+ href="https://remix.run/start/quickstart"
20
+ rel="noreferrer"
21
+ >
22
+ 5m Quick Start
23
+ </a>
24
+ </li>
25
+ <li>
26
+ <a
27
+ className="text-blue-700 underline visited:text-purple-900"
28
+ target="_blank"
29
+ href="https://remix.run/start/tutorial"
30
+ rel="noreferrer"
31
+ >
32
+ 30m Tutorial
33
+ </a>
34
+ </li>
35
+ <li>
36
+ <a
37
+ className="text-blue-700 underline visited:text-purple-900"
38
+ target="_blank"
39
+ href="https://remix.run/docs"
40
+ rel="noreferrer"
41
+ >
42
+ Remix Docs
43
+ </a>
44
+ </li>
45
+ </ul>
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "remix-test-app",
3
+ "private": true,
4
+ "sideEffects": false,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "remix vite:build",
8
+ "dev": "remix vite:dev",
9
+ "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
10
+ "start": "remix-serve ./build/server/index.js",
11
+ "typecheck": "tsc"
12
+ },
13
+ "dependencies": {
14
+ "@remix-run/node": "^2.11.1",
15
+ "@remix-run/react": "^2.11.1",
16
+ "@remix-run/serve": "^2.11.1",
17
+ "isbot": "^4.1.0",
18
+ "react": "^18.2.0",
19
+ "react-dom": "^18.2.0"
20
+ },
21
+ "devDependencies": {
22
+ "@remix-run/dev": "^2.11.1",
23
+ "@types/react": "^18.2.20",
24
+ "@types/react-dom": "^18.2.7",
25
+ "@typescript-eslint/eslint-plugin": "^6.7.4",
26
+ "@typescript-eslint/parser": "^6.7.4",
27
+ "autoprefixer": "^10.4.19",
28
+ "postcss": "^8.4.38",
29
+ "tailwindcss": "^3.4.4",
30
+ "typescript": "^5.1.6",
31
+ "vite": "^5.1.0",
32
+ "vite-tsconfig-paths": "^4.2.1"
33
+ },
34
+ "engines": {
35
+ "node": ">=20.0.0"
36
+ }
37
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,9 @@
1
+ import type { Config } from "tailwindcss";
2
+
3
+ export default {
4
+ content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"],
5
+ theme: {
6
+ extend: {},
7
+ },
8
+ plugins: [],
9
+ } satisfies Config;
@@ -0,0 +1,16 @@
1
+ import { vitePlugin as remix } from "@remix-run/dev";
2
+ import { defineConfig } from "vite";
3
+ import tsconfigPaths from "vite-tsconfig-paths";
4
+
5
+ export default defineConfig({
6
+ plugins: [
7
+ remix({
8
+ future: {
9
+ v3_fetcherPersist: true,
10
+ v3_relativeSplatPath: true,
11
+ v3_throwAbortReason: true,
12
+ },
13
+ }),
14
+ tsconfigPaths(),
15
+ ],
16
+ });
@@ -0,0 +1,163 @@
1
+ /* eslint-disable jest/expect-expect */
2
+ import { Integration } from '../../lib/Constants';
3
+ import {
4
+ checkEnvBuildPlugin,
5
+ cleanupGit,
6
+ KEYS,
7
+ revertLocalChanges,
8
+ } from '../utils';
9
+ import { startWizardInstance } from '../utils';
10
+ import {
11
+ checkFileContents,
12
+ checkFileExists,
13
+ checkIfBuilds,
14
+ checkIfRunsOnDevMode,
15
+ checkIfRunsOnProdMode,
16
+ checkPackageJson,
17
+ TEST_ARGS,
18
+ } from '../utils';
19
+ import * as path from 'path';
20
+
21
+ describe('Remix', () => {
22
+ const integration = Integration.remix;
23
+ const projectDir = path.resolve(
24
+ __dirname,
25
+ '../test-applications/remix-test-app',
26
+ );
27
+
28
+ beforeAll(async () => {
29
+ const wizardInstance = startWizardInstance(integration, projectDir);
30
+ const packageManagerPrompted = await wizardInstance.waitForOutput(
31
+ 'Please select your package manager.',
32
+ );
33
+
34
+ if (packageManagerPrompted) {
35
+ // Selecting `yarn` as the package manager
36
+ wizardInstance.sendStdin(KEYS.DOWN);
37
+ wizardInstance.sendStdin(KEYS.ENTER);
38
+ }
39
+
40
+ const tracingOptionPrompted = await wizardInstance.waitForOutput(
41
+ 'Do you want to enable Tracing',
42
+ {
43
+ timeout: 240_000,
44
+ },
45
+ );
46
+
47
+ if (tracingOptionPrompted) {
48
+ wizardInstance.sendStdin(KEYS.ENTER);
49
+ }
50
+
51
+ const replayOptionPrompted = await wizardInstance.waitForOutput(
52
+ 'Do you want to enable Sentry Session Replay',
53
+ );
54
+
55
+ if (replayOptionPrompted) {
56
+ wizardInstance.sendStdin(KEYS.ENTER);
57
+ }
58
+
59
+ const examplePagePrompted = await wizardInstance.waitForOutput(
60
+ 'Do you want to create an example page',
61
+ {
62
+ optional: true,
63
+ },
64
+ );
65
+
66
+ if (examplePagePrompted) {
67
+ wizardInstance.sendStdin(KEYS.ENTER);
68
+ wizardInstance.sendStdin(KEYS.ENTER);
69
+ }
70
+
71
+ await wizardInstance.waitForOutput(
72
+ 'Sentry has been successfully configured for your Remix project',
73
+ );
74
+
75
+ wizardInstance.kill();
76
+ });
77
+
78
+ afterAll(() => {
79
+ revertLocalChanges(projectDir);
80
+ cleanupGit(projectDir);
81
+ });
82
+
83
+ test('package.json is updated correctly', () => {
84
+ checkPackageJson(projectDir, integration);
85
+ });
86
+
87
+ test('.env-sentry-build-plugin is created and contains the auth token', () => {
88
+ checkEnvBuildPlugin(projectDir);
89
+ });
90
+
91
+ test('example page exists', () => {
92
+ checkFileExists(`${projectDir}/app/routes/sentry-example-page.tsx`);
93
+ });
94
+
95
+ test('instrumentation.server file exists', () => {
96
+ checkFileExists(`${projectDir}/instrumentation.server.mjs`);
97
+ });
98
+
99
+ test('entry.client file contains Sentry initialization', () => {
100
+ checkFileContents(`${projectDir}/app/entry.client.tsx`, [
101
+ 'import * as Sentry from "@sentry/remix";',
102
+ `Sentry.init({
103
+ dsn: "${TEST_ARGS.PROJECT_DSN}",
104
+ tracesSampleRate: 1,
105
+
106
+ integrations: [Sentry.browserTracingIntegration({
107
+ useEffect,
108
+ useLocation,
109
+ useMatches
110
+ }), Sentry.replayIntegration({
111
+ maskAllText: true,
112
+ blockAllMedia: true
113
+ })],
114
+
115
+ replaysSessionSampleRate: 0.1,
116
+ replaysOnErrorSampleRate: 1
117
+ })`,
118
+ ]);
119
+ });
120
+
121
+ test('entry.server file contains Sentry code', () => {
122
+ checkFileContents(`${projectDir}/app/entry.server.tsx`, [
123
+ 'import * as Sentry from "@sentry/remix";',
124
+ `export const handleError = Sentry.wrapHandleErrorWithSentry((error, { request }) => {
125
+ // Custom handleError implementation
126
+ });`,
127
+ ]);
128
+ });
129
+
130
+ test('instrumentation.server file contains Sentry initialization', () => {
131
+ checkFileContents(`${projectDir}/instrumentation.server.mjs`, [
132
+ 'import * as Sentry from "@sentry/remix";',
133
+ `Sentry.init({
134
+ dsn: "${TEST_ARGS.PROJECT_DSN}",
135
+ tracesSampleRate: 1,
136
+ autoInstrumentRemix: true
137
+ })`,
138
+ ]);
139
+ });
140
+
141
+ test('root file contains Sentry ErrorBoundary', () => {
142
+ checkFileContents(`${projectDir}/app/root.tsx`, [
143
+ 'import { captureRemixErrorBoundaryError } from "@sentry/remix";',
144
+ `export const ErrorBoundary = () => {
145
+ const error = useRouteError();
146
+ captureRemixErrorBoundaryError(error);
147
+ return <div>Something went wrong</div>;
148
+ };`,
149
+ ]);
150
+ });
151
+
152
+ test('builds correctly', async () => {
153
+ await checkIfBuilds(projectDir, 'built');
154
+ });
155
+
156
+ test('runs on dev mode correctly', async () => {
157
+ await checkIfRunsOnDevMode(projectDir, 'to expose');
158
+ });
159
+
160
+ test('runs on prod mode correctly', async () => {
161
+ await checkIfRunsOnProdMode(projectDir, '[remix-serve]');
162
+ });
163
+ });
@@ -0,0 +1,302 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ import type { Integration } from '../../lib/Constants';
5
+ import { spawn, execSync } from 'child_process';
6
+ import type { ChildProcess } from 'child_process';
7
+ import { dim, green, red } from '../../lib/Helper/Logging';
8
+
9
+ export const KEYS = {
10
+ UP: '\u001b[A',
11
+ DOWN: '\u001b[B',
12
+ LEFT: '\u001b[D',
13
+ RIGHT: '\u001b[C',
14
+ ENTER: '\r',
15
+ SPACE: ' ',
16
+ };
17
+
18
+ export const TEST_ARGS = {
19
+ AUTH_TOKEN: 'TEST_AUTH_TOKEN',
20
+ PROJECT_DSN: 'https://public@dsn.ingest.sentry.io/1337',
21
+ };
22
+
23
+ export const log = {
24
+ success: (message: string) => {
25
+ green(`[SUCCESS] ${message}`);
26
+ },
27
+ info: (message: string) => {
28
+ dim(`[INFO] ${message}`);
29
+ },
30
+ error: (message: string) => {
31
+ red(`[ERROR] ${message}`);
32
+ },
33
+ };
34
+
35
+ export class WizardTestEnv {
36
+ taskHandle: ChildProcess;
37
+
38
+ constructor(
39
+ cmd: string,
40
+ args: string[],
41
+ opts?: {
42
+ cwd?: string;
43
+ debug?: boolean;
44
+ },
45
+ ) {
46
+ this.taskHandle = spawn(cmd, args, { cwd: opts?.cwd, stdio: 'pipe' });
47
+
48
+ if (opts?.debug) {
49
+ this.taskHandle.stdout.pipe(process.stdout);
50
+ this.taskHandle.stderr.pipe(process.stderr);
51
+ }
52
+ }
53
+
54
+ sendStdin(input: string) {
55
+ this.taskHandle.stdin.write(input);
56
+ }
57
+
58
+ /**
59
+ * Waits for the provided output with `.includes()` logic.
60
+ *
61
+ * @returns a promise that resolves to `true` if the output was found, `false` if the output was not found within the
62
+ * timeout and `optional: true` is set, or it rejects when the timeout was reached with `optional: false`
63
+ */
64
+ waitForOutput(
65
+ output: string,
66
+ options: {
67
+ /** Timeout in ms */
68
+ timeout?: number;
69
+ /** Whether to always resolve after the timeout, no matter whether the input was actually found or not. */
70
+ optional?: boolean;
71
+ } = {},
72
+ ) {
73
+ const { timeout, optional } = {
74
+ timeout: 30_000,
75
+ optional: false,
76
+ ...options,
77
+ };
78
+
79
+ return new Promise<boolean>((resolve, reject) => {
80
+ let outputBuffer = '';
81
+ const timeoutId = setTimeout(() => {
82
+ if (optional) {
83
+ // The output is not found but it's optional so we can resolve the promise with false
84
+ resolve(false);
85
+ } else {
86
+ reject(new Error(`Timeout waiting for output: ${output}`));
87
+ }
88
+ }, timeout);
89
+
90
+ this.taskHandle.stdout.on('data', (data) => {
91
+ outputBuffer += data;
92
+ if (outputBuffer.includes(output)) {
93
+ clearTimeout(timeoutId);
94
+ // The output is found so we can resolve the promise with true
95
+ resolve(true);
96
+ }
97
+ });
98
+ });
99
+ }
100
+
101
+ kill() {
102
+ this.taskHandle.stdin.destroy();
103
+ this.taskHandle.stderr.destroy();
104
+ this.taskHandle.stdout.destroy();
105
+ this.taskHandle.kill('SIGINT');
106
+ this.taskHandle.unref();
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Initialize a git repository in the given directory
112
+ * @param projectDir
113
+ */
114
+ export function initGit(projectDir: string): void {
115
+ try {
116
+ execSync('git init', { cwd: projectDir });
117
+ // Add all files to the git repo
118
+ execSync('git add -A', { cwd: projectDir });
119
+ // Add author info to avoid git commit error
120
+ execSync('git config user.email test@test.sentry.io', { cwd: projectDir });
121
+ execSync('git config user.name Test', { cwd: projectDir });
122
+ execSync('git commit -m init', { cwd: projectDir });
123
+ } catch (e) {
124
+ log.error('Error initializing git');
125
+ throw e;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Cleanup the git repository in the given directory
131
+ *
132
+ * Caution! Make sure `projectDir` is a test project directory,
133
+ * if in doubt, please commit your local non-test changes first!
134
+ * @param projectDir
135
+ */
136
+ export function cleanupGit(projectDir: string): void {
137
+ try {
138
+ // Remove the .git directory
139
+ execSync(`rm -rf ${projectDir}/.git`);
140
+ } catch (e) {
141
+ log.error('Error cleaning up git');
142
+ throw e;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Revert local changes in the given directory
148
+ *
149
+ * Caution! Make sure `projectDir` is a test project directory,
150
+ * if in doubt, please commit your local non-test changes first!
151
+ *
152
+ * @param projectDir
153
+ */
154
+ export function revertLocalChanges(projectDir: string): void {
155
+ try {
156
+ // Revert tracked files
157
+ execSync('git checkout .', { cwd: projectDir });
158
+ // Revert untracked files
159
+ execSync('git clean -fd .', { cwd: projectDir });
160
+ } catch (e) {
161
+ log.error('Error reverting local changes');
162
+ throw e;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Start the wizard instance with the given integration and project directory
168
+ * @param integration
169
+ * @param projectDir
170
+ *
171
+ * @returns WizardTestEnv
172
+ */
173
+ export function startWizardInstance(
174
+ integration: Integration,
175
+ projectDir: string,
176
+ ): WizardTestEnv {
177
+ const binPath = path.join(__dirname, '../../dist/bin.js');
178
+
179
+ revertLocalChanges(projectDir);
180
+ cleanupGit(projectDir);
181
+ initGit(projectDir);
182
+
183
+ return new WizardTestEnv(
184
+ 'node',
185
+ [
186
+ binPath,
187
+ '--debug',
188
+ '-i',
189
+ integration,
190
+ '--preSelectedProject.authToken',
191
+ TEST_ARGS.AUTH_TOKEN,
192
+ '--preSelectedProject.dsn',
193
+ TEST_ARGS.PROJECT_DSN,
194
+ ],
195
+ { cwd: projectDir },
196
+ );
197
+ }
198
+
199
+ /**
200
+ * Read the file contents and check if it contains the given content
201
+ *
202
+ * @param filePath
203
+ * @param content
204
+ */
205
+ export function checkFileContents(
206
+ filePath: string,
207
+ content: string | string[],
208
+ ) {
209
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
210
+ const contentArray = Array.isArray(content) ? content : [content];
211
+
212
+ for (const c of contentArray) {
213
+ expect(fileContent).toContain(c);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Check if the file exists
219
+ *
220
+ * @param filePath
221
+ */
222
+ export function checkFileExists(filePath: string) {
223
+ expect(fs.existsSync(filePath)).toBe(true);
224
+ }
225
+
226
+ /**
227
+ * Check if the package.json contains the given integration
228
+ * @param projectDir
229
+ * @param integration
230
+ */
231
+ export function checkPackageJson(projectDir: string, integration: Integration) {
232
+ checkFileContents(`${projectDir}/package.json`, `@sentry/${integration}`);
233
+ }
234
+
235
+ /**
236
+ * Check if the .sentryclirc contains the auth token
237
+ * @param projectDir
238
+ */
239
+ export function checkSentryCliRc(projectDir: string) {
240
+ checkFileContents(
241
+ `${projectDir}/.sentryclirc`,
242
+ `token=${TEST_ARGS.AUTH_TOKEN}`,
243
+ );
244
+ }
245
+
246
+ /**
247
+ * Check if the .env.sentry-build-plugin contains the auth token
248
+ * @param projectDir
249
+ */
250
+ export function checkEnvBuildPlugin(projectDir: string) {
251
+ checkFileContents(
252
+ `${projectDir}/.env.sentry-build-plugin`,
253
+ `SENTRY_AUTH_TOKEN=${TEST_ARGS.AUTH_TOKEN}`,
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Check if the project builds
259
+ * @param projectDir
260
+ */
261
+ export async function checkIfBuilds(
262
+ projectDir: string,
263
+ expectedOutput: string,
264
+ ) {
265
+ const testEnv = new WizardTestEnv('npm', ['run', 'build'], {
266
+ cwd: projectDir,
267
+ });
268
+
269
+ await expect(testEnv.waitForOutput(expectedOutput)).resolves.toBe(true);
270
+ }
271
+
272
+ /**
273
+ * Check if the project runs on dev mode
274
+ * @param projectDir
275
+ * @param expectedOutput
276
+ */
277
+ export async function checkIfRunsOnDevMode(
278
+ projectDir: string,
279
+ expectedOutput: string,
280
+ ) {
281
+ const testEnv = new WizardTestEnv('npm', ['run', 'dev'], { cwd: projectDir });
282
+
283
+ await expect(testEnv.waitForOutput(expectedOutput)).resolves.toBe(true);
284
+ testEnv.kill();
285
+ }
286
+
287
+ /**
288
+ * Check if the project runs on prod mode
289
+ * @param projectDir
290
+ * @param expectedOutput
291
+ */
292
+ export async function checkIfRunsOnProdMode(
293
+ projectDir: string,
294
+ expectedOutput: string,
295
+ ) {
296
+ const testEnv = new WizardTestEnv('npm', ['run', 'start'], {
297
+ cwd: projectDir,
298
+ });
299
+
300
+ await expect(testEnv.waitForOutput(expectedOutput)).resolves.toBe(true);
301
+ testEnv.kill();
302
+ }
@@ -13,7 +13,7 @@ export class SentryProjectSelector extends BaseStep {
13
13
  this.debug(answers);
14
14
 
15
15
  if (!_.has(answers, 'wizard')) {
16
- // we skip this completly because the wizard wasn't running
16
+ // we skip this completely because the wizard wasn't running
17
17
  return {};
18
18
  }
19
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/wizard",
3
- "version": "3.32.0",
3
+ "version": "3.33.0",
4
4
  "homepage": "https://github.com/getsentry/sentry-wizard",
5
5
  "repository": "https://github.com/getsentry/sentry-wizard",
6
6
  "description": "Sentry wizard helping you to configure your project",
@@ -27,7 +27,7 @@
27
27
  "@clack/prompts": "0.7.0",
28
28
  "@sentry/cli": "^1.72.0",
29
29
  "@sentry/node": "^7.69.0",
30
- "axios": "1.6.0",
30
+ "axios": "1.7.4",
31
31
  "chalk": "^2.4.1",
32
32
  "glob": "^8.1.0",
33
33
  "inquirer": "^6.2.0",
@@ -44,6 +44,7 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@sentry-internal/eslint-config-sdk": "^7.48.0",
47
+ "@types/chai": "^4.3.17",
47
48
  "@types/glob": "^7.2.0",
48
49
  "@types/inquirer": "^0.0.43",
49
50
  "@types/jest": "^29.5.0",
@@ -62,6 +63,7 @@
62
63
  "rimraf": "^3.0.2",
63
64
  "ts-jest": "^29.1.0",
64
65
  "ts-node": "^10.9.1",
66
+ "tsx": "^3.14.0",
65
67
  "typescript": "^5.0.4"
66
68
  },
67
69
  "resolutions": {
@@ -85,6 +87,7 @@
85
87
  "fix:prettier": "prettier --write \"{lib,src,test}/**/*.ts\"",
86
88
  "fix:eslint": "eslint . --format stylish --fix",
87
89
  "test": "yarn build && jest",
90
+ "test:e2e": "yarn build && jest -c=\"./e2e-tests/jest.config.ts\" ./e2e-tests/",
88
91
  "try": "ts-node bin.ts",
89
92
  "try:uninstall": "ts-node bin.ts --uninstall",
90
93
  "test:watch": "jest --watch"
@@ -112,7 +115,8 @@
112
115
  "/dist/",
113
116
  "/node_modules/",
114
117
  "\\.d\\.(jsx?|tsx?)$",
115
- "\\.no-jest\\.(jsx?|tsx?)$"
118
+ "\\.no-jest\\.(jsx?|tsx?)$",
119
+ "/e2e-tests/"
116
120
  ],
117
121
  "testEnvironment": "node"
118
122
  },
@@ -13,6 +13,12 @@ export async function createExamplePage(options: {
13
13
  url: string;
14
14
  isTS: boolean;
15
15
  }) {
16
+ const routesPath = 'app/routes';
17
+
18
+ if (!fs.existsSync(routesPath)) {
19
+ fs.mkdirSync(routesPath, { recursive: true });
20
+ }
21
+
16
22
  const exampleRoutePath = `app/routes/sentry-example-page.${
17
23
  options.isTS ? 'ts' : 'js'
18
24
  }x`;