Skip to content

Commit 06e8c9d

Browse files
authored
Merge branch 'develop' into onur/remix-v2
2 parents 6f14958 + 130e4a3 commit 06e8c9d

File tree

55 files changed

+1449
-595
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1449
-595
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ jobs:
661661
needs: [job_get_metadata, job_build]
662662
if: needs.job_get_metadata.outputs.changed_node == 'true' || github.event_name != 'pull_request'
663663
runs-on: ubuntu-20.04
664-
timeout-minutes: 10
664+
timeout-minutes: 15
665665
strategy:
666666
fail-fast: false
667667
matrix:
@@ -738,11 +738,11 @@ jobs:
738738
github.actor != 'dependabot[bot]'
739739
needs: [job_get_metadata, job_build]
740740
runs-on: ubuntu-20.04
741-
timeout-minutes: 20
741+
timeout-minutes: 30
742742
strategy:
743743
fail-fast: false
744744
matrix:
745-
shard: [1, 2, 3]
745+
shard: [1, 2, 3, 4]
746746

747747
steps:
748748
- name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
@@ -773,7 +773,7 @@ jobs:
773773
E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks'
774774
E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests'
775775
E2E_TEST_SHARD: ${{ matrix.shard }}
776-
E2E_TEST_SHARD_AMOUNT: 3
776+
E2E_TEST_SHARD_AMOUNT: 4
777777
run: |
778778
cd packages/e2e-tests
779779
yarn test:e2e

