mirror of
https://github.com/soconnor0919/robot-plugins.git
synced 2025-12-12 23:24:43 -05:00
248 lines
8.4 KiB
YAML
Executable File
248 lines
8.4 KiB
YAML
Executable File
name: Validate Plugins
|
|
|
|
on:
|
|
push:
|
|
branches: ["main", "develop"]
|
|
pull_request:
|
|
branches: ["main"]
|
|
|
|
jobs:
|
|
validate:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '18'
|
|
cache: 'npm'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
npm install -g ajv-cli
|
|
npm install -g jsonlint
|
|
|
|
- name: Validate JSON syntax
|
|
run: |
|
|
echo "Validating repository.json..."
|
|
jsonlint repository.json
|
|
|
|
echo "Validating plugins/index.json..."
|
|
jsonlint plugins/index.json
|
|
|
|
echo "Validating plugin files..."
|
|
for file in plugins/*.json; do
|
|
if [ "$file" != "plugins/index.json" ]; then
|
|
echo "Validating $file..."
|
|
jsonlint "$file"
|
|
fi
|
|
done
|
|
|
|
- name: Check plugin index consistency
|
|
run: |
|
|
echo "Checking plugin index consistency..."
|
|
node -e "
|
|
const fs = require('fs');
|
|
const index = JSON.parse(fs.readFileSync('plugins/index.json', 'utf8'));
|
|
const files = fs.readdirSync('plugins').filter(f => f.endsWith('.json') && f !== 'index.json');
|
|
|
|
console.log('Index files:', index);
|
|
console.log('Actual files:', files);
|
|
|
|
const missing = files.filter(f => !index.includes(f));
|
|
const extra = index.filter(f => !files.includes(f));
|
|
|
|
if (missing.length > 0) {
|
|
console.error('Files missing from index:', missing);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (extra.length > 0) {
|
|
console.error('Extra files in index:', extra);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('Plugin index is consistent ✓');
|
|
"
|
|
|
|
- name: Validate plugin schemas
|
|
run: |
|
|
echo "Validating plugin schemas..."
|
|
node -e "
|
|
const fs = require('fs');
|
|
|
|
// Basic plugin schema validation
|
|
function validatePlugin(pluginPath) {
|
|
const plugin = JSON.parse(fs.readFileSync(pluginPath, 'utf8'));
|
|
const errors = [];
|
|
|
|
// Required fields
|
|
const required = ['robotId', 'name', 'platform', 'version', 'pluginApiVersion', 'hriStudioVersion', 'trustLevel', 'category'];
|
|
for (const field of required) {
|
|
if (!plugin[field]) {
|
|
errors.push(\`Missing required field: \${field}\`);
|
|
}
|
|
}
|
|
|
|
// Validate trustLevel enum
|
|
if (plugin.trustLevel && !['official', 'verified', 'community'].includes(plugin.trustLevel)) {
|
|
errors.push(\`Invalid trustLevel: \${plugin.trustLevel}\`);
|
|
}
|
|
|
|
// Validate actions
|
|
if (plugin.actions && Array.isArray(plugin.actions)) {
|
|
plugin.actions.forEach((action, i) => {
|
|
if (!action.id) errors.push(\`Action \${i}: missing id\`);
|
|
if (!action.name) errors.push(\`Action \${i}: missing name\`);
|
|
if (!action.category) errors.push(\`Action \${i}: missing category\`);
|
|
if (action.category && !['movement', 'interaction', 'sensors', 'logic'].includes(action.category)) {
|
|
errors.push(\`Action \${i}: invalid category \${action.category}\`);
|
|
}
|
|
if (!action.parameterSchema) errors.push(\`Action \${i}: missing parameterSchema\`);
|
|
});
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
const index = JSON.parse(fs.readFileSync('plugins/index.json', 'utf8'));
|
|
let hasErrors = false;
|
|
|
|
for (const pluginFile of index) {
|
|
console.log(\`Validating \${pluginFile}...\`);
|
|
const errors = validatePlugin(\`plugins/\${pluginFile}\`);
|
|
|
|
if (errors.length > 0) {
|
|
console.error(\`Errors in \${pluginFile}:\`);
|
|
errors.forEach(error => console.error(\` - \${error}\`));
|
|
hasErrors = true;
|
|
} else {
|
|
console.log(\` ✓ Valid\`);
|
|
}
|
|
}
|
|
|
|
if (hasErrors) {
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('All plugins are valid ✓');
|
|
"
|
|
|
|
- name: Check asset references
|
|
run: |
|
|
echo "Checking asset references..."
|
|
node -e "
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
function checkAssets(pluginPath) {
|
|
const plugin = JSON.parse(fs.readFileSync(pluginPath, 'utf8'));
|
|
const errors = [];
|
|
|
|
if (plugin.assets) {
|
|
const checkAsset = (assetPath, description) => {
|
|
if (assetPath && assetPath.startsWith('assets/')) {
|
|
const fullPath = assetPath;
|
|
if (!fs.existsSync(fullPath)) {
|
|
errors.push(\`\${description}: \${assetPath} not found\`);
|
|
}
|
|
}
|
|
};
|
|
|
|
checkAsset(plugin.assets.thumbnailUrl, 'Thumbnail');
|
|
|
|
if (plugin.assets.images) {
|
|
checkAsset(plugin.assets.images.main, 'Main image');
|
|
checkAsset(plugin.assets.images.logo, 'Logo');
|
|
|
|
if (plugin.assets.images.angles) {
|
|
Object.entries(plugin.assets.images.angles).forEach(([angle, path]) => {
|
|
checkAsset(path, \`\${angle} angle image\`);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
const index = JSON.parse(fs.readFileSync('plugins/index.json', 'utf8'));
|
|
let hasErrors = false;
|
|
|
|
for (const pluginFile of index) {
|
|
console.log(\`Checking assets for \${pluginFile}...\`);
|
|
const errors = checkAssets(\`plugins/\${pluginFile}\`);
|
|
|
|
if (errors.length > 0) {
|
|
console.error(\`Asset errors in \${pluginFile}:\`);
|
|
errors.forEach(error => console.error(\` - \${error}\`));
|
|
hasErrors = true;
|
|
} else {
|
|
console.log(\` ✓ All assets found\`);
|
|
}
|
|
}
|
|
|
|
if (hasErrors) {
|
|
console.warn('Some assets are missing - this may cause display issues');
|
|
} else {
|
|
console.log('All assets are available ✓');
|
|
}
|
|
"
|
|
|
|
- name: Validate repository metadata
|
|
run: |
|
|
echo "Validating repository metadata..."
|
|
node -e "
|
|
const fs = require('fs');
|
|
const repo = JSON.parse(fs.readFileSync('repository.json', 'utf8'));
|
|
const index = JSON.parse(fs.readFileSync('plugins/index.json', 'utf8'));
|
|
|
|
// Check plugin count matches
|
|
const actualCount = index.length;
|
|
const reportedCount = repo.stats?.plugins || 0;
|
|
|
|
if (actualCount !== reportedCount) {
|
|
console.error(\`Plugin count mismatch: reported \${reportedCount}, actual \${actualCount}\`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(\`Plugin count is correct: \${actualCount} ✓\`);
|
|
|
|
// Check required repository fields
|
|
const required = ['id', 'name', 'apiVersion', 'pluginApiVersion', 'trust'];
|
|
for (const field of required) {
|
|
if (!repo[field]) {
|
|
console.error(\`Missing required repository field: \${field}\`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
console.log('Repository metadata is valid ✓');
|
|
"
|
|
|
|
- name: Test web interface
|
|
run: |
|
|
echo "Testing web interface..."
|
|
# Start a simple HTTP server
|
|
python3 -m http.server 8000 &
|
|
SERVER_PID=$!
|
|
|
|
# Wait for server to start
|
|
sleep 2
|
|
|
|
# Test that index.html loads
|
|
curl -f http://localhost:8000/index.html > /dev/null
|
|
|
|
# Test that repository.json is accessible
|
|
curl -f http://localhost:8000/repository.json > /dev/null
|
|
|
|
# Test that plugins/index.json is accessible
|
|
curl -f http://localhost:8000/plugins/index.json > /dev/null
|
|
|
|
# Clean up
|
|
kill $SERVER_PID
|
|
|
|
echo "Web interface test passed ✓"
|