Skip to main content

sync-shadcn

Sync upstream shadcn/ui updates into our forked primitives in packages/storefront-ui/src/components/ui/ via a 3-way merge that preserves our customizations (relative imports, rounded-ui/shadow-ui/border-ui shape tokens, data-slot attributes, added props), AND apply our house-style shape tokens to ANY component via `restyle`. Use when onboarding a brand-new shadcn primitive in one house-style-correct step — fork + baseline + shape tokens + unified `radix-ui`→individual `@radix-ui/react-*` imports (`add`); when shadcn ships new variants, accessibility fixes, or dependency bumps and you want to pull them into our forks without clobbering local changes (`sync`); when applying/normalizing our shape tokens on a new or existing component (`restyle`); when checking which primitives have drifted behind upstream (`status`) or off our house style (`infer`); or to see exactly what we customized for a component (`diff`). Repo-agnostic: resolves the ui dir from components.json, so it runs in a flattened customer/mirror rep

来源信息

仓库
SalesforceCommerceCloud/storefront-next-template
最近来源活动
2026年8月26日 14:10
检测到的 SKILL.md 语言
英语
星标
55
分支
16

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
4 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
sync-shadcn
description
Sync upstream shadcn/ui updates into our forked primitives in packages/storefront-ui/src/components/ui/ via a 3-way merge that preserves our customizations (relative imports, rounded-ui/shadow-ui/border-ui shape tokens, data-slot attributes, added props), AND apply our house-style shape tokens to ANY component via `restyle`. Use when onboarding a brand-new shadcn primitive in one house-style-correct step — fork + baseline + shape tokens + unified `radix-ui`→individual `@radix-ui/react-*` imports (`add`); when shadcn ships new variants, accessibility fixes, or dependency bumps and you want to pull them into our forks without clobbering local changes (`sync`); when applying/normalizing our shape tokens on a new or existing component (`restyle`); when checking which primitives have drifted behind upstream (`status`) or off our house style (`infer`); or to see exactly what we customized for a component (`diff`). Repo-agnostic: resolves the ui dir from components.json, so it runs in a flattened customer/mirror repo, not just the monorepo. SKIP when: editing fashion/cosmetic primitives directly (those derive from packages/template via the mvt-* mirror skills — restyle the storefront-ui source instead); making a one-off manual tweak to a single primitive.
# sync-shadcn — 3-way merge for upstream shadcn updates Our `src/components/ui/` primitives are **forks** of shadcn/ui (copy-paste, not an npm package), edited in place with our customizations. This skill pulls upstream updates in without losing those edits, using a 3-way merge: | input | what it is | where it lives | |--------|------------|----------------| | **base** | pristine upstream we last synced from | `packages/<pkg>/.shadcn-baseline/<name>.tsx` | | **theirs** | current upstream, freshly fetched | shadcn registry (`new-york-v4`) | | **ours** | our customized fork | `packages/<pkg>/src/components/ui/<name>.tsx` | `git merge-file` replays the **base→theirs** delta (what upstream changed) onto **ours**. Our customizations are the **ours↔base** delta, so they survive automatically; a conflict appears **only** where upstream changed a line we also customized. > The `.shadcn-baseline/` snapshots are not imported by anything — they exist solely as the > merge's reference point. They contain vanilla `@/` imports and no copyright header, so the > root ESLint config ignores `**/.shadcn-baseline/**` and `tsconfig` (`include: src/**`) never > compiles them. ## Two composable mechanisms The skill is **customization-as-merge** plus **customization-as-transform**: | | mechanism | what it handles | needs a baseline? | |---|---|---|---| | **`sync`** | 3-way `git merge-file` | STRUCTURAL deltas: added props, `data-slot`, behavior, variants | yes (the `.shadcn-baseline/` anchor) | | **`restyle`** | declarative ruleset (`ruleset.json`) | MECHANICAL shape tokens: `rounded-*`→`rounded-ui`, `shadow-*`→`shadow-ui`, `border`→`border-ui` (Card), import convention, unified `radix-ui`→individual `@radix-ui/react-*` | no — works on any file | They compose: `sync` brings upstream structure in, then `restyle` normalizes any raw shape tokens the merge reintroduced. A brand-new component (no baseline) gets its house style from `restyle` alone. ## When to run - shadcn announced a release, or you want to check for drift → `status`. - A primitive needs an upstream a11y/variant fix → `sync <name>`. - Onboarding a NEW shadcn primitive → `add <name>` (one step: fork + baseline + house style). - Want to confirm forks haven't drifted off our house style → `restyle --all --check` / `infer --all`. - Periodically (e.g. quarterly) to keep forks from drifting far. Default package is `storefront-ui` (the canonical fork off upstream; the only package with `.shadcn-baseline/` snapshots). For any other layout — including a **flattened customer/mirror repo** — the skill discovers the ui dir from the nearest `components.json` (walk up from `--path` or cwd), so no `--package` is needed. Fashion/cosmetic primitives are **out of scope** for direct edits — they mirror `packages/template` (which inlines storefront-ui), so restyle the `storefront-ui` source, not the mirror output. ## Commands ```bash S=.claude/skills/sync-shadcn/sync.mjs node $S add slider # ONBOARD a new primitive: fork + baseline + house style, one step node $S status # behind / up-to-date / no-baseline for every primitive node $S sync button # 3-way merge one component (+ auto-restyle on a clean merge) node $S sync button dialog card # several node $S sync --all # every primitive with a baseline node $S diff button # show our customizations (baseline -> fork diff) node $S advance button # promote baseline -> current upstream (after a clean merge) node $S restyle button # apply the ruleset to a fork (idempotent) node $S restyle --all # every fork in the resolved ui dir node $S restyle --path src/components/ui/toggle.tsx # a NEW component / any repo layout node $S restyle --all --check # dry-run conformance gate (exit 1 if anything would change) node $S infer --all # report drift: raw shape tokens not yet -ui (exit 1 if any) node $S infer card --emit # print a ruleset inferred from baseline<->fork diffs node $S sync --all --bootstrap # ONE-TIME: seed every baseline from current upstream ``` ## Restyle — the house-style ruleset `ruleset.json` (beside this skill) is the declarative source of truth — token families, exclude list, the Card-only `border` scope, and the import convention. `restyle` rewrites **only** className / `cn()` / `cva()` string literals, matching tokens by exact variant-stripped core: - `rounded-md`/`-lg`/`-xl`/… → `rounded-ui`; `shadow-sm`/`-xs`/… → `shadow-ui`. - `border` → `border-ui` **only on Card** (`families.border.scope.only`). Every other primitive keeps literal `border` — border-ui is Card-only (see `docs/README-SHAPE-TOKENS.md`). - **Preserved** (never members): `rounded-full`, `rounded-none`, directional/arbitrary radii (`rounded-t-lg`, `rounded-[2px]`), `border-2`, `border-input`, `shadow-none`. - **Import convention** auto-adapts: where the `@/` alias resolves via tsconfig `paths` (the customer/mirror convention), `@/` imports are KEPT; where it does not (storefront-ui bans `@/` under `ui/`), they are relativized (`@/lib/utils` → `../../lib/utils`). - **Radix unbundle** (`imports.unbundle`): upstream's unified `import { Dialog as DialogPrimitive } from "radix-ui"` → the individual `import * as DialogPrimitive from "@radix-ui/react-dialog"` our forks use (package derived from the export name, so `Dialog as SheetPrimitive` → `react-dialog` with the alias kept). `Slot` is an `exceptions` entry: it becomes a named import from `@radix-ui/react-slot` and its `Slot.Root` usage collapses to bare `Slot`. Matches only the exact bare `radix-ui` specifier, so it is a **no-op** on already-individual forks. `restyle` is **idempotent** — the replacements are never members, so running twice is a no-op. ### New component flow One command onboards a new primitive — it fetches from the correct `new-york-v4` path (**not** the stale `new-york` that `npx shadcn add` uses), writes a house-styled fork (shape tokens + radix unbundle + import convention), and seeds the baseline from the same raw upstream in one step: ```bash cd packages/storefront-ui node $S add <name> # fork + baseline + house style, one step pnpm lint && pnpm typecheck # ESLint enforces the no-@/ import rule ``` Because the fork and its baseline come from a single fetch, `status`/`diff` immediately show only our customizations — no phantom drift on the first sync. `add` refuses if the fork already exists (use `sync` to update it, or `add <name> --force` to overwrite). The baseline stays the pristine raw upstream (unified `radix-ui`, `@/` imports) — it is the 3-way merge anchor. ### Customer brand layer (dogfood path) A generated customer project gets this skill in `.claude/skills/sync-shadcn/`. Customers tailor it without editing `ruleset.json`: drop a `ruleset.customer.json` beside it (copy `ruleset.customer.json.example`). It deep-merges OVER ours — precedence **upstream → our ruleset → customer overlay**. Semantics are additive: `members` UNION (with explicit `removeMembers` to subtract — so you can't accidentally un-protect `rounded-full`, which was never a member), `replacement` overrides, `scope.only` and `relativizeAliases` UNION (with `removeAliases`). ## Workflow 1. **Check drift.** `node $S status` → lists which primitives are `BEHIND`. 2. **Sync.** `node $S sync <name>` for each behind component. The merged result (with any conflict markers) is written in place into the fork file. On a **clean** merge, `sync` auto-runs `restyle` to normalize any raw shape tokens the merge reintroduced (reported as `merged clean (restyled N token(s))`). The command reports per component: `up-to-date` / `merged clean` / `MERGED WITH CONFLICTS (N hunks)`, plus any anomalies. 3. **Resolve conflicts** (only if reported — see below). Conflicted files are **not** auto-restyled during `sync` (the engine won't tokenize conflict-marker lines). Once you've removed the markers, `advance` (step 5) normalizes any raw shape tokens you kept before promoting the baseline — or run `node $S restyle <name>` yourself to see the changes first. 4. **Verify** (always): ```bash cd packages/storefront-ui && pnpm lint && pnpm typecheck ``` `pnpm lint` is the safety net: storefront-ui's ESLint **errors** on any `@/*` import in `src/components/ui/**`, so an alias import pulled in from upstream fails here, not in prod. If your change altered rendered DOM, also run the consuming package's snapshot tests (`cd packages/template && pnpm storybook:test --type=snapshot`). 5. **Advance the baseline.** Once the fork is clean and verified: ```bash node $S advance <name> ``` This promotes `base` → current upstream so the next sync is conflict-free for those lines. `advance` **refuses** while any conflict marker remains — the baseline never advances from a half-merged tree. It also runs `restyle` on the (marker-free) fork first, normalizing any raw shape tokens a manual conflict resolution kept — idempotent, a no-op after a clean merge. 6. **Commit** the merged fork file(s) **and** the updated `.shadcn-baseline/` together. ## Conflicts — what to expect A conflict means upstream changed a line you also customized. With `--zdiff3` you see all three versions, so you can tell what each side changed: ```tsx <<<<<<< ours (our fork) outline: "border bg-background shadow-ui hover:bg-accent ...", ||||||| base (last synced upstream) outline: "border bg-background shadow-xs hover:bg-accent ...", ======= outline: "border bg-background shadow-xs hover:bg-accent ... dark:bg-input/30", >>>>>>> theirs (current upstream) ``` Read `base` → `ours` (we changed `shadow-xs`→`shadow-ui`) and `base` → `theirs` (upstream added `dark:bg-input/30`). Usually the resolution is the **union**: keep our token, take their addition. Delete the four marker lines, leaving the resolved line. Then re-run verify + `advance`. Common customizations you'll be protecting: `rounded-md`→`rounded-ui`, `shadow-xs`→`shadow-ui`, the `border-ui` utility, `data-slot`/`data-variant`/`data-size` attributes, relative `../../lib/utils` imports, and added props (e.g. dialog `showCloseButton`, card `CardAction`). ## Anomalies the skill reports (does not auto-merge) | report | meaning | action | |--------|---------|--------| | `[new-dependency]` | upstream declares a dep we don't have (e.g. unified `radix-ui`) | expected — the unbundle rule rewrites it to individual `@radix-ui/*`; review only if it's a non-radix dep | | `[dep-missing]` | the fork imports a package (or an upstream dep) that isn't in `package.json` | install it at a version consistent with the other forks, then re-run `add`/`advance` | | `[multi-file]` | upstream split the component into several files | merge the primary `<name>.tsx`; handle extra files manually | | `[renamed]` | upstream's file basename ≠ `<name>.tsx` | likely rename/split; inspect before trusting the merge | | `[missing-fork]` | exists upstream, not in our set | `add <name>` — onboards fork + baseline + house style in one step | | `NOT FOUND (404)` | not published upstream (e.g. our custom `native-select`) | skipped; nothing to sync | ## Manifest fields (`.shadcn-baseline/manifest.json`) Each component entry records: - `contentSha256` — sha of the pristine upstream baseline (the change-detection key). - `dependencies` — raw upstream dep **names** (e.g. `["radix-ui"]`), unchanged. - `resolvedDependencies` — the fork's actual packages → **installed versions** (e.g. `{ "@radix-ui/react-slot": "1.2.3" }`), derived from the fork's post-unbundle imports and `package.json`. Written by `add`/`advance`/`sync --bootstrap`; pins what our fork ships. - `syncedAt` — last **promotion** (seed / advance / add). - `checkedAt` — last **drift-check** (also stamped by `sync`'s up-to-date path). `status` is read-only and does not write it. ## Notes - No npm dependencies; uses Node global `fetch` + `node:crypto` + system `git`. - The registry URL uses the **`-v4`** style suffix. The un-suffixed `new-york` path serves a stale, pre-`data-slot` snapshot older than our fork — `sync.mjs` resolves the suffix from `components.json` automatically; don't hardcode the un-versioned path. - `storefront-ui` is a private package → no changeset needed for changes confined to it. - This skill ships into generated customer projects: the customer-skills allowlist in `scripts/lib/ship-skills.mjs` copies it into the mirror artifact (`packages/template`'s `mirror.mjs`) and the standalone template (`scripts/generate-storefront.js`). Tests and fixtures are excluded from the shipped copy.
在 GitHub 查看