Monorepo of SDKs for integrating React/JamStack frameworks (Shopify Hydrogen, React Router, Next.js) with the Weaverse Headless CMS. Provides visual page building, theme customization, and content management through a drag-and-drop editor.
| Task | Command |
|---|---|
| Install dependencies | pnpm install |
| Build all packages | pnpm run build |
| Dev with hot reload | pnpm run dev |
| Run all tests | pnpm run test |
| Run single test file | pnpm exec vp test --run path/to/file.test.ts |
| Typecheck all packages | pnpm run typecheck |
| Lint/format check | pnpm run biome |
| Lint/format fix | pnpm run biome:fix |
| Format only | pnpm run format |
turbo dev --filter=@weaverse/core
turbo build --filter=@weaverse/react
turbo test --filter=@weaverse/hydrogen- Package manager: pnpm 11.1.2 (use
pnpm install, NOT npm/bun install) - Script runner: pnpm (
pnpm run <script>from any workspace package) - Test runner: Vite+ (
vp test, exposed via thevite-pluscatalog dep) — Vitest under the hood - Build orchestrator: Turbo (
turbo.jsondefines task pipeline) - Linting/Formatting: Biome 2.5.0 (configured in
packages/biome/biome.json) - Testing: Bun's native test runner
- Pre-commit hooks: Lefthook runs
biome check --writeon staged files - CI: GitHub Actions —
biome ci .+pnpm run typecheck+pnpm run test - Releases: Git tag +
pnpm publishworkflow via.claude/skills/releasing-weaverse-sdks/skill (core, react, hydrogen stay in sync)
@weaverse/hydrogen → @weaverse/react → @weaverse/core
→ @weaverse/schema
@weaverse/react → @weaverse/core
@weaverse/core ← foundation, no internal deps
@weaverse/schema (v0.8.2) ← independent versioning (Zod-based)
@weaverse/cli ← CLI tools (JavaScript)
@weaverse/i18n ← internationalization
@weaverse/next ← Next.js integration
@weaverse/remix ← Remix integration
@weaverse/experiments ← A/B testing (independent; zero-dep engine + optional react peer)
packages/
core/ Framework-agnostic foundation (event system, types, utilities)
react/ React bindings (hooks, context, components)
hydrogen/ Shopify Hydrogen integration (WeaverseClient, components, hooks)
schema/ Zod-based schema definitions for component settings
cli/ CLI tools
biome/ Shared Biome configuration
i18n/ Internationalization utilities
next/ Next.js integration
remix/ Remix integration
experiments/ Framework-agnostic A/B testing (engine + /server + /react)
templates/
pilot/ Example Hydrogen storefront theme
archived/
shopify/ Archived, do not modify
All packages use tsup with consistent config:
- Entry:
src/index.ts - Formats: ESM + CJS (schema is ESM-only)
- Output:
dist/directory - DTS: Generated type declarations
- Sourcemaps: Enabled
- Clean: Builds clear output directory first
| Rule | Setting |
|---|---|
| Indentation | 2 spaces |
| Quotes | Single quotes (') |
| Semicolons | As needed (not mandatory) |
| Trailing commas | ES5 style |
const vs let |
Prefer let (useConst is OFF) — use const only for true constants (ALL_CAPS) |
any type |
Warned — avoid, use proper types |
| Unused imports | Warned — remove them |
| Unused variables | Warned — remove them |
forEach |
Warned — prefer for...of loops |
console.* |
Warned — use proper logging |
| Optional chaining | Required (?. instead of && chains) |
| Namespace imports | Warned — use named imports, not import * as |
camelCase— variables, functions, methodsPascalCase— React components, interfaces, type aliases, classesALL_CAPS— true constants_prefix— private class members
- Target:
esnext, JSX:react-jsx, Module resolution:node strict: falsebutstrictNullChecks: true- Use
typekeyword for type-only imports:import type { Foo } from 'bar' - Prefer interfaces over type aliases for object shapes
- No
any— useunknownwith type guards if needed
- Functional components with hooks only (no class components)
- Never call hooks conditionally
- CSS Modules for styling
- Error Boundaries for error handling
- Use
React.memo,useMemo,useCallbackfor performance-critical paths
- Arrow functions for callbacks
async/await— no.then()chains- Destructuring for objects and arrays
- Template literals for string interpolation
- Optional chaining (
?.) and nullish coalescing (??) — always
Bun's native test runner. Tests exist for hydrogen, react, schema, and i18n packages.
packages/hydrogen/__tests__/**/*.test.ts
packages/react/test/**/*.test.ts
packages/schema/test/**/*.test.ts
packages/i18n/__tests__/**/*.test.ts
Write tests exclusively to verify complex logic, secure critical paths, and prevent regressions. Discard tests written solely for line-coverage metrics.
- Ignore Boilerplate: Never test language standards, generated code, simple getters/setters, or pure pass-through methods.
- Target Complexity: Test only functions containing conditional logic (
if,switch), state changes, or data transformations. - Isolate Boundaries: Mock all external I/O, databases, and network calls. Test internal business logic natively.
- Strict AAA: Physically separate Arrange, Act, and Assert phases with blank lines. Never entangle setup with execution.
- Concrete Naming: Use
should_[ExpectedBehavior]_when_[StateUnderTest]. - High Signal-to-Noise: Enforce one logical assertion per test. Remove all visual and logical clutter.
- Fast & Independent: Tests must execute in milliseconds, share zero state, and run deterministically in any environment.
- Agentic TDD: Output the failing test first. Verify it fails. Only then, generate the implementation code.
- Prove Necessity: A test is invalid until proven to fail. If mutating the source code does not break the test, delete the test.
Always structure tests with clear Arrange → Act → Assert sections:
import { describe, it, expect } from 'vitest'
describe('WeaverseClient', () => {
it('should_resolve_projectId_from_URL_query_param', () => {
// Arrange
let request = new Request('https://example.com?projectId=url-project')
let context = createMockContext({ request })
// Act
let client = new WeaverseClient(context)
let result = client.getProjectId()
// Assert
expect(result).toBe('url-project')
})
})Tests must be:
- Fast — No network calls, minimal I/O, mock external dependencies
- Independent — Each test runs in isolation, no shared mutable state
- Repeatable — Same input → same output, every time
- Self-validating — Assert outcomes explicitly, no manual inspection
- Timely — Write tests alongside or before implementation
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
// Mock factories for consistent test setup
function createMockContext(overrides = {}) {
return {
storefront: { i18n: { language: 'EN', country: 'US' } },
env: { WEAVERSE_PROJECT_ID: 'default-project' },
cache: { put: vi.fn(), match: vi.fn() },
...overrides,
}
}
// Suppress console noise in expected error tests
beforeEach(() => {
vi.spyOn(console, 'warn').mockImplementation(() => {})
})
afterEach(() => {
vi.restoreAllMocks()
})
// Error regex constants at top for reusability
const INVALID_PROJECT_ID_ERROR = /Invalid projectId/
const TIMEOUT_ERROR = /Request timeout/Note: Use let for variable declarations in tests (consistent with biome config).
# All tests
pnpm run test
# Single package
turbo test --filter=@weaverse/hydrogen
# Single file
pnpm exec vp test --run packages/hydrogen/__tests__/weaverse-client.test.ts
# Watch mode
pnpm exec vp test packages/hydrogen/__tests__/weaverse-client.test.ts
# Coverage
pnpm exec vp test --run --coverageWhen creating Weaverse components for Hydrogen, use createSchema with settings array:
import { createSchema, type HydrogenComponentProps } from '@weaverse/hydrogen'
// Component definition
export let MyComponent = forwardRef<HTMLDivElement, HydrogenComponentProps>((props, ref) => {
let { children, ...rest } = props
return <div ref={ref} {...rest}>{children}</div>
})
// Schema — use `settings`, NOT `inspector`
export let schema = createSchema({
type: 'my-component',
title: 'My Component',
settings: [
{
type: 'text',
name: 'heading',
label: 'Heading',
defaultValue: 'Hello World',
},
],
})
export default MyComponentImportant: Always use settings in schema definitions, never inspector (deprecated).
// Named imports from packages
import { WeaverseClient, fetchWeaverseData } from '@weaverse/hydrogen'
// Type-only imports
import type { HydrogenComponentProps, WeaverseLoaderData } from '@weaverse/hydrogen'
// Relative imports within a package
import { someUtil } from './utils'
// NO namespace imports (warned by biome)
// ❌ import * as Utils from './utils'
// ✅ import { specificUtil } from './utils'try/catchfor all async operations- React Error Boundaries for component-level errors
- Log errors with context (what operation failed, relevant data)
- Never swallow errors silently
try {
let data = await fetchWeaverseData(args)
} catch (error) {
console.error('[WeaverseClient] Failed to fetch data:', { error, args })
throw error
}Runs automatically on git commit:
biome check --writeon staged files- Auto-stages any fixed files
- Commit proceeds if checks pass
biome ci .— lint/format check (strict, no auto-fix)pnpm run typecheck— TypeScript type checking
Uses a Claude Code skill (.claude/skills/releasing-weaverse-sdks/SKILL.md) for releases:
- Tell Claude which packages to release and the bump type (e.g., "release the fixed group as minor")
- The skill runs: verify → bump → build → publish to npm → tag → GitHub Release → sync dev
- Core, React, and Hydrogen versions stay in sync (fixed group)
- Schema, CLI, Biome, and i18n are versioned independently
- See the skill file for the full 13-step ritual
- Don't use
npm install/bun install— usepnpm installfor dependency management - Don't use
constby default — this project preferslet(biome useConst is OFF) - Don't use double quotes — biome enforces single quotes
- Don't use
inspectorin schemas — usesettings(inspector is deprecated) - Don't use namespace imports — use named imports (
import { x }notimport * as) - Don't modify
archived/ortemplates/— these are excluded from linting - Don't add semicolons everywhere — biome uses "as needed" mode
- Don't use
.then()chains — useasync/await