feat: set up automated npm publishing for @soroban/abacus-react package

- Add semantic-release configuration for abacus-react package with scope-based versioning
- Create GitHub Actions workflow for automated publishing to npm
- Configure package-specific semantic versioning with conventional commits
- Add release scripts and update README with publishing documentation
- Update root release config to exclude abacus-react scope from monorepo releases
- Package releases are triggered by commits with scope 'abacus-react'

BREAKING CHANGE: abacus-react package now has independent versioning from monorepo
This commit is contained in:
Thomas Hallock 2025-09-28 08:38:37 -05:00
parent 9d7cfefb69
commit dd80d29c97
11 changed files with 353 additions and 85 deletions

View File

@ -148,7 +148,8 @@
"Bash(awk:*)",
"Bash(gh release list:*)",
"Bash(gh release view:*)",
"Bash(git pull:*)"
"Bash(git pull:*)",
"WebFetch(domain:antialias.github.io)"
],
"deny": [],
"ask": []

View File

@ -0,0 +1,67 @@
name: Publish @soroban/abacus-react
on:
push:
branches:
- main
paths:
- 'packages/abacus-react/**'
- '.github/workflows/publish-abacus-react.yml'
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
jobs:
publish:
name: Publish abacus-react package
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.0.0
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build abacus-react package
run: pnpm --filter @soroban/abacus-react build
- name: Run tests
run: pnpm --filter @soroban/abacus-react test:run
- name: Semantic Release
working-directory: packages/abacus-react
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release

View File

@ -1,8 +1,37 @@
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "type": "feat", "scope": "!abacus-react", "release": "minor" },
{ "type": "fix", "scope": "!abacus-react", "release": "patch" },
{ "type": "perf", "scope": "!abacus-react", "release": "patch" },
{ "type": "refactor", "scope": "!abacus-react", "release": "patch" },
{ "breaking": true, "scope": "!abacus-react", "release": "major" },
{ "scope": "abacus-react", "release": false }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance Improvements" },
{ "type": "refactor", "section": "Code Refactoring" },
{ "type": "docs", "section": "Documentation" },
{ "type": "style", "section": "Styles" },
{ "type": "test", "section": "Tests" }
]
}
}
],
[
"@semantic-release/changelog",
{

View File

@ -36,11 +36,11 @@ export function MemoryGrid() {
return (
<div className={css({
padding: '20px',
padding: { base: '12px', sm: '16px', md: '20px' },
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '20px'
gap: { base: '12px', sm: '16px', md: '20px' }
})}>
{/* Game Info Header */}
@ -50,37 +50,43 @@ export function MemoryGrid() {
alignItems: 'center',
width: '100%',
maxWidth: '800px',
padding: '16px 24px',
padding: { base: '12px 16px', sm: '14px 20px', md: '16px 24px' },
background: 'linear-gradient(135deg, rgba(255,255,255,0.9), rgba(248,250,252,0.9))',
borderRadius: '16px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
border: '1px solid rgba(255,255,255,0.8)'
})}>
<div className={css({ display: 'flex', alignItems: 'center', gap: '20px' })}>
<div className={css({
display: 'grid',
gridTemplateColumns: { base: 'repeat(3, 1fr)', sm: 'repeat(3, auto)' },
gap: { base: '8px', sm: '12px', md: '20px' },
justifyContent: { base: 'stretch', sm: 'center' },
width: { base: '100%', sm: 'auto' }
})}>
<div className={css({ textAlign: 'center' })}>
<div className={css({ fontSize: '24px', fontWeight: 'bold', color: 'blue.600' })}>
<div className={css({ fontSize: { base: '18px', sm: '20px', md: '24px' }, fontWeight: 'bold', color: 'blue.600' })}>
{state.matchedPairs}
</div>
<div className={css({ fontSize: '12px', color: 'gray.600' })}>
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, color: 'gray.600' })}>
Matched
</div>
</div>
<div className={css({ textAlign: 'center' })}>
<div className={css({ fontSize: '24px', fontWeight: 'bold', color: 'purple.600' })}>
<div className={css({ fontSize: { base: '18px', sm: '20px', md: '24px' }, fontWeight: 'bold', color: 'purple.600' })}>
{state.moves}
</div>
<div className={css({ fontSize: '12px', color: 'gray.600' })}>
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, color: 'gray.600' })}>
Moves
</div>
</div>
<div className={css({ textAlign: 'center' })}>
<div className={css({ fontSize: '24px', fontWeight: 'bold', color: 'green.600' })}>
<div className={css({ fontSize: { base: '18px', sm: '20px', md: '24px' }, fontWeight: 'bold', color: 'green.600' })}>
{state.totalPairs}
</div>
<div className={css({ fontSize: '12px', color: 'gray.600' })}>
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, color: 'gray.600' })}>
Total Pairs
</div>
</div>

