Skip to content

Commit fd1a8d4

Browse files
committed
Merge remote-tracking branch 'origin/canary' into sync-integrations-makeswift
# Conflicts: # core/app/layout.tsx
2 parents 5a3ec0b + e18feb6 commit fd1a8d4

22 files changed

Lines changed: 236 additions & 139 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Native Hosting
2+
3+
on:
4+
push:
5+
branches: [canary]
6+
7+
jobs:
8+
build-and-deploy:
9+
name: Build and Deploy
10+
runs-on: ubuntu-latest
11+
concurrency:
12+
group: ${{ github.workflow }}
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- uses: pnpm/action-setup@v3
18+
19+
- name: Use Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version-file: '.nvmrc'
23+
cache: 'pnpm'
24+
25+
- name: Install dependencies
26+
run: pnpm install --frozen-lockfile
27+
28+
- name: Install Catalyst CLI
29+
run: pnpm add @bigcommerce/catalyst@alpha @opennextjs/cloudflare@1.17.3
30+
working-directory: core
31+
32+
- name: Convert proxy.ts to middleware.ts
33+
working-directory: core
34+
run: |
35+
mv proxy.ts middleware.ts
36+
sed -i 's/export const proxy/export const middleware/' middleware.ts
37+
sed -i "s/export const config = {/export const config = {\n runtime: 'experimental-edge',/" middleware.ts
38+
39+
- name: Build monorepo packages
40+
run: pnpm --filter "./packages/*" build
41+
42+
- name: Generate GraphQL types
43+
run: pnpm run generate
44+
working-directory: core
45+
env:
46+
BIGCOMMERCE_STORE_HASH: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }}
47+
BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_STOREFRONT_TOKEN }}
48+
BIGCOMMERCE_CHANNEL_ID: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_CHANNEL_ID }}
49+
50+
- name: Build
51+
run: pnpm exec catalyst build
52+
working-directory: core
53+
env:
54+
# CLI env vars
55+
CATALYST_PROJECT_UUID: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_PROJECT_UUID }}
56+
# App env vars (needed by Next.js build for GraphQL calls in next.config.ts)
57+
BIGCOMMERCE_STORE_HASH: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }}
58+
BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_STOREFRONT_TOKEN }}
59+
BIGCOMMERCE_CHANNEL_ID: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_CHANNEL_ID }}
60+
AUTH_SECRET: ${{ secrets.NATIVE_HOSTING_AUTH_SECRET }}
61+
62+
- name: Deploy
63+
run: |
64+
pnpm exec catalyst deploy --prebuilt \
65+
--secret BIGCOMMERCE_STORE_HASH=${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }} \
66+
--secret BIGCOMMERCE_STOREFRONT_TOKEN=${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_STOREFRONT_TOKEN }} \
67+
--secret BIGCOMMERCE_CHANNEL_ID=${{ vars.NATIVE_HOSTING_BIGCOMMERCE_CHANNEL_ID }} \
68+
--secret AUTH_SECRET=${{ secrets.NATIVE_HOSTING_AUTH_SECRET }}
69+
working-directory: core
70+
env:
71+
CATALYST_STORE_HASH: ${{ vars.NATIVE_HOSTING_BIGCOMMERCE_STORE_HASH }}
72+
CATALYST_ACCESS_TOKEN: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_ACCESS_TOKEN }}
73+
CATALYST_PROJECT_UUID: ${{ secrets.NATIVE_HOSTING_BIGCOMMERCE_PROJECT_UUID }}

.github/workflows/regression-tests.yml

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,15 @@ on:
77
env:
88
VERCEL_PROTECTION_BYPASS: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
99

10-
concurrency:
11-
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.deployment_status.target_url }}
12-
cancel-in-progress: true
13-
1410
jobs:
1511
detect-provider:
1612
name: Detect Deployment Provider
1713
runs-on: ubuntu-latest
1814
outputs:
1915
provider: ${{ steps.detect.outputs.provider }}
20-
is-preview: ${{ steps.detect.outputs.is-preview }}
2116
production-url: ${{ steps.detect.outputs.production-url }}
22-
is-canary-branch: ${{ steps.detect.outputs.is-canary-branch }}
2317
branch-label: ${{ steps.detect.outputs.branch-label }}
18+
is-preview: ${{ steps.detect.outputs.is-preview }}
2419
steps:
2520
- uses: actions/checkout@v4
2621

