Skip to content

Commit 7267f25

Browse files
committed
Merge branch 'canary' into sync-integrations-makeswift
2 parents a2e290f + 7c626a7 commit 7267f25

File tree

90 files changed

+3198
-522
lines changed

Some content is hidden

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

90 files changed

+3198
-522
lines changed

.github/scripts/prevent-invalid-changesets.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = async ({ core, exec }) => {
1616

1717
const allFilenames = stdout.split("\n").filter((line) => line.trim());
1818
const changesetFilenames = allFilenames.filter(
19-
(file) => file.startsWith(".changeset/") && file.endsWith(".md")
19+
(file) => file.startsWith(".changeset/") && file.endsWith(".md"),
2020
);
2121

2222
if (changesetFilenames.length === 0) {
@@ -45,8 +45,10 @@ module.exports = async ({ core, exec }) => {
4545
}
4646

4747
if (!fs.existsSync(filename)) {
48-
core.setFailed(`File not found: ${filename}`);
49-
return;
48+
core.warning(
49+
`File not found: ${filename}. This is likely a version PR where the changeset was already consumed. Skipping validation for this file.`,
50+
);
51+
continue;
5052
}
5153

5254
// check file size (limit to 100KB)
@@ -83,18 +85,18 @@ module.exports = async ({ core, exec }) => {
8385

8486
if (packageMatches) {
8587
const invalidPackages = packageMatches.filter(
86-
(pkg) => pkg !== '"@bigcommerce/catalyst-makeswift"'
88+
(pkg) => pkg !== '"@bigcommerce/catalyst-makeswift"',
8789
);
8890

8991
if (invalidPackages.length > 0) {
9092
core.error(
9193
`Invalid package found in changeset file. Only @bigcommerce/catalyst-makeswift is allowed.`,
92-
{ file: filename }
94+
{ file: filename },
9395
);
9496
core.setFailed(
9597
`File ${filename} contains invalid packages: ${invalidPackages.join(
96-
", "
97-
)}`
98+
", ",
99+
)}`,
98100
);
99101
return;
100102
}

.github/workflows/basic.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Basic
22

33
on:
44
push:
5-
branches: [canary]
5+
branches: [canary, integrations/makeswift, integrations/b2b-makeswift]
66
pull_request:
77
types: [opened, synchronize]
88
merge_group:

.github/workflows/deploy.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Production Tag Deployment
2+
env:
3+
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
4+
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
5+
on:
6+
push:
7+
tags:
8+
- "@bigcommerce/catalyst-core@latest"
9+
- "@bigcommerce/catalyst-makeswift@latest"
10+
- "@bigcommerce/catalyst-b2b-makeswift@latest"
11+
jobs:
12+
deploy-tag:
13+
name: Deploy `{{ github.ref_name }}` tag
14+
runs-on: ubuntu-latest
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref_name }}
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Install Vercel CLI
21+
run: npm install --global vercel@latest
22+
23+
- name: Parse Tag and Build Domain
24+
id: parse-tag
25+
run: |
26+
TAG="${{ github.ref_name }}"
27+
PACKAGE_NAME=$(echo "$TAG" | sed -E 's/^@bigcommerce\/([^@]+)@.*$/\1/')
28+
case "$PACKAGE_NAME" in
29+
"catalyst-core") DOMAIN="catalyst-demo.site" ;;
30+
"catalyst-makeswift") DOMAIN="makeswift.catalyst-demo.site" ;;
31+
"catalyst-b2b-makeswift") DOMAIN="b2b-makeswift.catalyst-demo.site" ;;
32+
*) echo "Error: Unknown package name: $PACKAGE_NAME"; exit 1 ;;
33+
esac
34+
echo "domain=$DOMAIN" >> $GITHUB_OUTPUT
35+
36+
- name: Deploy to Vercel
37+
id: deploy
38+
run: |
39+
DEPLOYMENT_URL=$(vercel deploy --token=${{ secrets.VERCEL_TOKEN }})
40+
echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT
41+
42+
- name: Set Vercel Domain Alias
43+
run: |
44+
vercel alias ${{ steps.deploy.outputs.deployment_url }} ${{ steps.parse-tag.outputs.domain }} --token=${{ secrets.VERCEL_TOKEN }}

core/.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,16 @@ TURBO_REMOTE_CACHE_SIGNATURE_KEY=
4040
# https://nextjs.org/docs/app/building-your-application/caching#data-cache
4141
# This sets a sensible revalidation target for cached requests
4242
DEFAULT_REVALIDATE_TARGET=3600
43+
44+
# OpenTelemetry Configuration (Optional)
45+
# See OPENTELEMETRY.md for detailed setup and usage instructions
46+
# See https://nextjs.org/docs/app/guides/open-telemetry for Next.js guide
47+
48+
# Set a custom service name for your traces (defaults to 'next-app')
49+
# OTEL_SERVICE_NAME=catalyst-storefront
50+
51+
# Enable verbose tracing to see all spans (useful for debugging, increases data volume)
52+
# NEXT_OTEL_VERBOSE=1
53+
54+
# Disable automatic fetch instrumentation (not recommended unless using custom instrumentation)
55+
# NEXT_OTEL_FETCH_DISABLED=1

core/AGENTS.md

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
# AGENTS.md
2+
3+
## BigCommerce Catalyst Codebase Overview
4+
5+
This document provides guidance for Large Language Models (LLMs) working with the BigCommerce Catalyst codebase, focusing on the **Next.js App Router application** architecture, data fetching patterns, and key design principles.
6+
7+
**Catalyst is built as a Next.js App Router application** with React Server Components, enabling server-side data fetching, automatic code splitting, and optimal performance for e-commerce workloads.
8+
9+
## Repository Structure
10+
11+
The main Next.js application is located in the `/core` directory, which contains the complete e-commerce storefront implementation. Other packages exist outside of `/core` but are not the primary focus for most development work.
12+
13+
## Middleware Architecture
14+
15+
The application uses a composed middleware stack that significantly alters the default Next.js routing behavior. The middleware composition includes authentication, internationalization, analytics, channel handling, and most importantly, custom routing.
16+
17+
### Custom Routing with `with-routes` Middleware
18+
19+
The `with-routes` middleware is the most critical component that overrides Next.js's default path-based routing. Instead of relying on file-based routing, this middleware:
20+
21+
1. **Queries the BigCommerce GraphQL API** to resolve incoming URL paths to specific entity types (products, categories, brands, blog posts, pages).
22+
23+
2. **Rewrites requests** to internal Next.js routes based on the resolved entity type.
24+
25+
3. **Handles redirects** automatically based on BigCommerce's redirect configuration.
26+
27+
This means that URLs like `/my-product-name` can resolve to `/en/product/123` internally, providing flexible URL structure while maintaining SEO-friendly paths.
28+
29+
## Data Fetching and Partial Prerendering (PPR)
30+
31+
### PPR Configuration
32+
33+
The application uses Next.js Partial Prerendering with incremental adoption. This allows static parts of pages to be prerendered while dynamic content streams in.
34+
35+
### Streamable Pattern
36+
37+
The `Streamable<T>` pattern is a core architectural concept that enables efficient data streaming and React Server Component compatibility.
38+
39+
#### What is Streamable?
40+
41+
```typescript
42+
export type Streamable<T> = T | Promise<T>;
43+
```
44+
45+
A `Streamable<T>` represents data that can be either:
46+
- **Immediate**: Already resolved data of type `T`
47+
- **Deferred**: A Promise that will resolve to type `T`
48+
49+
#### Core Streamable API
50+
51+
Located in `core/vibes/soul/lib/streamable.tsx`, the Streamable system provides:
52+
53+
**`Streamable.from()`** - Creates a streamable from a lazy promise factory:
54+
```typescript
55+
const streamableProducts = Streamable.from(async () => {
56+
const customerToken = await getSessionCustomerAccessToken();
57+
const currencyCode = await getPreferredCurrencyCode();
58+
return getProducts(customerToken, currencyCode);
59+
});
60+
```
61+
62+
**`Streamable.all()`** - Combines multiple streamables with automatic caching:
63+
```typescript
64+
const combined = Streamable.all([
65+
streamableProducts,
66+
streamableCategories,
67+
streamableUser
68+
]);
69+
```
70+
71+
**`useStreamable()`** - Hook for consuming streamables in components:
72+
```typescript
73+
function MyComponent({ data }: { data: Streamable<Product[]> }) {
74+
const products = useStreamable(data);
75+
return <div>{products.map(...)}</div>;
76+
}
77+
```
78+
79+
**`<Stream>` Component** - Provides Suspense boundary for streamable data:
80+
```tsx
81+
<Stream value={streamableProducts} fallback={<ProductSkeleton />}>
82+
{(products) => <ProductList products={products} />}
83+
</Stream>
84+
```
85+
86+
#### Streamable Benefits
87+
88+
- **Performance**: Enables concurrent data fetching and streaming
89+
- **Caching**: Automatic promise deduplication and stability
90+
- **Flexibility**: Works with both sync and async data
91+
- **Suspense Integration**: Built-in React Suspense support
92+
- **Composition**: Easy chaining and combination of data sources
93+
94+
### Data Fetching Best Practices
95+
96+
1. **Use React's `cache()` function** for server-side data fetching to memoize function results and prevent repeated fetches or computations **per request** (React will invalidate the cache for all memoized functions for each server request).
97+
98+
2. **Implement proper cache strategies** based on whether user authentication is present.
99+
100+
3. **Leverage Streamable for progressive enhancement** where static content loads immediately and dynamic content streams in.
101+
102+
## GraphQL API Client
103+
104+
### Centralized Client Configuration
105+
106+
All interactions with the BigCommerce Storefront GraphQL API should use the centralized GraphQL client. This client provides:
107+
108+
- Automatic channel ID resolution based on locale
109+
- Proper authentication token handling
110+
- Request/response logging in development
111+
- Error handling with automatic auth redirects
112+
- IP address forwarding for personalization
113+
114+
### Usage Pattern
115+
116+
Always import and use the configured client rather than making direct API calls. The client handles all the necessary headers, authentication, and channel context automatically.
117+
118+
## UI Design System (Vibes)
119+
120+
### Architecture Overview
121+
122+
The `vibes/` directory contains the **highly customizable and styleable UI layer** that is completely separate from data fetching and business logic. This separation enables:
123+
124+
- **Complete visual customization** without touching data logic
125+
- **Theme-based styling** through CSS variables
126+
- **Reusable components** across different page contexts
127+
- **Clear separation of concerns** between data and presentation
128+
129+
### Vibes vs Pages Architecture
130+
131+
**`vibes/` folder**: Contains presentation components that are meant to be highly customizable and styleable to change the UI:
132+
- Accept `Streamable<T>` data as props
133+
- Handle rendering, styling, and user interactions
134+
- Support theming through CSS variables
135+
- No direct data fetching or business logic
136+
137+
**`page.tsx` files**: Where data fetching patterns should live:
138+
- Handle authentication and authorization
139+
- Create `Streamable` data sources
140+
- Transform API responses for vibes components
141+
- Manage routing and server-side logic
142+
143+
### Component Hierarchy
144+
145+
```
146+
vibes/soul/
147+
├── lib/
148+
│ └── streamable.tsx # Streamable utilities
149+
├── primitives/ # Basic UI components
150+
│ ├── button/
151+
│ ├── product-card/
152+
│ └── navigation/
153+
└── sections/ # Complex UI sections
154+
├── product-list/
155+
├── featured-product-carousel/
156+
└── footer/
157+
```
158+
159+
1. **Primitives** (`vibes/soul/primitives/`) - Basic reusable UI components like buttons, cards, forms.
160+
161+
2. **Sections** (`vibes/soul/sections/`) - Page-level components that compose primitives into complete page sections.
162+
163+
3. **Library** (`vibes/soul/lib/`) - Utility functions and patterns like the Streamable implementation.
164+
165+
### Data Flow Pattern
166+
167+
```
168+
page.tsx → Streamable data → Vibes components → User interaction
169+
```
170+
171+
**Example Pattern:**
172+
```typescript
173+
// app/[locale]/(default)/page.tsx - Data fetching
174+
export default async function HomePage({ params }: Props) {
175+
const streamableProducts = Streamable.from(async () => {
176+
const customerToken = await getSessionCustomerAccessToken();
177+
return getProducts(customerToken);
178+
});
179+
180+
return (
181+
<FeaturedProductList
182+
products={streamableProducts} // Pass streamable to vibes
183+
title="Featured Products"
184+
/>
185+
);
186+
}
187+
188+
// vibes/soul/sections/featured-product-list/index.tsx - Presentation
189+
export function FeaturedProductList({
190+
products,
191+
title
192+
}: {
193+
products: Streamable<Product[]>; // Accept streamable
194+
title: string;
195+
}) {
196+
return (
197+
<section>
198+
<h2>{title}</h2>
199+
<Stream value={products} fallback={<ProductSkeleton />}>
200+
{(productList) => (
201+
<div className="grid">
202+
{productList.map(product => <ProductCard key={product.id} product={product} />)}
203+
</div>
204+
)}
205+
</Stream>
206+
</section>
207+
);
208+
}
209+
```
210+
211+
### Import Patterns
212+
213+
Components should be imported from the vibes design system using the `@/vibes/soul/` alias, maintaining clear separation between business logic in `/components` and design system components in `/vibes`.
214+
215+
## App Router Data Fetching Patterns
216+
217+
### Server Components by Default
218+
219+
All pages are React Server Components, enabling:
220+
- Server-side data fetching with zero client JavaScript
221+
- Automatic code splitting and optimization
222+
- SEO-friendly content rendering
223+
- Direct database/API access
224+
225+
### File-based Routing Structure
226+
227+
```
228+
app/[locale]/(default)/
229+
├── page.tsx # Homepage with data fetching
230+
├── layout.tsx # Shared layout components
231+
├── product/[slug]/
232+
│ ├── page.tsx # Product detail page
233+
│ └── page-data.ts # Product data fetching logic
234+
├── (faceted)/category/[slug]/
235+
│ └── page.tsx # Category page
236+
└── cart/
237+
└── page.tsx # Cart page
238+
```
239+
240+
### Data Fetching Example
241+
242+
```typescript
243+
// page.tsx - Server Component with async data fetching
244+
export default async function ProductPage({ params, searchParams }: Props) {
245+
const { slug } = await params;
246+
const customerAccessToken = await getSessionCustomerAccessToken();
247+
248+
// Create streamables for concurrent data loading
249+
const streamableProduct = Streamable.from(async () => {
250+
return getProduct(slug, customerAccessToken);
251+
});
252+
253+
const streamableReviews = Streamable.from(async () => {
254+
const product = await streamableProduct; // Reuses cached promise
255+
return getProductReviews(product.id);
256+
});
257+
258+
return (
259+
<ProductDetail
260+
product={streamableProduct}
261+
reviews={streamableReviews}
262+
/>
263+
);
264+
}
265+
```
266+
267+
## Key Architectural Principles
268+
269+
1. **App Router Architecture**: Built on Next.js App Router with React Server Components for optimal performance
270+
2. **Routing Flexibility**: Unlike typical Next.js applications, URLs are resolved dynamically via GraphQL rather than file structure
271+
3. **Progressive Enhancement**: Static content loads immediately with dynamic content streaming via PPR and Streamable
272+
4. **Vibes Separation**: Complete separation between data fetching (`page.tsx`) and presentation (`vibes/`) concerns
273+
5. **Centralized API Access**: All BigCommerce API interactions go through the configured GraphQL client
274+
6. **Middleware-First**: Critical functionality like routing, auth, and internationalization handled at the middleware layer
275+
276+
## Notes
277+
278+
This codebase differs significantly from typical Next.js applications due to the custom routing middleware and e-commerce-specific patterns. The `with-routes` middleware essentially turns Next.js into a headless CMS router, where content structure is determined by the BigCommerce backend rather than the filesystem. Understanding this fundamental difference is crucial for working effectively with the codebase.
279+
280+
The Streamable pattern and PPR integration provide excellent user experience through progressive loading, but require understanding of React's newer concurrent features like the `use()` hook and Suspense boundaries.

0 commit comments

Comments
 (0)