Implement multi-scheme animation name mapper for Owen Animation System
Some checks failed
CI/CD Pipeline / Test & Lint (16.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (18.x) (push) Has been cancelled
CI/CD Pipeline / Test & Lint (20.x) (push) Has been cancelled
CI/CD Pipeline / Security Audit (push) Has been cancelled
CI/CD Pipeline / Release (push) Has been cancelled
Release / Validate Version (push) Has been cancelled
Release / Build and Test (push) Has been cancelled
Release / Create Release (push) Has been cancelled
Release / Publish to NPM (push) Has been cancelled
Release / Deploy Demo (push) Has been cancelled
Animation Processing Pipeline / Validate Animation Names (push) Has been cancelled
Animation Processing Pipeline / Process Blender Animation Assets (push) Has been cancelled
Animation Processing Pipeline / Update Animation Documentation (push) Has been cancelled
Animation Processing Pipeline / Deploy Animation Demo (push) Has been cancelled

- Added AnimationNameMapper class to handle conversion between different animation naming schemes (legacy, artist, hierarchical, semantic).
- Included methods for initialization, pattern matching, conversion, and validation of animation names.
- Developed comprehensive unit tests for the animation name converter and demo pages using Playwright.
- Created a Vite configuration for the demo application, including asset handling and optimization settings.
- Enhanced the demo with features for batch conversion, performance metrics, and responsive design.
This commit is contained in:
2025-05-24 05:18:13 +02:00
parent d513e80c07
commit ad8dbb95dd
55 changed files with 20060 additions and 6686 deletions

View File

@ -0,0 +1,169 @@
name: Animation Processing Pipeline
on:
push:
paths:
- 'assets/animations/**'
- 'src/animation/AnimationNameMapper.js'
- 'src/animation/AnimationConstants.js'
pull_request:
paths:
- 'assets/animations/**'
- 'src/animation/AnimationNameMapper.js'
- 'src/animation/AnimationConstants.js'
workflow_dispatch:
inputs:
animation_scheme:
description: 'Primary naming scheme to use'
required: true
default: 'semantic'
type: choice
options:
- legacy
- artist
- hierarchical
- semantic
jobs:
validate-animations:
name: Validate Animation Names
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Validate animation naming schemes
run: node scripts/validate-animations.js
env:
PRIMARY_SCHEME: ${{ github.event.inputs.animation_scheme || 'semantic' }}
- name: Generate animation constants
run: node scripts/generate-animation-constants.js
- name: Check for naming conflicts
run: node scripts/check-naming-conflicts.js
- name: Upload validation report
uses: actions/upload-artifact@v4
with:
name: animation-validation-report
path: |
reports/animation-validation.json
reports/naming-conflicts.json
process-blender-assets:
name: Process Blender Animation Assets
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[process-blender]') || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Blender
uses: FlorianBreitwieser/setup-blender@v1
with:
blender-version: '3.6'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Process Blender files
run: |
python scripts/blender-animation-processor.py \
--input-dir assets/blender \
--output-dir assets/animations \
--naming-scheme artist
- name: Convert animation names
run: node scripts/convert-animation-names.js
- name: Validate processed animations
run: node scripts/validate-processed-animations.js
- name: Commit processed assets
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'Auto-process Blender animation assets [skip ci]'
file_pattern: 'assets/animations/* src/animation/AnimationConstants.js'
update-documentation:
name: Update Animation Documentation
runs-on: ubuntu-latest
needs: [validate-animations]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate animation documentation
run: node scripts/generate-animation-docs.js
- name: Update API documentation
run: npm run docs
- name: Generate multi-scheme examples
run: node scripts/generate-scheme-examples.js
- name: Commit documentation updates
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'Auto-update animation documentation [skip ci]'
file_pattern: |
docs/**
MULTI_SCHEME_GUIDE.md
examples/*/README.md
deploy-demo:
name: Deploy Animation Demo
runs-on: ubuntu-latest
needs: [validate-animations, update-documentation]
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build demo
run: npm run build:demo
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist-demo
cname: owen-animation-demo.your-domain.com

118
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,118 @@
name: CI/CD Pipeline
on:
push:
branches: [ main, master, develop ]
pull_request:
branches: [ main, master ]
jobs:
test:
name: Test & Lint
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Build project
run: npm run build
- name: Generate documentation
run: npm run docs
- name: Upload build artifacts
if: matrix.node-version == '20.x'
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: |
dist/
docs/
security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run security audit
run: npm audit --audit-level=high
- name: Run security scan
uses: securecodewarrior/github-action-add-sarif@v1
with:
sarif-file: 'security-scan-results.sarif'
continue-on-error: true
release:
name: Release
runs-on: ubuntu-latest
needs: [test, security]
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Generate documentation
run: npm run docs
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts
- name: Create release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
dist/**
docs/**
CHANGELOG.md
README.md
MULTI_SCHEME_GUIDE.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

246
.github/workflows/demo-deployment.yml vendored Normal file
View File

@ -0,0 +1,246 @@
name: Demo Deployment
on:
push:
branches: [ main, master ]
paths:
- 'demo/**'
- 'src/**'
- 'vite.demo.config.js'
- 'package.json'
pull_request:
branches: [ main, master ]
paths:
- 'demo/**'
- 'src/**'
- 'vite.demo.config.js'
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
env:
NODE_VERSION: '20.x'
jobs:
build-demo:
name: Build Demo
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate animation constants
run: npm run generate:constants
- name: Build demo application
run: npm run build:demo
env:
NODE_ENV: production
- name: Validate demo build
run: |
test -d dist/demo || (echo "Demo build failed - dist/demo directory not found" && exit 1)
test -f dist/demo/index.html || (echo "Demo build failed - index.html not found" && exit 1)
test -f dist/demo/examples.html || (echo "Demo build failed - examples.html not found" && exit 1)
test -f dist/demo/comparison.html || (echo "Demo build failed - comparison.html not found" && exit 1)
test -f dist/demo/interactive.html || (echo "Demo build failed - interactive.html not found" && exit 1)
- name: Upload demo artifacts
uses: actions/upload-artifact@v4
with:
name: demo-build
path: dist/demo/
retention-days: 30
- name: Upload build reports
uses: actions/upload-artifact@v4
with:
name: build-reports
path: |
dist/demo/report.html
dist/demo/stats.json
retention-days: 7
test-demo:
name: Test Demo
runs-on: ubuntu-latest
needs: build-demo
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Download demo build
uses: actions/download-artifact@v4
with:
name: demo-build
path: dist/demo/
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run demo tests
run: npm run test:demo
env:
CI: true
PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/ms-playwright
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload test screenshots
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-screenshots
path: test-results/
retention-days: 7
lighthouse-audit:
name: Performance Audit
runs-on: ubuntu-latest
needs: build-demo
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Download demo build
uses: actions/download-artifact@v4
with:
name: demo-build
path: dist/demo/
- name: Install Lighthouse
run: npm install -g @lhci/cli lighthouse
- name: Start demo server
run: |
npx vite preview --config vite.demo.config.js --port 3000 &
sleep 10
env:
NODE_ENV: production
- name: Run Lighthouse audit
run: |
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- name: Upload Lighthouse results
uses: actions/upload-artifact@v4
with:
name: lighthouse-reports
path: .lighthouseci/
retention-days: 7
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [build-demo, test-demo]
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.event.inputs.environment == 'staging'
environment: staging
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download demo build
uses: actions/download-artifact@v4
with:
name: demo-build
path: dist/demo/
- name: Deploy to staging
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/demo
publish_branch: gh-pages-staging
force_orphan: true
- name: Update deployment status
run: |
echo "Demo deployed to staging: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/staging/"
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [build-demo, test-demo, lighthouse-audit]
if: github.ref == 'refs/heads/main' && github.event.inputs.environment == 'production'
environment: production
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download demo build
uses: actions/download-artifact@v4
with:
name: demo-build
path: dist/demo/
- name: Deploy to production
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/demo
publish_branch: gh-pages
force_orphan: true
- name: Update deployment status
run: |
echo "Demo deployed to production: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"
- name: Create deployment notification
uses: actions/github-script@v7
with:
script: |
github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,
repo: context.repo.repo,
deployment_id: context.payload.deployment.id,
state: 'success',
environment_url: 'https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/',
description: 'Demo successfully deployed'
});

View File

@ -0,0 +1,251 @@
name: Multi-Scheme Testing
on:
push:
branches: [ main, master, develop ]
paths:
- 'src/animation/**'
- 'src/core/OwenAnimationContext.js'
- 'examples/**'
pull_request:
branches: [ main, master ]
paths:
- 'src/animation/**'
- 'src/core/OwenAnimationContext.js'
- 'examples/**'
schedule:
# Run daily at 2 AM UTC
- cron: '0 2 * * *'
env:
NODE_VERSION: '20.x'
jobs:
scheme-validation:
name: Validate Naming Schemes
runs-on: ubuntu-latest
strategy:
matrix:
scheme: [legacy, artist, hierarchical, semantic]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Test ${{ matrix.scheme }} scheme
run: |
node -e "
const { AnimationNameMapper } = require('./src/animation/AnimationNameMapper.js');
const mapper = new AnimationNameMapper();
console.log('Testing ${{ matrix.scheme }} scheme...');
// Test all animations in this scheme
const animations = mapper.getAllAnimationsByScheme('${{ matrix.scheme }}');
console.log('Found', animations.length, 'animations');
// Test conversions
let errors = 0;
animations.forEach(anim => {
try {
const converted = mapper.convert(anim, '${{ matrix.scheme }}');
if (converted !== anim) {
console.error('Conversion error:', anim, '->', converted);
errors++;
}
} catch (e) {
console.error('Error processing:', anim, e.message);
errors++;
}
});
if (errors > 0) {
console.error('Found', errors, 'errors in ${{ matrix.scheme }} scheme');
process.exit(1);
} else {
console.log('All ${{ matrix.scheme }} animations validated successfully');
}
"
conversion-matrix:
name: Test Scheme Conversions
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Test all scheme conversions
run: |
node -e "
const { AnimationNameMapper } = require('./src/animation/AnimationNameMapper.js');
const mapper = new AnimationNameMapper();
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic'];
const testAnimations = [
'wait_idle_L',
'Owen_ReactAngry',
'owen.state.type.idle.loop',
'OwenSleepToWaitTransition'
];
console.log('Testing conversion matrix...');
let totalTests = 0;
let passedTests = 0;
testAnimations.forEach(anim => {
schemes.forEach(fromScheme => {
schemes.forEach(toScheme => {
totalTests++;
try {
const result = mapper.convert(anim, toScheme);
console.log('✓', anim, '->', result, '(' + fromScheme + ' to ' + toScheme + ')');
passedTests++;
} catch (e) {
console.log('✗', anim, 'failed conversion from', fromScheme, 'to', toScheme, ':', e.message);
}
});
});
});
console.log('Conversion matrix results:', passedTests + '/' + totalTests, 'passed');
if (passedTests < totalTests * 0.9) {
console.error('Too many conversion failures');
process.exit(1);
}
"
demo-validation:
name: Validate Demo Functionality
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps chromium
- name: Start demo server
run: |
cd examples/mock-demo
python -m http.server 8080 &
sleep 5
- name: Test demo functionality
run: |
npx playwright test --config=playwright.config.js
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: demo-test-results
path: |
test-results/
playwright-report/
performance-benchmark:
name: Performance Benchmarks
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run performance benchmarks
run: |
node -e "
const { AnimationNameMapper } = require('./src/animation/AnimationNameMapper.js');
const mapper = new AnimationNameMapper();
console.log('Running performance benchmarks...');
// Benchmark conversion speed
const testAnim = 'wait_idle_L';
const iterations = 10000;
console.time('10k conversions');
for (let i = 0; i < iterations; i++) {
mapper.convert(testAnim, 'semantic');
}
console.timeEnd('10k conversions');
// Benchmark validation speed
console.time('10k validations');
for (let i = 0; i < iterations; i++) {
mapper.validateAnimationName(testAnim);
}
console.timeEnd('10k validations');
// Memory usage test
const used = process.memoryUsage();
console.log('Memory usage:');
for (let key in used) {
console.log(key + ':', Math.round(used[key] / 1024 / 1024 * 100) / 100, 'MB');
}
"
- name: Comment PR with benchmark results
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
// Read benchmark results (would need to be saved to file in previous step)
const comment = `
## 🏃‍♂️ Performance Benchmark Results
Multi-scheme animation system performance test completed:
- ✅ Conversion speed: 10k operations completed
- ✅ Validation speed: 10k operations completed
- ✅ Memory usage: Within acceptable limits
Full results available in the workflow logs.
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});

View File

@ -0,0 +1,569 @@
name: Performance Testing
on:
push:
branches: [ main, master ]
paths:
- 'src/animation/**'
- 'demo/**'
- 'scripts/**'
pull_request:
branches: [ main, master ]
paths:
- 'src/animation/**'
- 'demo/**'
- 'scripts/**'
schedule:
# Run performance tests weekly on Sundays at 3 AM UTC
- cron: '0 3 * * 0'
workflow_dispatch:
inputs:
test_type:
description: 'Type of performance test to run'
required: true
default: 'all'
type: choice
options:
- all
- conversion
- validation
- memory
- lighthouse
env:
NODE_VERSION: '20.x'
jobs:
conversion-performance:
name: Animation Conversion Performance
runs-on: ubuntu-latest
if: github.event.inputs.test_type == 'all' || github.event.inputs.test_type == 'conversion' || github.event.inputs.test_type == null
strategy:
matrix:
scheme: [legacy, artist, hierarchical, semantic]
batch_size: [100, 1000, 5000]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Generate test data
run: |
node -e "
const fs = require('fs');
const testData = [];
const schemes = ['legacy', 'artist', 'hierarchical', 'semantic'];
const baseNames = [
'walk', 'run', 'idle', 'jump', 'attack', 'defend', 'crouch', 'climb',
'swim', 'fly', 'dance', 'wave', 'bow', 'kneel', 'sit', 'stand'
];
for (let i = 0; i < ${{ matrix.batch_size }}; i++) {
const baseName = baseNames[i % baseNames.length];
const variant = String(i + 1).padStart(2, '0');
let animationName;
switch ('${{ matrix.scheme }}') {
case 'legacy':
animationName = \`\${baseName}_\${variant}\`;
break;
case 'artist':
animationName = \`char_\${baseName}_\${variant}\`;
break;
case 'hierarchical':
animationName = \`character/movement/\${baseName}/\${variant}\`;
break;
case 'semantic':
animationName = \`character.movement.\${baseName}.forward\`;
break;
}
testData.push({
name: animationName,
sourceScheme: '${{ matrix.scheme }}',
targetScheme: schemes.filter(s => s !== '${{ matrix.scheme }}')[Math.floor(Math.random() * 3)]
});
}
fs.writeFileSync('test-data.json', JSON.stringify(testData, null, 2));
console.log(\`Generated \${testData.length} test cases for ${{ matrix.scheme }} scheme\`);
"
- name: Run conversion performance test
run: |
node -e "
const fs = require('fs');
const { AnimationNameMapper } = require('./src/animation/AnimationNameMapper.js');
const testData = JSON.parse(fs.readFileSync('test-data.json', 'utf8'));
const mapper = new AnimationNameMapper();
const results = {
scheme: '${{ matrix.scheme }}',
batchSize: ${{ matrix.batch_size }},
totalConversions: testData.length,
startTime: Date.now(),
conversions: [],
errors: []
};
console.log(\`Starting performance test: ${{ matrix.scheme }} scheme, \${testData.length} conversions\`);
for (const testCase of testData) {
const startTime = process.hrtime.bigint();
try {
const result = mapper.convert(
testCase.name,
testCase.sourceScheme,
testCase.targetScheme
);
const endTime = process.hrtime.bigint();
const duration = Number(endTime - startTime) / 1000000; // Convert to milliseconds
results.conversions.push({
input: testCase.name,
output: result,
sourceScheme: testCase.sourceScheme,
targetScheme: testCase.targetScheme,
duration: duration
});
} catch (error) {
results.errors.push({
input: testCase.name,
sourceScheme: testCase.sourceScheme,
targetScheme: testCase.targetScheme,
error: error.message
});
}
}
results.endTime = Date.now();
results.totalDuration = results.endTime - results.startTime;
results.averageConversionTime = results.conversions.length > 0
? results.conversions.reduce((sum, c) => sum + c.duration, 0) / results.conversions.length
: 0;
results.conversionsPerSecond = (results.conversions.length / results.totalDuration) * 1000;
results.errorRate = (results.errors.length / testData.length) * 100;
console.log(\`Performance Results:\`);
console.log(\` Total Duration: \${results.totalDuration}ms\`);
console.log(\` Average Conversion Time: \${results.averageConversionTime.toFixed(2)}ms\`);
console.log(\` Conversions per Second: \${results.conversionsPerSecond.toFixed(2)}\`);
console.log(\` Error Rate: \${results.errorRate.toFixed(2)}%\`);
console.log(\` Successful Conversions: \${results.conversions.length}\`);
console.log(\` Failed Conversions: \${results.errors.length}\`);
// Save detailed results
fs.writeFileSync('performance-results.json', JSON.stringify(results, null, 2));
// Performance thresholds
const MAX_AVG_CONVERSION_TIME = 10; // 10ms
const MAX_ERROR_RATE = 5; // 5%
const MIN_CONVERSIONS_PER_SECOND = 100;
if (results.averageConversionTime > MAX_AVG_CONVERSION_TIME) {
console.error(\`PERFORMANCE ISSUE: Average conversion time (\${results.averageConversionTime.toFixed(2)}ms) exceeds threshold (\${MAX_AVG_CONVERSION_TIME}ms)\`);
process.exit(1);
}
if (results.errorRate > MAX_ERROR_RATE) {
console.error(\`PERFORMANCE ISSUE: Error rate (\${results.errorRate.toFixed(2)}%) exceeds threshold (\${MAX_ERROR_RATE}%)\`);
process.exit(1);
}
if (results.conversionsPerSecond < MIN_CONVERSIONS_PER_SECOND) {
console.error(\`PERFORMANCE ISSUE: Conversions per second (\${results.conversionsPerSecond.toFixed(2)}) below threshold (\${MIN_CONVERSIONS_PER_SECOND})\`);
process.exit(1);
}
console.log('All performance thresholds passed! ✓');
"
- name: Upload performance results
uses: actions/upload-artifact@v4
with:
name: performance-results-${{ matrix.scheme }}-${{ matrix.batch_size }}
path: performance-results.json
retention-days: 30
memory-performance:
name: Memory Usage Analysis
runs-on: ubuntu-latest
if: github.event.inputs.test_type == 'all' || github.event.inputs.test_type == 'memory' || github.event.inputs.test_type == null
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run memory analysis
run: |
node --expose-gc -e "
const { AnimationNameMapper } = require('./src/animation/AnimationNameMapper.js');
function getMemoryUsage() {
global.gc();
const used = process.memoryUsage();
return {
rss: Math.round(used.rss / 1024 / 1024 * 100) / 100,
heapTotal: Math.round(used.heapTotal / 1024 / 1024 * 100) / 100,
heapUsed: Math.round(used.heapUsed / 1024 / 1024 * 100) / 100,
external: Math.round(used.external / 1024 / 1024 * 100) / 100
};
}
console.log('Starting memory analysis...');
const initialMemory = getMemoryUsage();
console.log('Initial memory usage:', initialMemory);
// Create multiple mappers to test memory leaks
const mappers = [];
for (let i = 0; i < 100; i++) {
mappers.push(new AnimationNameMapper());
}
const afterCreationMemory = getMemoryUsage();
console.log('After creating 100 mappers:', afterCreationMemory);
// Perform conversions
const testAnimations = [
'char_walk_01', 'char_run_02', 'prop_door_open',
'character.idle.basic', 'character/movement/walk/forward',
'idle_basic', 'walk_forward', 'attack_sword'
];
for (let round = 0; round < 10; round++) {
for (const mapper of mappers) {
for (const animation of testAnimations) {
try {
mapper.convert(animation, 'artist', 'semantic');
mapper.convert(animation, 'semantic', 'hierarchical');
mapper.convert(animation, 'hierarchical', 'legacy');
} catch (error) {
// Ignore conversion errors for memory test
}
}
}
if (round % 3 === 0) {
const memoryUsage = getMemoryUsage();
console.log(\`Round \${round + 1} memory usage:\`, memoryUsage);
}
}
const finalMemory = getMemoryUsage();
console.log('Final memory usage:', finalMemory);
// Calculate memory growth
const heapGrowth = finalMemory.heapUsed - initialMemory.heapUsed;
const rssGrowth = finalMemory.rss - initialMemory.rss;
console.log(\`Heap growth: \${heapGrowth} MB\`);
console.log(\`RSS growth: \${rssGrowth} MB\`);
// Memory leak thresholds
const MAX_HEAP_GROWTH = 50; // 50 MB
const MAX_RSS_GROWTH = 100; // 100 MB
if (heapGrowth > MAX_HEAP_GROWTH) {
console.error(\`MEMORY LEAK: Heap growth (\${heapGrowth} MB) exceeds threshold (\${MAX_HEAP_GROWTH} MB)\`);
process.exit(1);
}
if (rssGrowth > MAX_RSS_GROWTH) {
console.error(\`MEMORY LEAK: RSS growth (\${rssGrowth} MB) exceeds threshold (\${MAX_RSS_GROWTH} MB)\`);
process.exit(1);
}
console.log('Memory usage within acceptable limits ✓');
"
lighthouse-performance:
name: Demo Performance Audit
runs-on: ubuntu-latest
if: github.event.inputs.test_type == 'all' || github.event.inputs.test_type == 'lighthouse' || github.event.inputs.test_type == null
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build demo
run: npm run build:demo
- name: Install Lighthouse
run: npm install -g @lhci/cli lighthouse
- name: Start demo server
run: |
npm run preview:demo &
sleep 10
env:
NODE_ENV: production
- name: Run Lighthouse audit
run: |
lighthouse http://localhost:3000 \
--output=json \
--output-path=lighthouse-report.json \
--chrome-flags="--headless --no-sandbox --disable-dev-shm-usage" \
--only-categories=performance,accessibility,best-practices
- name: Analyze Lighthouse results
run: |
node -e "
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('lighthouse-report.json', 'utf8'));
const scores = {
performance: report.categories.performance.score * 100,
accessibility: report.categories.accessibility.score * 100,
bestPractices: report.categories['best-practices'].score * 100
};
const metrics = {
fcp: report.audits['first-contentful-paint'].numericValue,
lcp: report.audits['largest-contentful-paint'].numericValue,
cls: report.audits['cumulative-layout-shift'].numericValue,
tbt: report.audits['total-blocking-time'].numericValue,
tti: report.audits['interactive'].numericValue
};
console.log('Lighthouse Scores:');
console.log(\` Performance: \${scores.performance.toFixed(1)}/100\`);
console.log(\` Accessibility: \${scores.accessibility.toFixed(1)}/100\`);
console.log(\` Best Practices: \${scores.bestPractices.toFixed(1)}/100\`);
console.log('\\nCore Web Vitals:');
console.log(\` First Contentful Paint: \${(metrics.fcp / 1000).toFixed(2)}s\`);
console.log(\` Largest Contentful Paint: \${(metrics.lcp / 1000).toFixed(2)}s\`);
console.log(\` Cumulative Layout Shift: \${metrics.cls.toFixed(3)}\`);
console.log(\` Total Blocking Time: \${metrics.tbt.toFixed(0)}ms\`);
console.log(\` Time to Interactive: \${(metrics.tti / 1000).toFixed(2)}s\`);
// Performance thresholds
const thresholds = {
performance: 90,
accessibility: 95,
bestPractices: 90,
fcp: 2000, // 2 seconds
lcp: 2500, // 2.5 seconds
cls: 0.1,
tbt: 300, // 300ms
tti: 3800 // 3.8 seconds
};
let failed = false;
if (scores.performance < thresholds.performance) {
console.error(\`PERFORMANCE ISSUE: Performance score (\${scores.performance.toFixed(1)}) below threshold (\${thresholds.performance})\`);
failed = true;
}
if (scores.accessibility < thresholds.accessibility) {
console.error(\`ACCESSIBILITY ISSUE: Accessibility score (\${scores.accessibility.toFixed(1)}) below threshold (\${thresholds.accessibility})\`);
failed = true;
}
if (metrics.fcp > thresholds.fcp) {
console.error(\`PERFORMANCE ISSUE: FCP (\${(metrics.fcp / 1000).toFixed(2)}s) exceeds threshold (\${thresholds.fcp / 1000}s)\`);
failed = true;
}
if (metrics.lcp > thresholds.lcp) {
console.error(\`PERFORMANCE ISSUE: LCP (\${(metrics.lcp / 1000).toFixed(2)}s) exceeds threshold (\${thresholds.lcp / 1000}s)\`);
failed = true;
}
if (metrics.cls > thresholds.cls) {
console.error(\`PERFORMANCE ISSUE: CLS (\${metrics.cls.toFixed(3)}) exceeds threshold (\${thresholds.cls})\`);
failed = true;
}
if (failed) {
process.exit(1);
}
console.log('\\nAll performance thresholds passed! ✓');
"
- name: Upload Lighthouse report
uses: actions/upload-artifact@v4
with:
name: lighthouse-report
path: lighthouse-report.json
retention-days: 30
generate-performance-report:
name: Generate Performance Report
runs-on: ubuntu-latest
needs: [conversion-performance, memory-performance, lighthouse-performance]
if: always()
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Generate performance report
run: |
node -e "
const fs = require('fs');
const path = require('path');
const report = {
timestamp: new Date().toISOString(),
commit: process.env.GITHUB_SHA || 'unknown',
branch: process.env.GITHUB_REF_NAME || 'unknown',
results: {
conversion: [],
memory: null,
lighthouse: null
},
summary: {
passed: 0,
failed: 0,
warnings: []
}
};
// Process conversion performance results
const artifactsDir = 'artifacts';
if (fs.existsSync(artifactsDir)) {
const artifactDirs = fs.readdirSync(artifactsDir);
for (const dir of artifactDirs) {
if (dir.startsWith('performance-results-')) {
const resultFile = path.join(artifactsDir, dir, 'performance-results.json');
if (fs.existsSync(resultFile)) {
const result = JSON.parse(fs.readFileSync(resultFile, 'utf8'));
report.results.conversion.push(result);
if (result.errorRate <= 5 && result.averageConversionTime <= 10) {
report.summary.passed++;
} else {
report.summary.failed++;
}
}
}
if (dir === 'lighthouse-report') {
const lightouseFile = path.join(artifactsDir, dir, 'lighthouse-report.json');
if (fs.existsSync(lightouseFile)) {
const lighthouse = JSON.parse(fs.readFileSync(lightouseFile, 'utf8'));
report.results.lighthouse = {
performance: lighthouse.categories.performance.score * 100,
accessibility: lighthouse.categories.accessibility.score * 100,
bestPractices: lighthouse.categories['best-practices'].score * 100,
fcp: lighthouse.audits['first-contentful-paint'].numericValue,
lcp: lighthouse.audits['largest-contentful-paint'].numericValue,
cls: lighthouse.audits['cumulative-layout-shift'].numericValue
};
if (report.results.lighthouse.performance >= 90) {
report.summary.passed++;
} else {
report.summary.failed++;
}
}
}
}
}
// Generate markdown report
let markdown = \`# Performance Test Report\\n\\n\`;
markdown += \`**Date:** \${new Date(report.timestamp).toLocaleString()}\\n\`;
markdown += \`**Commit:** \${report.commit}\\n\`;
markdown += \`**Branch:** \${report.branch}\\n\\n\`;
markdown += \`## Summary\\n\\n\`;
markdown += \`- ✅ **Passed:** \${report.summary.passed}\\n\`;
markdown += \`- ❌ **Failed:** \${report.summary.failed}\\n\\n\`;
if (report.results.conversion.length > 0) {
markdown += \`## Conversion Performance\\n\\n\`;
markdown += \`| Scheme | Batch Size | Avg Time (ms) | Conversions/sec | Error Rate (%) |\\n\`;
markdown += \`|--------|------------|---------------|-----------------|----------------|\\n\`;
for (const result of report.results.conversion) {
const status = result.errorRate <= 5 && result.averageConversionTime <= 10 ? '✅' : '❌';
markdown += \`| \${status} \${result.scheme} | \${result.batchSize} | \${result.averageConversionTime.toFixed(2)} | \${result.conversionsPerSecond.toFixed(2)} | \${result.errorRate.toFixed(2)} |\\n\`;
}
markdown += \`\\n\`;
}
if (report.results.lighthouse) {
markdown += \`## Lighthouse Performance\\n\\n\`;
const l = report.results.lighthouse;
markdown += \`- **Performance Score:** \${l.performance.toFixed(1)}/100\\n\`;
markdown += \`- **Accessibility Score:** \${l.accessibility.toFixed(1)}/100\\n\`;
markdown += \`- **Best Practices Score:** \${l.bestPractices.toFixed(1)}/100\\n\`;
markdown += \`- **First Contentful Paint:** \${(l.fcp / 1000).toFixed(2)}s\\n\`;
markdown += \`- **Largest Contentful Paint:** \${(l.lcp / 1000).toFixed(2)}s\\n\`;
markdown += \`- **Cumulative Layout Shift:** \${l.cls.toFixed(3)}\\n\\n\`;
}
fs.writeFileSync('performance-report.json', JSON.stringify(report, null, 2));
fs.writeFileSync('performance-report.md', markdown);
console.log('Performance report generated');
console.log(markdown);
"
- name: Upload performance report
uses: actions/upload-artifact@v4
with:
name: performance-report
path: |
performance-report.json
performance-report.md
retention-days: 90
- name: Comment performance report on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
if (fs.existsSync('performance-report.md')) {
const report = fs.readFileSync('performance-report.md', 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
}

252
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,252 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v1.2.3)'
required: true
type: string
prerelease:
description: 'Mark as pre-release'
required: false
default: false
type: boolean
env:
NODE_VERSION: '20.x'
jobs:
validate-version:
name: Validate Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract version
id: version
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
IS_PRERELEASE="${{ github.event.inputs.prerelease }}"
else
VERSION="${GITHUB_REF#refs/tags/}"
# Check if version contains pre-release identifiers
if [[ "$VERSION" =~ -[a-zA-Z] ]]; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
echo "Release version: $VERSION (prerelease: $IS_PRERELEASE)"
- name: Validate semantic version
run: |
VERSION="${{ steps.version.outputs.version }}"
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "Invalid semantic version: $VERSION"
exit 1
fi
build-and-test:
name: Build and Test
runs-on: ubuntu-latest
needs: validate-version
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
- name: Run multi-scheme validation
run: npm run test:multi-schemes
- name: Build project
run: npm run build
- name: Build demo
run: npm run build:demo
- name: Generate documentation
run: npm run docs
- name: Validate build artifacts
run: |
test -d dist || (echo "Build failed - dist directory not found" && exit 1)
test -d dist/demo || (echo "Demo build failed - dist/demo directory not found" && exit 1)
test -d docs || (echo "Documentation generation failed" && exit 1)
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: |
dist/
docs/
CHANGELOG.md
README.md
MULTI_SCHEME_GUIDE.md
retention-days: 30
create-release:
name: Create Release
runs-on: ubuntu-latest
needs: [validate-version, build-and-test]
permissions:
contents: write
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: release-artifacts
path: ./artifacts
- name: Generate release notes
id: release_notes
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
# Extract changelog for this version
if [ -f CHANGELOG.md ]; then
# Get the changelog section for this version
CHANGELOG_SECTION=$(sed -n "/^## \[${VERSION#v}\]/,/^## \[/p" CHANGELOG.md | head -n -1)
if [ -n "$CHANGELOG_SECTION" ]; then
echo "Found changelog section for version ${VERSION#v}"
echo "$CHANGELOG_SECTION" > release_notes.md
else
echo "No changelog section found for version ${VERSION#v}, generating default notes"
echo "## Changes in ${VERSION}" > release_notes.md
echo "" >> release_notes.md
echo "This release includes various improvements and updates to the Owen Animation System." >> release_notes.md
fi
else
echo "## Changes in ${VERSION}" > release_notes.md
echo "" >> release_notes.md
echo "This release includes various improvements and updates to the Owen Animation System." >> release_notes.md
fi
# Add commit summary since last tag
echo "" >> release_notes.md
echo "### Commits since last release:" >> release_notes.md
git log --oneline $(git describe --tags --abbrev=0 2>/dev/null || echo "HEAD~10")..HEAD >> release_notes.md || true
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.validate-version.outputs.version }}
name: Owen Animation System ${{ needs.validate-version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: ${{ needs.validate-version.outputs.is_prerelease == 'true' }}
files: |
artifacts/dist/**
artifacts/docs/**
artifacts/CHANGELOG.md
artifacts/README.md
artifacts/MULTI_SCHEME_GUIDE.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish-npm:
name: Publish to NPM
runs-on: ubuntu-latest
needs: [validate-version, create-release]
if: needs.validate-version.outputs.is_prerelease == 'false'
environment: npm-publish
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Update package version
run: |
VERSION="${{ needs.validate-version.outputs.version }}"
npm version ${VERSION#v} --no-git-tag-version
- name: Publish to NPM
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
deploy-demo:
name: Deploy Demo
runs-on: ubuntu-latest
needs: [validate-version, create-release]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build demo
run: npm run build:demo
- name: Deploy demo to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/demo
destination_dir: releases/${{ needs.validate-version.outputs.version }}
- name: Update latest demo link
if: needs.validate-version.outputs.is_prerelease == 'false'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/demo
destination_dir: latest