@@ -30,13 +25,17 @@ jobs:
3025
CREATOR="${{ github.event.deployment_status.creator.login }}"
3126
ENVIRONMENT="${{ github.event.deployment.environment }}"
3227
28+
if [[ "$ENVIRONMENT" == "Preview" ]]; then
29+
echo "is-preview=true" >> $GITHUB_OUTPUT
30+
else
31+
echo "is-preview=false" >> $GITHUB_OUTPUT
32+
fi
33+
3334
if [[ "$CREATOR" == "vercel[bot]" ]]; then
3435
echo "provider=vercel" >> $GITHUB_OUTPUT
36+
PKG_NAME=$(node -p "require('./core/package.json').name")
37+
3538
if [[ "$ENVIRONMENT" == "Preview" ]]; then
36-
echo "is-preview=true" >> $GITHUB_OUTPUT
37-
echo "is-canary-branch=false" >> $GITHUB_OUTPUT
38-
echo "branch-label=" >> $GITHUB_OUTPUT
39-
PKG_NAME=$(node -p "require('./core/package.json').name")
4039
case "$PKG_NAME" in
4140
"@bigcommerce/catalyst-core")
4241
echo "production-url=https://canary.catalyst-demo.site/" >> $GITHUB_OUTPUT ;;
@@ -46,58 +45,45 @@ jobs:
4645
echo "::warning::No production URL configured for package: $PKG_NAME. Skipping comparison."
4746
echo "production-url=" >> $GITHUB_OUTPUT ;;
4847
esac
48+
echo "branch-label=" >> $GITHUB_OUTPUT
4949
else
50-
PKG_NAME=$(node -p "require('./core/package.json').name")
51-
echo "is-preview=false" >> $GITHUB_OUTPUT
5250
case "$PKG_NAME" in
5351
"@bigcommerce/catalyst-core")
54-
echo "is-canary-branch=true" >> $GITHUB_OUTPUT
5552
echo "branch-label=canary" >> $GITHUB_OUTPUT
5653
echo "production-url=${{ github.event.deployment_status.target_url }}" >> $GITHUB_OUTPUT ;;
5754
"@bigcommerce/catalyst-makeswift")
58-
echo "is-canary-branch=true" >> $GITHUB_OUTPUT
5955
echo "branch-label=integrations/makeswift" >> $GITHUB_OUTPUT
6056
echo "production-url=${{ github.event.deployment_status.target_url }}" >> $GITHUB_OUTPUT ;;
6157
*)
62-
echo "is-canary-branch=false" >> $GITHUB_OUTPUT
6358
echo "branch-label=" >> $GITHUB_OUTPUT
6459
echo "production-url=" >> $GITHUB_OUTPUT ;;
6560
esac
6661
fi
6762
6863
elif [[ "$CREATOR" == "cloudflare-pages[bot]" ]]; then
6964
echo "provider=cloudflare" >> $GITHUB_OUTPUT
70-
echo "is-canary-branch=false" >> $GITHUB_OUTPUT
65+
echo "::warning::Cloudflare production URL not yet configured. Skipping comparison."
66+
echo "production-url=" >> $GITHUB_OUTPUT
7167
echo "branch-label=" >> $GITHUB_OUTPUT
72-
if [[ "$ENVIRONMENT" == "Preview" ]]; then
73-
echo "is-preview=true" >> $GITHUB_OUTPUT
74-
echo "::warning::Cloudflare production URL not yet configured. Skipping comparison."
75-
echo "production-url=" >> $GITHUB_OUTPUT
76-
else
77-
echo "is-preview=false" >> $GITHUB_OUTPUT
78-
echo "production-url=" >> $GITHUB_OUTPUT
79-
fi
8068
8169
else
8270
echo "::warning::Unknown deployment provider: $CREATOR. Skipping audits."
8371
echo "provider=unknown" >> $GITHUB_OUTPUT
84-
echo "is-preview=false" >> $GITHUB_OUTPUT
85-
echo "is-canary-branch=false" >> $GITHUB_OUTPUT
86-
echo "branch-label=" >> $GITHUB_OUTPUT
8772
echo "production-url=" >> $GITHUB_OUTPUT
73+
echo "branch-label=" >> $GITHUB_OUTPUT
8874
fi
8975
9076
unlighthouse-audit-preview:
9177
name: Unlighthouse Audit Preview (${{ needs.detect-provider.outputs.provider }}) - ${{ matrix.device }}
9278
needs: [detect-provider]
9379
if: needs.detect-provider.outputs.is-preview == 'true'
9480
runs-on: ubuntu-latest
81+
concurrency:
82+
group: regression-preview-${{ github.event.deployment.ref }}-${{ matrix.device }}
83+
cancel-in-progress: true
9584
strategy:
9685
matrix:
9786
device: [desktop, mobile]
98-
concurrency:
99-
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.deployment_status.target_url }}-${{ matrix.device }}
100-
cancel-in-progress: true
10187