View File

@ -26,32 +26,36 @@ export function MemoryPairsGame() {
ref={gameRef}
className={css({
minHeight: '100vh',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
padding: '20px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative'
})}>
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
padding: { base: '12px', sm: '16px', md: '20px' },
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative'
})}>
{/* Note: Fullscreen restore prompt removed - client-side navigation preserves fullscreen */}
<header className={css({
textAlign: 'center',
marginBottom: '30px'
marginBottom: { base: '16px', sm: '20px', md: '30px' },
px: { base: '4', md: '0' }
})}>
<h1 className={css({
fontSize: '48px',
fontSize: { base: '24px', sm: '32px', md: '40px', lg: '48px' },
fontWeight: 'bold',
color: 'white',
textShadow: '2px 2px 4px rgba(0,0,0,0.3)',
marginBottom: '10px'
marginBottom: { base: '6px', md: '10px' },
lineHeight: { base: '1.2', md: '1.1' }
})}>
Memory Pairs Challenge
</h1>
<p className={css({
fontSize: '18px',
fontSize: { base: '14px', sm: '16px', md: '18px' },
color: 'rgba(255,255,255,0.9)',
maxWidth: '600px'
maxWidth: '600px',
lineHeight: { base: '1.4', md: '1.3' },
display: { base: 'none', sm: 'block' }
})}>
Match pairs of abacus representations with their numerical values, or find complement pairs that add up to 5 or 10!
</p>
@ -61,10 +65,13 @@ export function MemoryPairsGame() {
width: '100%',
maxWidth: '1200px',
background: 'rgba(255,255,255,0.95)',
borderRadius: '20px',
padding: '40px',
borderRadius: { base: '12px', md: '20px' },
padding: { base: '16px', sm: '24px', md: '32px', lg: '40px' },
boxShadow: '0 10px 30px rgba(0,0,0,0.2)',
minHeight: '500px'
minHeight: { base: '60vh', md: '500px' },
flex: 1,
display: 'flex',
flexDirection: 'column'
})}>
{state.gamePhase === 'setup' && <SetupPhase />}
{state.gamePhase === 'playing' && <GamePhase />}

View File