LICENSE

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
Copyright (c) 2018 Sentry (https://sentry.io) and individual contributors. All rights reserved.
1+
MIT License
22

3-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4-
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
5-
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
6-
persons to whom the Software is furnished to do so, subject to the following conditions:
3+
Copyright (c) 2022 Functional Software, Inc. dba Sentry
74

8-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
9-
Software.
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
this software and associated documentation files (the "Software"), to deal in
7+
the Software without restriction, including without limitation the rights to
8+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9+
of the Software, and to permit persons to whom the Software is furnished to do
10+
so, subject to the following conditions:
1011

11-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
12-
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
13-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
14-
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/e2e-tests/lib/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ export interface RecipeTestResult extends RecipeInstance {
4747
tests: TestResult[];
4848
}
4949

50-
export type Env = Record<string, string | string>;
50+
export type Env = Record<string, string | undefined>;

packages/e2e-tests/lib/utils.ts

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,50 @@ interface SpawnAsync {
1717
status: number | null;
1818
}
1919

20-
export function spawnAsync(
21-
cmd: string,
22-
options?:
23-
| childProcess.SpawnOptionsWithoutStdio
24-
| childProcess.SpawnOptionsWithStdioTuple<childProcess.StdioPipe, childProcess.StdioPipe, childProcess.StdioPipe>,
25-
input?: string,
26-
): Promise<SpawnAsync> {
27-
const start = Date.now();
20+
interface SpawnOptions {
21+
timeout?: number;
22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23+
env?: any;
24+
cwd?: string;
25+
}
26+
27+
export function spawnAsync(cmd: string, options?: SpawnOptions, input?: string): Promise<SpawnAsync> {
28+
const timeoutMs = options?.timeout || 60_000 * 5;
2829

2930
return new Promise<SpawnAsync>(resolve => {
3031
const cp = childProcess.spawn(cmd, { shell: true, ...options });
3132

33+
// Ensure we properly time out after max. 5 min per command
34+
let timeout: ReturnType<typeof setTimeout> | undefined = setTimeout(() => {
35+
console.log(`Command "${cmd}" timed out after 5 minutes.`);
36+
cp.kill();
37+
end(null, `ETDIMEDOUT: Process timed out after ${timeoutMs} ms.`);
38+
}, timeoutMs);
39+
3240
const stderr: unknown[] = [];
3341
const stdout: string[] = [];
3442
let error: Error | undefined;
3543

44+
function end(status: number | null, errorMessage?: string): void {
45+
// This means we already ended
46+
if (!timeout) {
47+
return;
48+
}
49+
50+
if (!error && errorMessage) {
51+
error = new Error(errorMessage);
52+
}
53+
54+
clearTimeout(timeout);
55+
timeout = undefined;
56+
resolve({
57+
stdout: stdout.join(''),
58+
stderr: stderr.join(''),
59+
error: error || (status !== 0 ? new Error(`Process exited with status ${status}`) : undefined),
60+
status,
61+
});
62+
}
63+
3664
cp.stdout.on('data', data => {
3765
stdout.push(data ? (data as object).toString() : '');
3866
});
@@ -46,19 +74,7 @@ export function spawnAsync(
4674
});
4775

4876
cp.on('close', status => {
49-
const end = Date.now();
50-
51-
// We manually mark this as timed out if the process takes too long
52-
if (!error && status === 1 && options?.timeout && end >= start + options.timeout) {
53-
error = new Error(`ETDIMEDOUT: Process timed out after ${options.timeout} ms.`);
54-
}
55-
56-
resolve({
57-
stdout: stdout.join(''),
58-
stderr: stderr.join(''),
59-
error: error || (status !== 0 ? new Error(`Process exited with status ${status}`) : undefined),
60-
status,
61-
});
77+
end(status);
6278
});
6379

6480
if (input) {

packages/e2e-tests/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
"fix": "run-s fix:eslint fix:prettier",
1111
"fix:eslint": "eslint . --format stylish --fix",
1212
"fix:prettier": "prettier --config ../../.prettierrc.json --write . ",
13-
"lint": "run-s lint:prettier lint:eslint",
13+
"lint": "run-s lint:prettier lint:eslint lint:ts",
1414
"lint:eslint": "eslint . --format stylish",
1515
"lint:prettier": "prettier --config ../../.prettierrc.json --check .",
16+
"lint:ts": "tsc --noEmit",
1617
"test:e2e": "run-s test:validate-configuration test:validate-test-app-setups test:run",
1718
"test:run": "ts-node run.ts",
1819
"test:validate-configuration": "ts-node validate-verdaccio-configuration.ts",

packages/e2e-tests/run.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async function run(): Promise<void> {
1717

1818
const envVarsToInject = {
1919
REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
20+
REMIX_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2021
NEXT_PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2122
PUBLIC_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
2223
BASE_PORT: '27496', // just some random port
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/** @type {import('eslint').Linter.Config} */
2+
module.exports = {
3+
extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'],
4+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
3+
/.cache
4+
/build
5+
/public/build
6+
.env
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@sentry:registry=http://localhost:4873
2+
@sentry-internal:registry=http://localhost:4873
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* By default, Remix will handle hydrating your app on the client for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.client
5+
*/
6+
7+
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
8+
import { startTransition, StrictMode, useEffect } from 'react';
9+
import { hydrateRoot } from 'react-dom/client';
10+
import * as Sentry from '@sentry/remix';
11+
12+
Sentry.init({
13+
dsn: process.env.REMIX_APP_E2E_TEST_DSN,
14+
integrations: [
15+
new Sentry.BrowserTracing({
16+
routingInstrumentation: Sentry.remixRouterInstrumentation(useEffect, useLocation, useMatches),
17+
}),
18+
new Sentry.Replay(),
19+
],
20+
// Performance Monitoring
21+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
22+
// Session Replay
23+
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
24+
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
25+
});
26+
27+
startTransition(() => {
28+
hydrateRoot(
29+
document,
30+
<StrictMode>
31+
<RemixBrowser />
32+
</StrictMode>,
33+
);
34+
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* By default, Remix will handle generating the HTTP Response for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.server
5+
*/
6+
7+
import { PassThrough } from 'node:stream';
8+
9+
import type { AppLoadContext, EntryContext } from '@remix-run/node';
10+
import { Response } from '@remix-run/node';
11+
import { RemixServer } from '@remix-run/react';
12+
import isbot from 'isbot';
13+
import { renderToPipeableStream } from 'react-dom/server';
14+
import * as Sentry from '@sentry/remix';
15+
16+
const ABORT_DELAY = 5_000;
17+
18+
Sentry.init({
19+
dsn: process.env.REMIX_APP_E2E_TEST_DSN,
20+
// Performance Monitoring
21+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
22+
});
23+
24+
export default function handleRequest(
25+
request: Request,
26+
responseStatusCode: number,
27+
responseHeaders: Headers,
28+
remixContext: EntryContext,
29+
loadContext: AppLoadContext,
30+
) {
31+
return isbot(request.headers.get('user-agent'))
32+
? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
33+
: handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
34+
}
35+
36+
function handleBotRequest(
37+
request: Request,
38+
responseStatusCode: number,
39+
responseHeaders: Headers,
40+
remixContext: EntryContext,
41+
) {
42+
return new Promise((resolve, reject) => {
43+
const { pipe, abort } = renderToPipeableStream(
44+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
45+
{
46+
onAllReady() {
47+
const body = new PassThrough();
48+
49+
responseHeaders.set('Content-Type', 'text/html');
50+
51+
resolve(
52+
new Response(body, {
53+
headers: responseHeaders,
54+
status: responseStatusCode,
55+
}),
56+
);
57+
58+
pipe(body);
59+
},
60+
onShellError(error: unknown) {
61+
reject(error);
62+
},
63+
onError(error: unknown) {
64+
responseStatusCode = 500;
65+
console.error(error);
66+
},
67+
},
68+
);
69+
70+
setTimeout(abort, ABORT_DELAY);
71+
});
72+
}
73+
74+
function handleBrowserRequest(
75+
request: Request,
76+
responseStatusCode: number,
77+
responseHeaders: Headers,
78+
remixContext: EntryContext,
79+
) {
80+
return new Promise((resolve, reject) => {
81+
const { pipe, abort } = renderToPipeableStream(
82+
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />,
83+
{
84+
onShellReady() {
85+
const body = new PassThrough();
86+
87+
responseHeaders.set('Content-Type', 'text/html');
88+
89+
resolve(
90+
new Response(body, {
91+
headers: responseHeaders,
92+
status: responseStatusCode,
93+
}),
94+
);
95+
96+
pipe(body);
97+
},
98+
onShellError(error: unknown) {
99+
reject(error);
100+
},
101+
onError(error: unknown) {
102+
console.error(error);
103+
responseStatusCode = 500;
104+
},
105+
},
106+
);
107+
108+
setTimeout(abort, ABORT_DELAY);
109+
});
110+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { cssBundleHref } from '@remix-run/css-bundle';
2+
import type { LinksFunction } from '@remix-run/node';
3+
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
4+
import { withSentry } from '@sentry/remix';
5+
6+
export const links: LinksFunction = () => [...(cssBundleHref ? [{ rel: 'stylesheet', href: cssBundleHref }] : [])];
7+
8+
function App() {
9+
return (
10+
<html lang="en">
11+
<head>
12+
<meta charSet="utf-8" />
13+
<meta name="viewport" content="width=device-width,initial-scale=1" />
14+
<Meta />
15+
<Links />
16+
</head>
17+
<body>
18+
<Outlet />
19+
<ScrollRestoration />
20+
<Scripts />
21+
<LiveReload />
22+
</body>
23+
</html>
24+
);
25+
}
26+
27+
export default withSentry(App);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { V2_MetaFunction } from '@remix-run/node';
2+
3+
export const meta: V2_MetaFunction = () => {
4+
return [{ title: 'New Remix App' }, { name: 'description', content: 'Welcome to Remix!' }];
5+
};
6+
7+
export default function Index() {
8+
return (
9+
<div style={{ fontFamily: 'system-ui, sans-serif', lineHeight: '1.8' }}>
10+
<h1>Welcome to Remix</h1>
11+
<ul>
12+
<li>
13+
<a target="_blank" href="https://remix.run/tutorials/blog" rel="noreferrer">
14+
15m Quickstart Blog Tutorial
15+
</a>
16+
</li>
17+
<li>
18+
<a target="_blank" href="https://remix.run/tutorials/jokes" rel="noreferrer">
19+
Deep Dive Jokes App Tutorial
20+
</a>
21+
</li>
22+
<li>
23+
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
24+
Remix Docs
25+
</a>
26+
</li>
27+
</ul>
28+
</div>
29+
);
30+
}

0 commit comments

Comments
 (0)