Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,6 @@ jobs:

- name: Check for Zag package and versions
run: bun scripts check:zag

- name: Check for anatomy exports
run: bun scripts check:anatomy
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"scripts": {
"postinstall": "lefthook install",
"build": "turbo run build --filter='!./templates/**' --filter='!./website/**' --filter='!./integrations/**'",
"check:anatomy": "bun scripts check:anatomy",
"check:exports": "bun scripts check:exports",
"check:zag": "bun scripts check:zag",
"clean": "find . -name 'node_modules' -type d -exec rm -rf {} +",
Expand Down
4 changes: 4 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## [Unreleased]

### Fixed

- **AngleSlider**: Export `angleSliderAnatomy` from the anatomy exports

## [5.24.1] - 2025-09-14

### Fixed
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/anatomy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from '@zag-js/anatomy'
export { accordionAnatomy } from './accordion/accordion.anatomy'
export { angleSliderAnatomy } from './angle-slider/angle-slider.anatomy'
export { avatarAnatomy } from './avatar/avatar.anatomy'
export { carouselAnatomy } from './carousel/carousel.anatomy'
export { checkboxAnatomy } from './checkbox/checkbox.anatomy'
Expand Down
4 changes: 4 additions & 0 deletions packages/solid/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## [Unreleased]

### Fixed

- **AngleSlider**: Export `angleSliderAnatomy` from the anatomy exports

## [5.24.1] - 2025-09-14

### Fixed
Expand Down
1 change: 1 addition & 0 deletions packages/solid/src/components/anatomy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from '@zag-js/anatomy'
export { accordionAnatomy } from './accordion/accordion.anatomy'
export { angleSliderAnatomy } from './angle-slider/angle-slider.anatomy'
export { avatarAnatomy } from './avatar/avatar.anatomy'
export { carouselAnatomy } from './carousel/carousel.anatomy'
export { checkboxAnatomy } from './checkbox/checkbox.anatomy'
Expand Down
1 change: 1 addition & 0 deletions scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "module",
"private": true,
"scripts": {
"check:anatomy": "bun run src/check-anatomy.ts",
"check:exports": "bun run src/check-exports.ts",
"check:nodes": "bun run src/check-nodes.ts",
"check:zag": "bun run src/check-zag-versions.ts",
Expand Down
115 changes: 115 additions & 0 deletions scripts/src/check-anatomy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { existsSync } from 'node:fs'
import { join, basename, dirname } from 'node:path'
import { glob } from 'glob'

const packages = ['react', 'solid', 'svelte', 'vue']

interface AnatomyCheck {
package: string
anatomyFiles: string[]
exportedAnatomies: string[]
missing: string[]
extra: string[]
}

function getComponentName(filePath: string): string {
return basename(dirname(filePath))
}

async function getExportedAnatomies(anatomyTsPath: string): Promise<string[]> {
if (!existsSync(anatomyTsPath)) {
return []
}

const content = await Bun.file(anatomyTsPath).text()
const exportRegex = /export\s+{\s*(\w+Anatomy)\s*}\s+from\s+['"]\.\/([\w-]+)\//g
const anatomies: string[] = []

let match: RegExpExecArray | null
// biome-ignore lint/suspicious/noAssignInExpressions: works
while ((match = exportRegex.exec(content)) !== null) {
anatomies.push(match[2])
}

return anatomies.sort()
}

async function checkPackage(packageName: string): Promise<AnatomyCheck> {
// Find the root directory (parent of scripts or packages)
const rootDir = process.cwd().endsWith('/scripts') ? join(process.cwd(), '..') : process.cwd()

const basePath =
packageName === 'svelte'
? join(rootDir, 'packages', packageName, 'src', 'lib', 'components')
: join(rootDir, 'packages', packageName, 'src', 'components')

const anatomyTsPath = join(basePath, 'anatomy.ts')

// Find all anatomy files
const anatomyFiles = await glob('*/*.anatomy.{ts,tsx}', {
cwd: basePath,
absolute: false,
})

const componentNames = anatomyFiles.map((file) => getComponentName(file)).sort()

// Get exported anatomies
const exportedAnatomies = await getExportedAnatomies(anatomyTsPath)

// Find missing and extra exports
const missing = componentNames.filter((comp) => !exportedAnatomies.includes(comp))
const extra = exportedAnatomies.filter((exp) => !componentNames.includes(exp))

return {
package: packageName,
anatomyFiles: componentNames,
exportedAnatomies,
missing,
extra,
}
}

async function main() {
console.log('🔍 Checking anatomy exports across all packages...\n')

let hasIssues = false

for (const pkg of packages) {
const result = await checkPackage(pkg)

console.log(`📦 ${pkg.toUpperCase()}`)
console.log(` Total anatomy files: ${result.anatomyFiles.length}`)
console.log(` Exported in anatomy.ts: ${result.exportedAnatomies.length}`)

if (result.missing.length > 0) {
hasIssues = true
console.log(` ❌ Missing exports:`)
result.missing.forEach((comp) => {
console.log(` - ${comp}`)
})
}

if (result.extra.length > 0) {
hasIssues = true
console.log(` ⚠️ Extra exports (no anatomy file):`)
result.extra.forEach((comp) => {
console.log(` - ${comp}`)
})
}

if (result.missing.length === 0 && result.extra.length === 0) {
console.log(` ✅ All anatomy files are properly exported`)
}

console.log()
}

if (hasIssues) {
console.log('❌ Some packages have anatomy export issues!')
process.exit(1)
} else {
console.log('✅ All packages have complete anatomy exports!')
}
}

main().catch(console.error)