@ -47,13 +47,13 @@ export function SetupPhase() {
const getButtonStyles = (isSelected: boolean, variant: 'primary' | 'secondary' | 'difficulty' = 'primary') => {
const baseStyles = {
border: 'none',
borderRadius: '16px',
padding: '16px 24px',
fontSize: '16px',
borderRadius: { base: '12px', md: '16px' },
padding: { base: '12px 16px', sm: '14px 20px', md: '16px 24px' },
fontSize: { base: '14px', sm: '15px', md: '16px' },
fontWeight: 'bold',
cursor: 'pointer',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
minWidth: '160px',
minWidth: { base: '120px', sm: '140px', md: '160px' },
textAlign: 'center' as const,
position: 'relative' as const,
overflow: 'hidden' as const,
@ -130,13 +130,16 @@ export function SetupPhase() {
return (
<div className={css({
textAlign: 'center',
padding: '40px 20px',
padding: { base: '20px 16px', sm: '24px 20px', md: '40px 20px' },
maxWidth: '800px',
margin: '0 auto'
margin: '0 auto',
height: '100%',
display: 'flex',
flexDirection: 'column'
})}>
<h2 className={css({
fontSize: '36px',
marginBottom: '16px',
fontSize: { base: '20px', sm: '24px', md: '32px', lg: '36px' },
marginBottom: { base: '12px', md: '16px' },
color: 'gray.800',
fontWeight: 'bold'
})}>
@ -144,46 +147,48 @@ export function SetupPhase() {
</h2>
<p className={css({
fontSize: '18px',
fontSize: { base: '14px', sm: '16px', md: '18px' },
color: 'gray.600',
marginBottom: '40px',
lineHeight: '1.6'
marginBottom: { base: '24px', sm: '32px', md: '40px' },
lineHeight: '1.6',
display: { base: 'none', sm: 'block' }
})}>
Configure your memory challenge. Choose your preferred mode, game type, and difficulty level.
</p>
<div className={css({
display: 'grid',
gap: '32px',
margin: '0 auto'
gap: { base: '20px', sm: '24px', md: '32px' },
margin: '0 auto',
flex: 1
})}>
{/* Current Player Setup */}
<div className={css({
background: 'linear-gradient(135deg, #f3f4f6, #e5e7eb)',
rounded: '2xl',
p: '6',
rounded: { base: 'xl', md: '2xl' },
p: { base: '4', md: '6' },
border: '2px solid',
borderColor: 'gray.300'
})}>
<h3 className={css({
fontSize: '20px',
fontSize: { base: '16px', sm: '18px', md: '20px' },
fontWeight: 'bold',
color: 'gray.700',
mb: '3',
mb: { base: '2', md: '3' },
textAlign: 'center'
})}>
🎮 Current Setup
</h3>
<div className={css({
fontSize: '16px',
fontSize: { base: '14px', md: '16px' },
color: 'gray.700',
textAlign: 'center'
})}>
<p>
<strong>{activePlayerCount}</strong> player{activePlayerCount !== 1 ? 's' : ''} selected
</p>
<p className={css({ fontSize: '14px', color: 'gray.600', mt: '1' })}>
<p className={css({ fontSize: { base: '12px', md: '14px' }, color: 'gray.600', mt: '1' })}>
{activePlayerCount === 1
? 'Solo challenge mode - focus & memory'
: `${activePlayerCount}-player battle mode - compete for the most pairs`
@ -211,31 +216,34 @@ export function SetupPhase() {
<div>
<label className={css({
display: 'block',
fontSize: '20px',
fontSize: { base: '16px', sm: '18px', md: '20px' },
fontWeight: 'bold',
marginBottom: '16px',
marginBottom: { base: '12px', md: '16px' },
color: 'gray.700'
})}>
Game Type
</label>
<div className={css({
display: 'flex',
gap: '12px',
justifyContent: 'center',
flexWrap: 'wrap'
display: 'grid',
gridTemplateColumns: {
base: '1fr',
sm: 'repeat(2, 1fr)'
},
gap: { base: '8px', sm: '10px', md: '12px' },
justifyItems: 'stretch'
})}>
<button
className={getButtonStyles(state.gameType === 'abacus-numeral', 'secondary')}
onClick={() => setGameType('abacus-numeral')}
>
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px' })}>
<div className={css({ fontSize: '28px', display: 'flex', alignItems: 'center', gap: '8px' })}>
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: { base: '4px', md: '6px' } })}>
<div className={css({ fontSize: { base: '20px', sm: '24px', md: '28px' }, display: 'flex', alignItems: 'center', gap: { base: '4px', md: '8px' } })}>
<span>🧮</span>
<span className={css({ fontSize: '20px' })}></span>
<span className={css({ fontSize: { base: '16px', md: '20px' } })}></span>
<span>🔢</span>
</div>
<div className={css({ fontWeight: 'bold' })}>Abacus-Numeral</div>
<div className={css({ fontSize: '12px', opacity: 0.8, textAlign: 'center' })}>
<div className={css({ fontWeight: 'bold', fontSize: { base: '12px', sm: '13px', md: '14px' } })}>Abacus-Numeral</div>
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, opacity: 0.8, textAlign: 'center', display: { base: 'none', sm: 'block' } })}>
Match visual patterns<br/>with numbers
</div>
</div>
@ -244,23 +252,25 @@ export function SetupPhase() {
className={getButtonStyles(state.gameType === 'complement-pairs', 'secondary')}
onClick={() => setGameType('complement-pairs')}
>
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '6px' })}>
<div className={css({ fontSize: '28px', display: 'flex', alignItems: 'center', gap: '8px' })}>
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: { base: '4px', md: '6px' } })}>
<div className={css({ fontSize: { base: '20px', sm: '24px', md: '28px' }, display: 'flex', alignItems: 'center', gap: { base: '4px', md: '8px' } })}>
<span>🤝</span>
<span className={css({ fontSize: '20px' })}></span>
<span className={css({ fontSize: { base: '16px', md: '20px' } })}></span>
<span>🔟</span>
</div>
<div className={css({ fontWeight: 'bold' })}>Complement Pairs</div>
<div className={css({ fontSize: '12px', opacity: 0.8, textAlign: 'center' })}>
<div className={css({ fontWeight: 'bold', fontSize: { base: '12px', sm: '13px', md: '14px' } })}>Complement Pairs</div>
<div className={css({ fontSize: { base: '10px', sm: '11px', md: '12px' }, opacity: 0.8, textAlign: 'center', display: { base: 'none', sm: 'block' } })}>
Find number friends<br/>that add to 5 or 10
</div>
</div>
</button>
</div>
<p className={css({
fontSize: '14px',
fontSize: { base: '12px', md: '14px' },
color: 'gray.500',
marginTop: '8px'
marginTop: { base: '6px', md: '8px' },
textAlign: 'center',
display: { base: 'none', sm: 'block' }
})}>
{state.gameType === 'abacus-numeral'
? 'Match abacus representations with their numerical values'
@ -273,18 +283,21 @@ export function SetupPhase() {
<div>
<label className={css({
display: 'block',
fontSize: '20px',
fontSize: { base: '16px', sm: '18px', md: '20px' },
fontWeight: 'bold',
marginBottom: '16px',
marginBottom: { base: '12px', md: '16px' },
color: 'gray.700'
})}>
Difficulty ({state.difficulty} pairs)
</label>
<div className={css({
display: 'flex',
gap: '12px',
justifyContent: 'center',
flexWrap: 'wrap'
display: 'grid',
gridTemplateColumns: {
base: 'repeat(2, 1fr)',
sm: 'repeat(4, 1fr)'
},
gap: { base: '8px', sm: '10px', md: '12px' },
justifyItems: 'stretch'
})}>
{([6, 8, 12, 15] as const).map(difficulty => {
const difficultyInfo = {
@ -377,15 +390,15 @@ export function SetupPhase() {
)}
{/* Start Game Button */}
<div className={css({ marginTop: '20px' })}>
<div className={css({ marginTop: { base: '16px', md: '20px' } })}>
<button
className={css({
background: 'linear-gradient(135deg, #ff6b6b 0%, #ee5a24 50%, #ff9ff3 100%)',
color: 'white',
border: 'none',
borderRadius: '60px',
padding: '20px 60px',
fontSize: '28px',
borderRadius: { base: '20px', sm: '30px', md: '60px' },
padding: { base: '16px 32px', sm: '18px 40px', md: '20px 60px' },
fontSize: { base: '18px', sm: '22px', md: '28px' },
fontWeight: 'black',
cursor: 'pointer',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
@ -393,6 +406,7 @@ export function SetupPhase() {
textShadow: '0 2px 4px rgba(0,0,0,0.3)',
position: 'relative',
overflow: 'hidden',
width: { base: '100%', sm: 'auto' },
_before: {
content: '""',
position: 'absolute',
@ -404,7 +418,7 @@ export function SetupPhase() {
transition: 'left 0.6s ease',
},
_hover: {
transform: 'translateY(-5px) scale(1.05)',
transform: { base: 'translateY(-2px)', md: 'translateY(-5px) scale(1.05)' },
boxShadow: '0 15px 40px rgba(255, 107, 107, 0.6), inset 0 2px 0 rgba(255,255,255,0.3)',
background: 'linear-gradient(135deg, #ff5252 0%, #dd2c00 50%, #e91e63 100%)',
_before: {
@ -412,7 +426,7 @@ export function SetupPhase() {
}
},
_active: {
transform: 'translateY(-2px) scale(1.02)',
transform: 'translateY(-1px) scale(1.01)',
}
})}
onClick={handleStartGame}
@ -420,16 +434,16 @@ export function SetupPhase() {
<div className={css({
display: 'flex',
alignItems: 'center',
gap: '12px',
gap: { base: '8px', md: '12px' },
justifyContent: 'center'
})}>
<span className={css({
fontSize: '32px',
fontSize: { base: '20px', sm: '24px', md: '32px' },
animation: 'bounce 2s infinite'
})}>🚀</span>
<span>START GAME</span>
<span className={css({
fontSize: '32px',
fontSize: { base: '20px', sm: '24px', md: '32px' },
animation: 'bounce 2s infinite',
animationDelay: '0.5s'
})}>🎮</span>

View File

@ -644,8 +644,8 @@ export function EnhancedChampionArena({ onGameModeChange, onConfigurePlayer, cla
<SortableContext items={availablePlayers.map(p => p.id)} strategy={rectSortingStrategy}>
<DroppableZone
id="roster"
title="Available"
subtitle="Tap to add"
title="🎯 Available Champions"
subtitle="Drag champions here to remove from arena"
isEmpty={availablePlayers.length === 0}
>
{availablePlayers.map(player => (
@ -672,8 +672,8 @@ export function EnhancedChampionArena({ onGameModeChange, onConfigurePlayer, cla
<SortableContext items={arenaPlayers.map(p => p.id)} strategy={rectSortingStrategy}>
<DroppableZone
id="arena"
title="Arena"
subtitle="Drop here"
title="🏟️ Arena"
subtitle="1 champion = Solo • 2 = Battle • 3+ = Tournament"
isEmpty={arenaPlayers.length === 0}
>
{arenaPlayers.map(player => (

View File

@ -0,0 +1,74 @@
{
"branches": ["main"],
"repositoryUrl": "https://github.com/antialias/soroban-abacus-flashcards",
"tagFormat": "abacus-react-v${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "type": "feat", "scope": "abacus-react", "release": "minor" },
{ "type": "fix", "scope": "abacus-react", "release": "patch" },
{ "type": "perf", "scope": "abacus-react", "release": "patch" },
{ "type": "refactor", "scope": "abacus-react", "release": "patch" },
{ "breaking": true, "scope": "abacus-react", "release": "major" }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance Improvements" },
{ "type": "refactor", "section": "Code Refactoring" },
{ "type": "docs", "section": "Documentation" },
{ "type": "style", "section": "Styles" },
{ "type": "test", "section": "Tests" }
]
}
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/npm",
{
"npmPublish": true
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "CHANGELOG.md",
"label": "Changelog"
},
{
"path": "dist/**/*",
"label": "Distribution files"
}
]
}
],
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md",
"package.json"
],
"message": "chore(abacus-react): release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}

View File

@ -375,6 +375,54 @@ import {
// All interfaces fully typed for excellent developer experience
```
## Publishing and Versioning
This package uses [semantic-release](https://semantic-release.gitbook.io/) for automated publishing to npm. Versions are determined by conventional commit messages:
### Commit Message Format
Use these prefixes for commits that affect the `packages/abacus-react` directory:
```bash
# New features (minor version bump)
feat(abacus-react): add new bead animation system
# Bug fixes (patch version bump)
fix(abacus-react): resolve gesture detection issue
# Performance improvements (patch version bump)
perf(abacus-react): optimize bead rendering
# Breaking changes (major version bump)
feat(abacus-react)!: change callback signature
# or
feat(abacus-react): redesign API
BREAKING CHANGE: callback functions now receive different parameters
```
### Release Process
1. **Automatic**: Releases happen automatically when changes are pushed to `main` branch
2. **Manual testing**: Run `pnpm release:dry-run` to test release without publishing
3. **Version tags**: Releases are tagged as `abacus-react-v1.2.3` (separate from monorepo versions)
### Development Commands
```bash
# Build the package
pnpm build
# Run tests
pnpm test:run
# Run Storybook locally
pnpm storybook
# Test release process (dry run)
pnpm release:dry-run
```
## Contributing
Contributions welcome! Please see our contributing guidelines and feel free to submit issues or pull requests.

View File

@ -28,7 +28,9 @@
"storybook": "storybook dev -p 6007",
"build-storybook": "storybook build",
"clean": "rm -rf dist storybook-static",
"generate-examples": "tsx generate-examples.js"
"generate-examples": "tsx generate-examples.js",
"release": "semantic-release",
"release:dry-run": "semantic-release --dry-run"
},
"keywords": [
"react",
@ -51,6 +53,9 @@
"@radix-ui/react-tooltip": "^1.2.8"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.0",
"@semantic-release/git": "^10.0.0",
"@semantic-release/github": "^9.0.0",
"@storybook/addon-actions": "^7.6.0",
"@storybook/addon-controls": "^7.6.0",
"@storybook/addon-docs": "^7.6.0",
@ -68,10 +73,12 @@
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^5.0.2",
"@vitest/ui": "^3.2.4",
"conventional-changelog-conventionalcommits": "^7.0.0",
"jest-environment-jsdom": "^30.1.2",
"jsdom": "^27.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semantic-release": "^22.0.0",
"storybook": "^7.6.0",
"tsx": "^4.20.5",
"typescript": "^5.0.0",

View File

@ -225,6 +225,15 @@ importers:
specifier: ^10.3.0
version: 10.3.0(react@18.2.0)
devDependencies:
'@semantic-release/changelog':
specifier: ^6.0.0
version: 6.0.0(semantic-release@22.0.0)
'@semantic-release/git':
specifier: ^10.0.0
version: 10.0.0(semantic-release@22.0.0)
'@semantic-release/github':
specifier: ^9.0.0
version: 9.0.0(semantic-release@22.0.0)
'@storybook/addon-actions':
specifier: ^7.6.0
version: 7.6.0
@ -276,6 +285,9 @@ importers:
'@vitest/ui':
specifier: ^3.2.4
version: 3.2.4(vitest@1.0.0)
conventional-changelog-conventionalcommits:
specifier: ^7.0.0
version: 7.0.0
jest-environment-jsdom:
specifier: ^30.1.2
version: 30.1.2
@ -288,6 +300,9 @@ importers:
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
semantic-release:
specifier: ^22.0.0
version: 22.0.0(typescript@5.0.2)
storybook:
specifier: ^7.6.0
version: 7.6.0