Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ jobs:
# See: https://github.com/actions/runner/issues/2205
if: always() && needs.job_e2e_prepare.result == 'success' && needs.job_e2e_prepare.outputs.matrix != '{"include":[]}'
needs: [job_get_metadata, job_build, job_e2e_prepare]
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this: cloudflare/workerd#3411

timeout-minutes: 15
env:
# We just use a dummy DSN here, only send to the tunnel anyhow
Expand Down
2 changes: 2 additions & 0 deletions dev-packages/e2e-tests/test-applications/remix-hydrogen/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SESSION_SECRET = "foo"
PUBLIC_STORE_DOMAIN="mock.shop"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build
node_modules
bin
*.d.ts
dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* This is intended to be a basic starting point for linting in your app.
* It relies on recommended configs out of the box for simplicity, but you can
* and should modify this configuration to best suit your team's needs.
*/

/** @type {import('eslint').Linter.Config} */
module.exports = {
root: true,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
commonjs: true,
es6: true,
},

// Base config
extends: ['eslint:recommended'],

overrides: [
// React
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: ['react', 'jsx-a11y'],
extends: [
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
],
settings: {
react: {
version: 'detect',
},
formComponents: ['Form'],
linkComponents: [
{ name: 'Link', linkAttribute: 'to' },
{ name: 'NavLink', linkAttribute: 'to' },
],
'import/resolver': {
typescript: {},
},
},
},

// Typescript
{
files: ['**/*.{ts,tsx}'],
plugins: ['@typescript-eslint', 'import'],
parser: '@typescript-eslint/parser',
settings: {
'import/internal-regex': '^~/',
'import/resolver': {
node: {
extensions: ['.ts', '.tsx'],
},
typescript: {
alwaysTryTypes: true,
},
},
},
extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/recommended', 'plugin:import/typescript'],
},

// Node
{
files: ['.eslintrc.cjs', 'server.ts'],
env: {
node: true,
},
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
/.cache
/build
/dist
/public/build
/.mf
!.env
.shopify
storefrontapi.generated.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://127.0.0.1:4873
@sentry-internal:registry=http://127.0.0.1:4873
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { RemixBrowser, useLocation, useMatches } from '@remix-run/react';
import * as Sentry from '@sentry/remix/cloudflare';
import { StrictMode, startTransition } from 'react';
import { useEffect } from 'react';
import { hydrateRoot } from 'react-dom/client';

Sentry.init({
environment: 'qa', // dynamic sampling bias to keep transactions
// Could not find a working way to set the DSN in the browser side from the environment variables
dsn: 'https://[email protected]/1337',
debug: true,
integrations: [
Sentry.browserTracingIntegration({
useEffect,
useLocation,
useMatches,
}),
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],

tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
tunnel: 'http://localhost:3031/', // proxy server
});

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>,
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { RemixServer } from '@remix-run/react';
import { createContentSecurityPolicy } from '@shopify/hydrogen';
import type { EntryContext } from '@shopify/remix-oxygen';
import isbot from 'isbot';
import { renderToReadableStream } from 'react-dom/server';

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const { nonce, header, NonceProvider } = createContentSecurityPolicy({
connectSrc: [
// Need to allow the proxy server to fetch the data
'http://localhost:3031/',
],
});

const body = await renderToReadableStream(
<NonceProvider>
<RemixServer context={remixContext} url={request.url} />
</NonceProvider>,
{
nonce,
signal: request.signal,
onError(error) {
// eslint-disable-next-line no-console
console.error(error);
responseStatusCode = 500;
},
},
);

if (isbot(request.headers.get('user-agent'))) {
await body.allReady;
}

responseHeaders.set('Content-Type', 'text/html');
responseHeaders.set('Content-Security-Policy', header);

// Add the document policy header to enable JS profiling
// This is required for Sentry's profiling integration
responseHeaders.set('Document-Policy', 'js-profiling');

return new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createPagesFunctionHandler } from '@remix-run/cloudflare-pages';
import { sentryPagesPlugin } from '@sentry/cloudflare';

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - the server build file is generated by `remix vite:build`
// eslint-disable-next-line import/no-unresolved
import * as build from '../build/server';

export const onRequest = [
context => sentryPagesPlugin({ dsn: context.env.E2E_TEST_DSN, tracesSampleRate: 1.0 })(context),
createPagesFunctionHandler({ build }),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// NOTE: https://shopify.dev/docs/api/storefront/latest/queries/cart
export const CART_QUERY_FRAGMENT = `#graphql
fragment Money on MoneyV2 {
currencyCode
amount
}
fragment CartLine on CartLine {
id
quantity
attributes {
key
value
}
cost {
totalAmount {
...Money
}
amountPerQuantity {
...Money
}
compareAtAmountPerQuantity {
...Money
}
}
merchandise {
... on ProductVariant {
id
availableForSale
compareAtPrice {
...Money
}
price {
...Money
}
requiresShipping
title
image {
id
url
altText
width
height

}
product {
handle
title
id
vendor
}
selectedOptions {
name
value
}
}
}
}
fragment CartApiQuery on Cart {
updatedAt
id
checkoutUrl
totalQuantity
buyerIdentity {
countryCode
customer {
id
email
firstName
lastName
displayName
}
email
phone
}
lines(first: $numCartLines) {
nodes {
...CartLine
}
}
cost {
subtotalAmount {
...Money
}
totalAmount {
...Money
}
totalDutyAmount {
...Money
}
totalTaxAmount {
...Money
}
}
note
attributes {
key
value
}
discountCodes {
code
applicable
}
}
` as const;

const MENU_FRAGMENT = `#graphql
fragment MenuItem on MenuItem {
id
resourceId
tags
title
type
url
}
fragment ChildMenuItem on MenuItem {
...MenuItem
}
fragment ParentMenuItem on MenuItem {
...MenuItem
items {
...ChildMenuItem
}
}
fragment Menu on Menu {
id
items {
...ParentMenuItem
}
}
` as const;

export const HEADER_QUERY = `#graphql
fragment Shop on Shop {
id
name
description
primaryDomain {
url
}
brand {
logo {
image {
url
}
}
}
}
query Header(
$country: CountryCode
$headerMenuHandle: String!
$language: LanguageCode
) @inContext(language: $language, country: $country) {
shop {
...Shop
}
menu(handle: $headerMenuHandle) {
...Menu
}
}
${MENU_FRAGMENT}
` as const;

export const FOOTER_QUERY = `#graphql
query Footer(
$country: CountryCode
$footerMenuHandle: String!
$language: LanguageCode
) @inContext(language: $language, country: $country) {
menu(handle: $footerMenuHandle) {
...Menu
}
}
${MENU_FRAGMENT}
` as const;
Loading
Loading