10288
steps:
10389
- uses: actions/checkout@v4
@@ -130,14 +116,14 @@ jobs:
130116
unlighthouse-audit-production:
131117
name: Unlighthouse Audit Production (${{ needs.detect-provider.outputs.provider }}) - ${{ matrix.device }}
132118
needs: [detect-provider]
133-
if: (needs.detect-provider.outputs.is-preview == 'true' && needs.detect-provider.outputs.production-url != '') || needs.detect-provider.outputs.is-canary-branch == 'true'
119+
if: needs.detect-provider.outputs.production-url != ''
134120
runs-on: ubuntu-latest
121+
concurrency:
122+
group: regression-production-${{ github.event.deployment.environment == 'Preview' && github.event.deployment.ref || github.event.deployment.sha }}-${{ matrix.device }}
123+
cancel-in-progress: ${{ github.event.deployment.environment == 'Preview' }}
135124
strategy:
136125
matrix:
137126
device: [desktop, mobile]
138-
concurrency:
139-
group: ${{ github.workflow }}-${{ github.ref }}-production-${{ matrix.device }}
140-
cancel-in-progress: true
141127

142128
steps:
143129
- uses: actions/checkout@v4
@@ -210,7 +196,7 @@ jobs:
210196
unlighthouse-report:
211197
name: Unlighthouse Report (${{ needs.detect-provider.outputs.provider }}) — ${{ needs.detect-provider.outputs.branch-label }}
212198
needs: [detect-provider, unlighthouse-audit-production]
213-
if: needs.detect-provider.outputs.is-canary-branch == 'true'
199+
if: needs.detect-provider.outputs.is-preview == 'false' && needs.detect-provider.outputs.production-url != ''
214200
runs-on: ubuntu-latest
215201
permissions:
216202
contents: write

core/app/[locale]/layout.tsx

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { Analytics } from '@vercel/analytics/react';
22
import { SpeedInsights } from '@vercel/speed-insights/next';
3+
import { clsx } from 'clsx';
34
import type { Metadata } from 'next';
45
import { notFound } from 'next/navigation';
56
import { NextIntlClientProvider } from 'next-intl';
67
import { setRequestLocale } from 'next-intl/server';
78
import { NuqsAdapter } from 'nuqs/adapters/next/app';
89
import { cache, PropsWithChildren } from 'react';
910

