Files
design-system/docs/superpowers/specs/2026-03-18-design-system-packaging-design.md
hsiegeln 45c35b59fe docs: add design spec for design system packaging
Covers Vite library mode build, Gitea npm registry publishing,
snapshot + tag-based versioning, and consumer setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 20:07:28 +01:00

5.6 KiB

Design System Packaging — Design Spec

Date: 2026-03-18 Status: Approved Package: @cameleer/design-system Registry: Gitea npm registry at gitea.siegeln.net

Goal

Package the Cameleer3 design system as a reusable npm library so other React applications in the Cameleer ecosystem can consume it via npm install. Publishing is automated via Gitea Actions.

Decisions

Decision Choice Rationale
Registry Gitea built-in npm registry Already have Gitea infrastructure
Package scope @cameleer/design-system Matches the org
Export style Single package, flat exports Simple DX, tree-shaking handles unused code
What's included Everything (primitives, composites, layout, providers, utils, tokens) All consuming apps are Cameleer apps
Build tool Vite library mode Already using Vite, CSS Modules first-class
Output format ESM only All consumers are Vite/ESM
Versioning Tag-based releases + snapshot on every main push Snapshots for dev, tags for milestones
Runner arch ARM64 Gitea runner is ARM64
Auth secret REGISTRY_TOKEN (org-level) Existing all-access token

1. Library Entry Point

New file src/design-system/index.ts — the single public API:

export * from './primitives'
export * from './composites'
export * from './layout'
export * from './providers/ThemeProvider'
export * from './providers/CommandPaletteProvider'
export * from './providers/GlobalFilterProvider'
export * from './utils/hashColor'
export * from './utils/timePresets'

CSS (tokens.css, reset.css, all component CSS Modules) is bundled into a single dist/style.css. Consumers import it once:

import '@cameleer/design-system/style.css'

2. Vite Library Build Config

A separate vite.lib.config.ts to keep library and app builds independent:

  • Entry: src/design-system/index.ts
  • Output: dist/index.es.js (ESM)
  • CSS: Extracted to dist/style.css
  • Externals: react, react-dom, react-router-dom (peer deps, not bundled)
  • Types: vite-plugin-dts generates dist/index.d.ts with full TypeScript declarations
  • Build script: "build:lib": "vite build --config vite.lib.config.ts"

Output structure:

dist/
  index.es.js      # ESM bundle
  style.css         # All CSS (tokens + reset + component modules)
  index.d.ts        # TypeScript declarations

3. Package Configuration

Updates to package.json:

{
  "name": "@cameleer/design-system",
  "version": "0.1.0",
  "type": "module",
  "main": "./dist/index.es.js",
  "module": "./dist/index.es.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.es.js",
      "types": "./dist/index.d.ts"
    },
    "./style.css": "./dist/style.css"
  },
  "files": ["dist"],
  "sideEffects": ["*.css"],
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.0.0"
  }
}
  • "private": true is removed
  • Existing scripts, dependencies, and devDependencies remain for the app build
  • peerDependencies tells consumers what to provide

4. Gitea Actions CI/CD Pipeline

Workflow at .gitea/workflows/publish.yml:

Triggers:

  • Push to main → publish snapshot (0.0.0-snapshot.<YYYYMMDD>.<short-sha>) with dev dist-tag
  • Push tag v* → publish stable release (e.g., 1.0.0) with latest dist-tag

Steps:

  1. Checkout at ref
  2. npm ci (install deps)
  3. npx vitest run (gate: don't publish broken code)
  4. npm run build:lib (build the library)
  5. Determine version from tag or generate snapshot version
  6. Configure .npmrc with REGISTRY_TOKEN
  7. npm publish --tag <dev|latest>

Runner: ARM64 with node:22-bookworm-slim container image.

name: Build & Publish

on:
  push:
    branches: [main]
    tags: ['v*']

jobs:
  publish:
    runs-on: linux-arm64
    container:
      image: node:22-bookworm-slim
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx vitest run
      - run: npm run build:lib
      - run: |
          if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
            VERSION="${GITHUB_REF_NAME#v}"
            npm version "$VERSION" --no-git-tag-version
            TAG="latest"
          else
            SHORT_SHA=$(echo "$GITHUB_SHA" | head -c 7)
            DATE=$(date +%Y%m%d)
            npm version "0.0.0-snapshot.${DATE}.${SHORT_SHA}" --no-git-tag-version
            TAG="dev"
          fi
          echo "//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${{ secrets.REGISTRY_TOKEN }}" > .npmrc
          npm publish --tag "$TAG"

5. Consumer Setup

In any consuming app (e.g., cameleer3-server/ui):

1. Add .npmrc to project root:

@cameleer:registry=https://gitea.siegeln.net/api/packages/cameleer/npm/
//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${GITEA_TOKEN}

2. Install:

# During development (snapshot builds)
npm install @cameleer/design-system@dev

# For stable releases (later)
npm install @cameleer/design-system

3. Use:

// Import styles once at app root
import '@cameleer/design-system/style.css'

// Import components
import { Button, Input, Modal, AppShell, ThemeProvider } from '@cameleer/design-system'

6. Documentation Updates

Update CLAUDE.md and COMPONENT_GUIDE.md in this repo with:

  • The package name and registry URL
  • How consuming apps should configure .npmrc
  • Import patterns for consumers (@cameleer/design-system instead of relative paths)
  • Note that style.css must be imported once at the app root

This ensures other AI agents working on consuming Cameleer apps understand how to use the design system.