11+
import '../../globals.css';
12+
13+
import { fonts } from '~/app/fonts';
1014
import { CookieNotifications } from '~/app/notifications';
1115
import { Providers } from '~/app/providers';
1216
import { client } from '~/client';
@@ -136,32 +140,34 @@ export default async function RootLayout({ params, children }: Props) {
136140
const privacyPolicyUrl = rootData.data.site.settings?.privacy?.privacyPolicyUrl;
137141

138142
return (
139-
<>
140-
<NextIntlClientProvider>
141-
<ConsentManager
142-
isCookieConsentEnabled={isCookieConsentEnabled}
143-
privacyPolicyUrl={privacyPolicyUrl}
144-
scripts={scripts}
145-
>
146-
<NuqsAdapter>
147-
<AnalyticsProvider
148-
channelId={rootData.data.channel.entityId}
149-
isCookieConsentEnabled={isCookieConsentEnabled}
150-
settings={rootData.data.site.settings}
151-
>
152-
<Providers>
153-
{toastNotificationCookieData && (
154-
<CookieNotifications {...toastNotificationCookieData} />
155-
)}
156-
{children}
157-
</Providers>
158-
</AnalyticsProvider>
159-
</NuqsAdapter>
160-
</ConsentManager>
161-
</NextIntlClientProvider>
162-
<VercelComponents />
163-
<ContainerQueryPolyfill />
164-
</>
143+
<html className={clsx(fonts.map((f) => f.variable))} lang={locale}>
144+
<body className="flex min-h-screen flex-col">
145+
<NextIntlClientProvider>
146+
<ConsentManager
147+
isCookieConsentEnabled={isCookieConsentEnabled}
148+
privacyPolicyUrl={privacyPolicyUrl}
149+
scripts={scripts}
150+
>
151+
<NuqsAdapter>
152+
<AnalyticsProvider
153+
channelId={rootData.data.channel.entityId}
154+
isCookieConsentEnabled={isCookieConsentEnabled}
155+
settings={rootData.data.site.settings}
156+
>
157+
<Providers>
158+
{toastNotificationCookieData && (
159+
<CookieNotifications {...toastNotificationCookieData} />
160+
)}
161+
{children}
162+
</Providers>
163+
</AnalyticsProvider>
164+
</NuqsAdapter>
165+
</ConsentManager>
166+
</NextIntlClientProvider>
167+
<VercelComponents />
168+
<ContainerQueryPolyfill />
169+
</body>
170+
</html>
165171
);
166172
}
167173

core/app/layout.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
import { getSiteVersion } from '@makeswift/runtime/next/server';
2-
import { clsx } from 'clsx';
32
import { PropsWithChildren } from 'react';
43

5-
import '../globals.css';
6-
7-
import { fonts } from '~/app/fonts';
84
import { SiteTheme } from '~/lib/makeswift/components/site-theme';
95
import { MakeswiftProvider } from '~/lib/makeswift/provider';
106

117
import '~/lib/makeswift/components';
128

9+
// Since we have a `not-found.tsx` at the root, a layout file is required even if
10+
// it just passes children through. Ownership of <html>/<body> lives in
11+
// app/[locale]/layout.tsx (to set lang={locale}) and app/not-found.tsx (for
12+
// non-localized 404s). See: https://next-intl.dev/docs/environments/error-files
13+
// TODO: Move <html>/<body> back here and set lang via Next.js `rootParams`
14+
// once it is available on Native Hosting (Next.js 16.2+).
1315
export default async function RootLayout({ children }: PropsWithChildren) {
1416
const siteVersion = await getSiteVersion();
1517

1618
return (
1719
<MakeswiftProvider siteVersion={siteVersion}>
18-
<html className={clsx(fonts.map((f) => f.variable))} lang="en">
19-
<head>
20-
<SiteTheme />
21-
</head>
22-
<body className="flex min-h-screen flex-col">{children}</body>
23-
</html>
20+
<SiteTheme />
21+
{children}
2422
</MakeswiftProvider>
2523
);
2624
}

core/app/not-found.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
1+
import { clsx } from 'clsx';
2+
3+
import '../globals.css';
4+
5+
import { fonts } from '~/app/fonts';
6+
7+
// Renders for non-localized requests that don't match any route (e.g. /unknown.txt)
8+
// or when app/[locale]/layout.tsx calls notFound() for an invalid locale. Since
9+
// app/layout.tsx is a passthrough, this page must own its own <html>/<body>.
110
export default function RootNotFound() {
211
return (
3-
<main className="flex flex-1 items-center justify-center font-[family-name:var(--not-found-font-family,var(--font-family-body))]">
4-
<div className="mx-auto w-full max-w-screen-2xl px-3 py-10 @container @xl:px-6 @4xl:px-20">
5-
<header className="text-center">
6-
<h1 className="mb-3 font-[family-name:var(--not-found-title-font-family,var(--font-family-heading))] text-3xl font-medium leading-none text-[var(--not-found-title,hsl(var(--foreground)))] @xl:text-4xl @4xl:text-5xl">
7-
Not found
8-
</h1>
9-
<p className="mb-4 text-lg text-[var(--not-found-subtitle,hsl(var(--contrast-500)))]">
10-
The page you are looking for could not be found.
11-
</p>
12-
<a
13-
className="relative z-0 inline-flex min-h-14 select-none items-center justify-center gap-x-3 overflow-hidden rounded-full border border-[var(--button-primary-border,hsl(var(--primary)))] bg-[var(--button-primary-background,hsl(var(--primary)))] px-6 py-4 text-center font-[family-name:var(--button-font-family)] text-base font-semibold leading-normal text-[var(--button-primary-text)] after:absolute after:inset-0 after:-z-10 after:-translate-x-[105%] after:rounded-full after:bg-[var(--button-primary-background-hover,color-mix(in_oklab,hsl(var(--primary)),white_75%))] after:transition-[opacity,transform] after:duration-300 after:[animation-timing-function:cubic-bezier(0,0.25,0,1)] hover:after:translate-x-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--button-focus,hsl(var(--primary)))] focus-visible:ring-offset-2"
14-
href="/"
15-
>
16-
<span>Go to homepage</span>
17-
</a>
18-
</header>
19-
</div>
20-
</main>
12+
<html className={clsx(fonts.map((f) => f.variable))} lang="en">
13+
<body className="flex min-h-screen flex-col">
14+
<main className="flex flex-1 items-center justify-center font-[family-name:var(--not-found-font-family,var(--font-family-body))]">
15+
<div className="mx-auto w-full max-w-screen-2xl px-3 py-10 @container @xl:px-6 @4xl:px-20">
16+
<header className="text-center">
17+
<h1 className="mb-3 font-[family-name:var(--not-found-title-font-family,var(--font-family-heading))] text-3xl font-medium leading-none text-[var(--not-found-title,hsl(var(--foreground)))] @xl:text-4xl @4xl:text-5xl">
18+
Not found
19+
</h1>
20+
<p className="mb-4 text-lg text-[var(--not-found-subtitle,hsl(var(--contrast-500)))]">
21+
The page you are looking for could not be found.
22+
</p>
23+
<a
24+
className="relative z-0 inline-flex min-h-14 select-none items-center justify-center gap-x-3 overflow-hidden rounded-full border border-[var(--button-primary-border,hsl(var(--primary)))] bg-[var(--button-primary-background,hsl(var(--primary)))] px-6 py-4 text-center font-[family-name:var(--button-font-family)] text-base font-semibold leading-normal text-[var(--button-primary-text)] after:absolute after:inset-0 after:-z-10 after:-translate-x-[105%] after:rounded-full after:bg-[var(--button-primary-background-hover,color-mix(in_oklab,hsl(var(--primary)),white_75%))] after:transition-[opacity,transform] after:duration-300 after:[animation-timing-function:cubic-bezier(0,0.25,0,1)] hover:after:translate-x-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--button-focus,hsl(var(--primary)))] focus-visible:ring-offset-2"
25+
href="/"
26+
>
27+
<span>Go to homepage</span>
28+
</a>
29+
</header>
30+
</div>
31+
</main>
32+
</body>
33+
</html>
2134
);
2235
}

core/client/correlation-id.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { cache } from 'react';
2+
3+
export const getCorrelationId = cache(() => crypto.randomUUID());

core/client/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export const client = createClient({
5252
const requestHeaders: Record<string, string> = {};
5353
const locale = await getLocale();
5454

55+
try {
56+
const { getCorrelationId } = await import('./correlation-id');
57+
58+
requestHeaders['X-Correlation-ID'] = getCorrelationId();
59+
} catch {
60+
// correlation-id imports React.cache which is unavailable during next.config.ts resolution
61+
}
62+
5563
if (fetchOptions?.cache && ['no-store', 'no-cache'].includes(fetchOptions.cache)) {
5664
const { headers } = await import('next/headers');
5765
const ipAddress = (await headers()).get('X-Forwarded-For');

0 commit comments

Comments
 (0)