mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-26 06:44:58 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
211
.github/workflows/README.md
vendored
Normal file
211
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
# GitHub Workflows Documentation
|
||||
|
||||
This directory contains automated workflows for CI/CD, code quality, and AI-assisted development.
|
||||
|
||||
## Workflows Overview
|
||||
|
||||
### 1. CI/CD Workflow (`ci.yml`)
|
||||
**Triggered on:** Push to main/master/develop branches, Pull requests
|
||||
|
||||
**Jobs:**
|
||||
- **Lint**: Runs ESLint to check code quality
|
||||
- **Build**: Builds the application and uploads artifacts
|
||||
- **E2E Tests**: Runs Playwright end-to-end tests
|
||||
- **Quality Check**: Checks for console.log statements and TODO comments
|
||||
|
||||
### 2. Automated Code Review (`code-review.yml`)
|
||||
**Triggered on:** Pull request opened, synchronized, or reopened
|
||||
|
||||
**Features:**
|
||||
- Analyzes code changes for security issues (eval, innerHTML, XSS risks)
|
||||
- Checks for code quality issues (console.log, debugger, any types)
|
||||
- Provides suggestions for improvements
|
||||
- **Auto-approves PRs** if no blocking issues are found
|
||||
- Adds appropriate labels (needs-changes, ready-for-review, has-warnings)
|
||||
|
||||
**Review Criteria:**
|
||||
- ✅ Security vulnerabilities
|
||||
- ✅ Code quality issues
|
||||
- ✅ Type safety
|
||||
- ✅ React best practices
|
||||
- ✅ File size warnings
|
||||
|
||||
### 3. Auto Merge (`auto-merge.yml`)
|
||||
**Triggered on:** PR approval, CI workflow completion
|
||||
|
||||
**Features:**
|
||||
- Automatically merges PRs when:
|
||||
- PR is approved by reviewers
|
||||
- All CI checks pass (lint, build, e2e tests)
|
||||
- No merge conflicts
|
||||
- PR is not in draft
|
||||
- **Automatically deletes the branch** after successful merge
|
||||
- Uses squash merge strategy
|
||||
- Posts comments about merge status
|
||||
|
||||
### 4. Issue Triage (`issue-triage.yml`)
|
||||
**Triggered on:** New issues opened, issues labeled
|
||||
|
||||
**Features:**
|
||||
- Automatically categorizes and labels issues:
|
||||
- Type: bug, enhancement, documentation, testing, security, performance
|
||||
- Priority: high, medium, low
|
||||
- AI-fixable flag for automated fixes
|
||||
- Posts welcome message with issue summary
|
||||
- Suggests automated fix attempts for simple issues
|
||||
- Can create fix branches automatically with `create-pr` label
|
||||
|
||||
### 5. PR Management (`pr-management.yml`)
|
||||
**Triggered on:** PR opened, synchronized, labeled
|
||||
|
||||
**Features:**
|
||||
- Auto-labels PRs based on changed files:
|
||||
- workflows, tests, documentation, ui, styling, configuration, dependencies
|
||||
- Size labels (small/medium/large)
|
||||
- Type labels from PR title (bug, enhancement, refactor, etc.)
|
||||
- Validates PR descriptions
|
||||
- Links related issues automatically
|
||||
- Posts comments on related issues
|
||||
|
||||
### 6. Merge Conflict Check (`merge-conflict-check.yml`)
|
||||
**Triggered on:** PR opened/synchronized, push to main/master
|
||||
|
||||
**Features:**
|
||||
- Detects merge conflicts
|
||||
- Posts comment mentioning @copilot to resolve
|
||||
- Adds/removes `merge-conflict` label
|
||||
- Fails CI if conflicts exist
|
||||
|
||||
## Labels Used
|
||||
|
||||
### Automated Labels
|
||||
- `bug` - Bug fixes
|
||||
- `enhancement` - New features
|
||||
- `documentation` - Documentation changes
|
||||
- `tests` - Test-related changes
|
||||
- `security` - Security issues
|
||||
- `performance` - Performance improvements
|
||||
- `needs-changes` - PR requires changes
|
||||
- `ready-for-review` - PR is ready for review
|
||||
- `has-warnings` - PR has warnings to address
|
||||
- `large-pr` - PR with many changes
|
||||
- `size: small/medium/large` - PR size indicators
|
||||
- `ai-fixable` - Issues that can be auto-fixed
|
||||
- `good first issue` - Good for newcomers
|
||||
- `priority: high/medium/low` - Issue priority
|
||||
- `merge-conflict` - PR has merge conflicts
|
||||
- `auto-fix` - Request automated fix
|
||||
- `create-pr` - Create fix PR for issue
|
||||
|
||||
## Configuration
|
||||
|
||||
### ESLint
|
||||
The project uses ESLint with TypeScript support and React-specific rules:
|
||||
- File: `eslint.config.js`
|
||||
- Strict type checking (warnings for gradual adoption)
|
||||
- React hooks validation
|
||||
- Code quality rules
|
||||
|
||||
### Playwright E2E Tests
|
||||
- Configuration: `playwright.config.ts`
|
||||
- Tests directory: `e2e/`
|
||||
- Runs on Chromium browser
|
||||
- Tests include:
|
||||
- Login functionality
|
||||
- Navigation
|
||||
- CRUD operations
|
||||
- Form interactions
|
||||
|
||||
## Usage
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
# Run linter
|
||||
npm run lint
|
||||
|
||||
# Fix linting issues automatically
|
||||
npm run lint:fix
|
||||
|
||||
# Run e2e tests
|
||||
npm run test:e2e
|
||||
|
||||
# Run e2e tests with UI
|
||||
npm run test:e2e:ui
|
||||
|
||||
# Run e2e tests in headed mode
|
||||
npm run test:e2e:headed
|
||||
|
||||
# Build the project
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Triggering Workflows
|
||||
|
||||
**For Issues:**
|
||||
1. Create an issue - automatically triaged and labeled
|
||||
2. Add `auto-fix` label to request automated fix
|
||||
3. Add `create-pr` label to create a fix branch
|
||||
|
||||
**For PRs:**
|
||||
1. Open a PR - automatically reviewed, labeled, and validated
|
||||
2. Push changes - triggers CI/CD pipeline
|
||||
3. Get approval + pass tests - automatically merged and branch deleted
|
||||
|
||||
### Working with AI Assistance
|
||||
|
||||
**Request automated fixes:**
|
||||
- Comment "@copilot fix this issue" on any issue
|
||||
- Add `ai-fixable` label to issues that can be auto-fixed
|
||||
|
||||
**Get code review feedback:**
|
||||
- Reviews are automatic on every PR
|
||||
- Address feedback and push changes
|
||||
- Workflow will re-review automatically
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Write descriptive PR titles** - Used for automatic labeling
|
||||
2. **Link issues in PR descriptions** - Enables automatic issue closing
|
||||
3. **Keep PRs focused and small** - Easier to review and merge
|
||||
4. **Address all review comments** - Even warnings should be considered
|
||||
5. **Test locally before pushing** - Run lint and tests
|
||||
6. **Don't commit console.log statements** - Will be flagged in review
|
||||
7. **Remove debugger statements** - Treated as blocking issues
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### PR Not Auto-Merging
|
||||
- Check that all CI checks passed
|
||||
- Verify PR has approval
|
||||
- Ensure no merge conflicts
|
||||
- Confirm PR is not in draft mode
|
||||
|
||||
### Tests Failing
|
||||
- Run tests locally: `npm run test:e2e`
|
||||
- Check test report artifacts in GitHub Actions
|
||||
- Ensure dev server starts correctly
|
||||
|
||||
### Linting Errors
|
||||
- Run `npm run lint:fix` to auto-fix
|
||||
- Review errors: `npm run lint`
|
||||
- Check `eslint.config.js` for rule configuration
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new workflows:
|
||||
1. Document the workflow in this README
|
||||
2. Add appropriate error handling
|
||||
3. Test the workflow on a test branch
|
||||
4. Ensure proper permissions are set
|
||||
5. Add labels if needed (they'll be created automatically)
|
||||
|
||||
## Security
|
||||
|
||||
Workflows use `GITHUB_TOKEN` with minimal required permissions:
|
||||
- `contents: read/write` - For reading code and merging PRs
|
||||
- `pull-requests: write` - For commenting and managing PRs
|
||||
- `issues: write` - For managing issues
|
||||
- `checks: read` - For reading CI status
|
||||
|
||||
No secrets are required for basic functionality.
|
||||
185
.github/workflows/auto-merge.yml
vendored
Normal file
185
.github/workflows/auto-merge.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
name: Auto Merge
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
check_suite:
|
||||
types: [completed]
|
||||
workflow_run:
|
||||
workflows: ["CI/CD"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
name: Auto Merge PR
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{
|
||||
(github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
|
||||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|
||||
}}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check PR status and merge
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Get PR number from event
|
||||
let prNumber;
|
||||
|
||||
if (context.payload.pull_request) {
|
||||
prNumber = context.payload.pull_request.number;
|
||||
} else if (context.payload.workflow_run) {
|
||||
// Get PR from workflow run
|
||||
const { data: prs } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}`
|
||||
});
|
||||
if (prs.length === 0) {
|
||||
console.log('No open PR found for this branch');
|
||||
return;
|
||||
}
|
||||
prNumber = prs[0].number;
|
||||
} else {
|
||||
console.log('Could not determine PR number');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Checking PR #${prNumber}`);
|
||||
|
||||
// Get PR details
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
if (pr.state !== 'open') {
|
||||
console.log('PR is not open');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pr.draft) {
|
||||
console.log('PR is still in draft');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if PR is approved
|
||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
|
||||
const latestReviews = {};
|
||||
for (const review of reviews) {
|
||||
latestReviews[review.user.login] = review.state;
|
||||
}
|
||||
|
||||
const hasApproval = Object.values(latestReviews).includes('APPROVED');
|
||||
const hasRequestChanges = Object.values(latestReviews).includes('CHANGES_REQUESTED');
|
||||
|
||||
if (!hasApproval) {
|
||||
console.log('PR has not been approved yet');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasRequestChanges) {
|
||||
console.log('PR has requested changes');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check CI status
|
||||
const { data: checks } = await github.rest.checks.listForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: pr.head.sha
|
||||
});
|
||||
|
||||
const requiredChecks = ['lint', 'build', 'test-e2e'];
|
||||
const checkStatuses = {};
|
||||
|
||||
for (const check of checks.check_runs) {
|
||||
checkStatuses[check.name] = check.conclusion;
|
||||
}
|
||||
|
||||
console.log('Check statuses:', checkStatuses);
|
||||
|
||||
// Wait for all required checks to pass
|
||||
const allChecksPassed = requiredChecks.every(checkName =>
|
||||
checkStatuses[checkName] === 'success' || checkStatuses[checkName] === 'skipped'
|
||||
);
|
||||
|
||||
if (!allChecksPassed) {
|
||||
console.log('Not all required checks have passed');
|
||||
|
||||
// Check if any checks failed
|
||||
const anyChecksFailed = Object.values(checkStatuses).some(status =>
|
||||
status === 'failure'
|
||||
);
|
||||
|
||||
if (anyChecksFailed) {
|
||||
console.log('Some checks failed, not merging');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Checks are still running, will retry later');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('All conditions met, merging PR');
|
||||
|
||||
// Add comment before merging
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: '✅ All checks passed and PR is approved! Auto-merging and cleaning up branch.'
|
||||
});
|
||||
|
||||
try {
|
||||
// Merge the PR
|
||||
await github.rest.pulls.merge({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber,
|
||||
merge_method: 'squash',
|
||||
commit_title: `${pr.title} (#${prNumber})`,
|
||||
commit_message: pr.body || ''
|
||||
});
|
||||
|
||||
console.log('PR merged successfully');
|
||||
|
||||
// Delete the branch
|
||||
try {
|
||||
await github.rest.git.deleteRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `heads/${pr.head.ref}`
|
||||
});
|
||||
console.log(`Branch ${pr.head.ref} deleted successfully`);
|
||||
} catch (deleteError) {
|
||||
console.log('Could not delete branch:', deleteError.message);
|
||||
// Don't fail the workflow if branch deletion fails
|
||||
}
|
||||
|
||||
} catch (mergeError) {
|
||||
console.error('Failed to merge PR:', mergeError.message);
|
||||
|
||||
// Post comment about merge failure
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: `❌ Auto-merge failed: ${mergeError.message}\n\nPlease merge manually.`
|
||||
});
|
||||
}
|
||||
122
.github/workflows/ci.yml
vendored
Normal file
122
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master, develop ]
|
||||
pull_request:
|
||||
branches: [ main, master, develop ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint Code
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
|
||||
build:
|
||||
name: Build Application
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
retention-days: 7
|
||||
|
||||
test-e2e:
|
||||
name: E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:e2e
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
quality-check:
|
||||
name: Code Quality Check
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check for console.log statements
|
||||
run: |
|
||||
if git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -E '^\+.*console\.(log|debug|info)'; then
|
||||
echo "⚠️ Found console.log statements in the changes"
|
||||
echo "Please remove console.log statements before merging"
|
||||
exit 1
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check for TODO comments
|
||||
run: |
|
||||
TODO_COUNT=$(git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -E '^\+.*TODO|FIXME' | wc -l)
|
||||
if [ $TODO_COUNT -gt 0 ]; then
|
||||
echo "⚠️ Found $TODO_COUNT TODO/FIXME comments in the changes"
|
||||
echo "Please address TODO comments before merging or create issues for them"
|
||||
fi
|
||||
continue-on-error: true
|
||||
261
.github/workflows/code-review.yml
vendored
Normal file
261
.github/workflows/code-review.yml
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
name: Automated Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
automated-review:
|
||||
name: AI-Assisted Code Review
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run linter for review
|
||||
id: lint
|
||||
run: |
|
||||
npm run lint > lint-output.txt 2>&1 || echo "LINT_FAILED=true" >> $GITHUB_OUTPUT
|
||||
cat lint-output.txt
|
||||
continue-on-error: true
|
||||
|
||||
- name: Analyze code changes
|
||||
id: analyze
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Get PR diff
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
let issues = [];
|
||||
let warnings = [];
|
||||
let suggestions = [];
|
||||
|
||||
// Analyze each file
|
||||
for (const file of files) {
|
||||
const patch = file.patch || '';
|
||||
const filename = file.filename;
|
||||
|
||||
// Check for security issues
|
||||
if (patch.match(/eval\s*\(/)) {
|
||||
issues.push(`⚠️ **Security**: Use of \`eval()\` found in ${filename}`);
|
||||
}
|
||||
if (patch.match(/innerHTML\s*=/)) {
|
||||
warnings.push(`⚠️ **Security**: Direct \`innerHTML\` usage in ${filename}. Consider using safer alternatives.`);
|
||||
}
|
||||
if (patch.match(/dangerouslySetInnerHTML/)) {
|
||||
warnings.push(`⚠️ **Security**: \`dangerouslySetInnerHTML\` usage in ${filename}. Ensure content is sanitized.`);
|
||||
}
|
||||
|
||||
// Check for code quality
|
||||
if (patch.match(/console\.(log|debug|info)/)) {
|
||||
warnings.push(`🔍 **Code Quality**: Console statements found in ${filename}. Remove before merging.`);
|
||||
}
|
||||
if (patch.match(/debugger/)) {
|
||||
issues.push(`🐛 **Debug Code**: Debugger statement found in ${filename}. Remove before merging.`);
|
||||
}
|
||||
if (patch.match(/(:\s*any\b|\bany\s*[>;,\)])/)) {
|
||||
suggestions.push(`💡 **Type Safety**: Consider replacing \`any\` types with specific types in ${filename}`);
|
||||
}
|
||||
|
||||
// Check for best practices
|
||||
if (filename.endsWith('.tsx') || filename.endsWith('.jsx')) {
|
||||
if (patch.match(/useEffect.*\[\]/) && !patch.includes('// eslint-disable')) {
|
||||
suggestions.push(`💡 **React**: Empty dependency array in useEffect in ${filename}. Verify if intentional.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for large files
|
||||
if (file.additions > 500) {
|
||||
warnings.push(`📏 **File Size**: ${filename} has ${file.additions} additions. Consider breaking into smaller files.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Read lint output if exists
|
||||
let lintIssues = '';
|
||||
try {
|
||||
lintIssues = fs.readFileSync('lint-output.txt', 'utf8');
|
||||
} catch (e) {
|
||||
// File doesn't exist
|
||||
}
|
||||
|
||||
// Determine if auto-approve is appropriate
|
||||
const hasBlockingIssues = issues.length > 0 || lintIssues.includes('error');
|
||||
|
||||
return {
|
||||
issues,
|
||||
warnings,
|
||||
suggestions,
|
||||
lintIssues,
|
||||
hasBlockingIssues,
|
||||
fileCount: files.length,
|
||||
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
|
||||
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0)
|
||||
};
|
||||
|
||||
- name: Post review comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
|
||||
|
||||
let comment = '## 🤖 Automated Code Review\n\n';
|
||||
comment += `**Changes Summary:**\n`;
|
||||
comment += `- Files changed: ${analysis.fileCount}\n`;
|
||||
comment += `- Lines added: ${analysis.totalAdditions}\n`;
|
||||
comment += `- Lines deleted: ${analysis.totalDeletions}\n\n`;
|
||||
|
||||
if (analysis.issues.length > 0) {
|
||||
comment += '### ❌ Blocking Issues\n\n';
|
||||
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.warnings.length > 0) {
|
||||
comment += '### ⚠️ Warnings\n\n';
|
||||
analysis.warnings.forEach(warning => comment += `- ${warning}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.suggestions.length > 0) {
|
||||
comment += '### 💡 Suggestions\n\n';
|
||||
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
|
||||
comment += '\n';
|
||||
}
|
||||
|
||||
if (analysis.lintIssues && analysis.lintIssues.includes('error')) {
|
||||
comment += '### 🔴 Linting Errors\n\n';
|
||||
comment += '```\n' + analysis.lintIssues + '\n```\n\n';
|
||||
}
|
||||
|
||||
if (analysis.hasBlockingIssues) {
|
||||
comment += '---\n';
|
||||
comment += '### ❌ Review Status: **CHANGES REQUESTED**\n\n';
|
||||
comment += 'Please address the blocking issues above before this PR can be approved.\n';
|
||||
} else {
|
||||
comment += '---\n';
|
||||
comment += '### ✅ Review Status: **APPROVED**\n\n';
|
||||
comment += 'No blocking issues found! This PR looks good to merge after CI checks pass.\n';
|
||||
}
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(c =>
|
||||
c.user.type === 'Bot' && c.body.includes('Automated Code Review')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
- name: Add labels based on review
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const analysis = JSON.parse('${{ steps.analyze.outputs.result }}');
|
||||
|
||||
let labels = [];
|
||||
|
||||
if (analysis.hasBlockingIssues) {
|
||||
labels.push('needs-changes');
|
||||
} else {
|
||||
labels.push('ready-for-review');
|
||||
}
|
||||
|
||||
if (analysis.warnings.length > 0) {
|
||||
labels.push('has-warnings');
|
||||
}
|
||||
|
||||
if (analysis.totalAdditions > 500) {
|
||||
labels.push('large-pr');
|
||||
}
|
||||
|
||||
// Remove conflicting labels first
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'needs-changes'
|
||||
});
|
||||
} catch (e) {
|
||||
// Label doesn't exist
|
||||
}
|
||||
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'ready-for-review'
|
||||
});
|
||||
} catch (e) {
|
||||
// Label doesn't exist
|
||||
}
|
||||
|
||||
// Add new labels
|
||||
for (const label of labels) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: [label]
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Label ${label} might not exist, skipping...`);
|
||||
}
|
||||
}
|
||||
|
||||
- name: Auto-approve if no issues
|
||||
if: steps.analyze.outputs.result && !fromJSON(steps.analyze.outputs.result).hasBlockingIssues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
event: 'APPROVE',
|
||||
body: '✅ Automated review passed! No blocking issues found. This PR is approved pending successful CI checks.'
|
||||
});
|
||||
194
.github/workflows/issue-triage.yml
vendored
Normal file
194
.github/workflows/issue-triage.yml
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
name: Issue Triage and Auto-Fix
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
name: Triage and Label Issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Analyze and label issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const title = issue.title.toLowerCase();
|
||||
const body = (issue.body || '').toLowerCase();
|
||||
const text = title + ' ' + body;
|
||||
|
||||
let labels = [];
|
||||
|
||||
// Categorize by type
|
||||
if (text.match(/bug|error|crash|broken|fail/)) {
|
||||
labels.push('bug');
|
||||
}
|
||||
if (text.match(/feature|enhancement|add|new|implement/)) {
|
||||
labels.push('enhancement');
|
||||
}
|
||||
if (text.match(/document|readme|docs|guide/)) {
|
||||
labels.push('documentation');
|
||||
}
|
||||
if (text.match(/test|testing|spec|e2e/)) {
|
||||
labels.push('testing');
|
||||
}
|
||||
if (text.match(/security|vulnerability|exploit|xss|sql/)) {
|
||||
labels.push('security');
|
||||
}
|
||||
if (text.match(/performance|slow|optimize|speed/)) {
|
||||
labels.push('performance');
|
||||
}
|
||||
|
||||
// Categorize by priority
|
||||
if (text.match(/critical|urgent|asap|blocker/)) {
|
||||
labels.push('priority: high');
|
||||
} else if (text.match(/minor|low|nice to have/)) {
|
||||
labels.push('priority: low');
|
||||
} else {
|
||||
labels.push('priority: medium');
|
||||
}
|
||||
|
||||
// Check if it's a good first issue
|
||||
if (text.match(/beginner|easy|simple|starter/) || labels.length <= 2) {
|
||||
labels.push('good first issue');
|
||||
}
|
||||
|
||||
// Check if AI can help
|
||||
if (labels.includes('bug') || labels.includes('documentation') || labels.includes('testing')) {
|
||||
labels.push('ai-fixable');
|
||||
}
|
||||
|
||||
// Add labels
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labels
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Some labels may not exist:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Post welcome comment
|
||||
const comment = `👋 Thank you for opening this issue!
|
||||
|
||||
This issue has been automatically labeled as: ${labels.join(', ')}
|
||||
|
||||
${labels.includes('ai-fixable') ? '🤖 This issue appears to be something AI can help with! A fix may be automatically attempted.' : ''}
|
||||
|
||||
A maintainer will review this issue soon. In the meantime, please make sure you've provided:
|
||||
- A clear description of the issue
|
||||
- Steps to reproduce (for bugs)
|
||||
- Expected vs actual behavior
|
||||
- Any relevant error messages or screenshots
|
||||
|
||||
@copilot may be able to help with this issue.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
attempt-auto-fix:
|
||||
name: Attempt Automated Fix
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'ai-fixable') ||
|
||||
(github.event.action == 'labeled' && github.event.label.name == 'auto-fix')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Analyze issue and suggest fix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
|
||||
const comment = `🤖 **AI-Assisted Fix Attempt**
|
||||
|
||||
I've analyzed this issue and here are my suggestions:
|
||||
|
||||
**Issue Type:** ${issue.labels.map(l => l.name).join(', ')}
|
||||
|
||||
**Suggested Actions:**
|
||||
1. Review the issue description carefully
|
||||
2. Check for similar issues in the repository history
|
||||
3. Consider using @copilot to help implement the fix
|
||||
|
||||
**To request an automated fix:**
|
||||
- Add the \`auto-fix\` label to this issue
|
||||
- Ensure the issue description clearly explains:
|
||||
- What needs to be fixed
|
||||
- Where the issue is located (file/line if known)
|
||||
- Expected behavior
|
||||
|
||||
**Note:** Complex issues may require human review before implementation.
|
||||
|
||||
Would you like me to attempt an automated fix? If so, please confirm by commenting "@copilot fix this issue".`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
create-fix-pr:
|
||||
name: Create Fix PR
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'create-pr'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Create fix branch and PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const branchName = `auto-fix/issue-${issue.number}`;
|
||||
|
||||
const comment = `🤖 **Automated Fix PR Creation**
|
||||
|
||||
I've created a branch \`${branchName}\` for this fix.
|
||||
|
||||
**Next Steps:**
|
||||
1. A developer or @copilot will work on the fix in this branch
|
||||
2. A pull request will be created automatically
|
||||
3. The PR will be linked to this issue
|
||||
|
||||
**Branch:** \`${branchName}\`
|
||||
|
||||
To work on this fix:
|
||||
\`\`\`bash
|
||||
git fetch origin
|
||||
git checkout ${branchName}
|
||||
\`\`\`
|
||||
|
||||
This issue will be automatically closed when the PR is merged.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: comment
|
||||
});
|
||||
132
.github/workflows/merge-conflict-check.yml
vendored
Normal file
132
.github/workflows/merge-conflict-check.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Check for Merge Conflicts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
# Also run when the base branch is updated
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check-conflicts:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch base branch
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref || github.event.repository.default_branch }}
|
||||
|
||||
- name: Check for merge conflicts
|
||||
id: conflict-check
|
||||
run: |
|
||||
# Determine the base branch
|
||||
BASE_BRANCH="${{ github.base_ref }}"
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
BASE_BRANCH="${{ github.event.repository.default_branch }}"
|
||||
fi
|
||||
|
||||
echo "Checking for conflicts with origin/$BASE_BRANCH"
|
||||
|
||||
# Try to merge the base branch to see if there are conflicts
|
||||
if git merge-tree $(git merge-base HEAD origin/$BASE_BRANCH) origin/$BASE_BRANCH HEAD | grep -q "^<<<<<"; then
|
||||
echo "has_conflicts=true" >> $GITHUB_OUTPUT
|
||||
echo "✗ Merge conflicts detected!"
|
||||
else
|
||||
echo "has_conflicts=false" >> $GITHUB_OUTPUT
|
||||
echo "✓ No merge conflicts detected"
|
||||
fi
|
||||
|
||||
- name: Comment on PR if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const comment = `## ⚠️ Merge Conflicts Detected
|
||||
|
||||
@copilot This pull request has merge conflicts that need to be resolved.
|
||||
|
||||
**Please resolve the conflicts by:**
|
||||
1. Merging the latest changes from the base branch
|
||||
2. Resolving any conflicting files
|
||||
3. Pushing the updated changes
|
||||
|
||||
---
|
||||
*This is an automated message from the merge conflict checker.*`;
|
||||
|
||||
// Check if we already commented
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('Merge Conflicts Detected')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
- name: Add label if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['merge-conflict']
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Label might not exist yet, skipping...');
|
||||
}
|
||||
|
||||
- name: Remove label if no conflicts
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'false' && github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
name: 'merge-conflict'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Label does not exist or is not applied, skipping...');
|
||||
}
|
||||
|
||||
- name: Fail if conflicts exist
|
||||
if: steps.conflict-check.outputs.has_conflicts == 'true'
|
||||
run: |
|
||||
echo "❌ This PR has merge conflicts and cannot be merged."
|
||||
exit 1
|
||||
187
.github/workflows/pr-management.yml
vendored
Normal file
187
.github/workflows/pr-management.yml
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
name: PR Labeling and Management
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
label-pr:
|
||||
name: Auto-Label Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened' || github.event.action == 'synchronize'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Analyze PR and add labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Get PR files
|
||||
const { data: files } = await github.rest.pulls.listFiles({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr.number,
|
||||
});
|
||||
|
||||
let labels = [];
|
||||
|
||||
// Analyze file changes
|
||||
const fileTypes = {
|
||||
workflows: files.some(f => f.filename.includes('.github/workflows')),
|
||||
tests: files.some(f => f.filename.includes('test') || f.filename.includes('spec') || f.filename.includes('e2e')),
|
||||
docs: files.some(f => f.filename.includes('README') || f.filename.includes('.md') || f.filename.includes('docs/')),
|
||||
components: files.some(f => f.filename.includes('components/') || f.filename.includes('.tsx')),
|
||||
styles: files.some(f => f.filename.includes('.css') || f.filename.includes('style')),
|
||||
config: files.some(f => f.filename.match(/\.(json|yml|yaml|config\.(js|ts))$/)),
|
||||
dependencies: files.some(f => f.filename === 'package.json' || f.filename === 'package-lock.json'),
|
||||
};
|
||||
|
||||
if (fileTypes.workflows) labels.push('workflows');
|
||||
if (fileTypes.tests) labels.push('tests');
|
||||
if (fileTypes.docs) labels.push('documentation');
|
||||
if (fileTypes.components) labels.push('ui');
|
||||
if (fileTypes.styles) labels.push('styling');
|
||||
if (fileTypes.config) labels.push('configuration');
|
||||
if (fileTypes.dependencies) labels.push('dependencies');
|
||||
|
||||
// Size labels
|
||||
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
||||
if (totalChanges < 50) {
|
||||
labels.push('size: small');
|
||||
} else if (totalChanges < 200) {
|
||||
labels.push('size: medium');
|
||||
} else {
|
||||
labels.push('size: large');
|
||||
}
|
||||
|
||||
// Check PR title for type
|
||||
const title = pr.title.toLowerCase();
|
||||
if (title.match(/^fix|bug/)) labels.push('bug');
|
||||
if (title.match(/^feat|feature|add/)) labels.push('enhancement');
|
||||
if (title.match(/^refactor/)) labels.push('refactor');
|
||||
if (title.match(/^docs/)) labels.push('documentation');
|
||||
if (title.match(/^test/)) labels.push('tests');
|
||||
if (title.match(/^chore/)) labels.push('chore');
|
||||
|
||||
// Add labels
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: labels
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Some labels may not exist:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
check-pr-description:
|
||||
name: Check PR Description
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Validate PR description
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body || '';
|
||||
|
||||
let issues = [];
|
||||
|
||||
// Check if description is too short
|
||||
if (body.length < 50) {
|
||||
issues.push('PR description is too short. Please provide more details about the changes.');
|
||||
}
|
||||
|
||||
// Check if description links to an issue
|
||||
if (!body.match(/#\d+|https:\/\/github\.com/)) {
|
||||
issues.push('Consider linking to a related issue using #issue_number');
|
||||
}
|
||||
|
||||
// Check for test information
|
||||
if (body.toLowerCase().includes('test') === false &&
|
||||
!pr.labels.some(l => l.name === 'documentation')) {
|
||||
issues.push('Please mention how these changes were tested.');
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
const comment = `## 📋 PR Description Checklist
|
||||
|
||||
The following items could improve this PR:
|
||||
|
||||
${issues.map(i => `- [ ] ${i}`).join('\n')}
|
||||
|
||||
**Good PR descriptions include:**
|
||||
- What changes were made and why
|
||||
- How to test the changes
|
||||
- Any breaking changes or special considerations
|
||||
- Links to related issues
|
||||
- Screenshots (for UI changes)
|
||||
|
||||
This is a friendly reminder to help maintain code quality! 😊`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
|
||||
link-related-issues:
|
||||
name: Link Related Issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Find and link related issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const body = pr.body || '';
|
||||
const title = pr.title;
|
||||
|
||||
// Extract issue numbers from PR body
|
||||
const issueNumbers = [...body.matchAll(/#(\d+)/g)].map(m => m[1]);
|
||||
|
||||
if (issueNumbers.length > 0) {
|
||||
const comment = `🔗 **Related Issues**
|
||||
|
||||
This PR is related to: ${issueNumbers.map(n => `#${n}`).join(', ')}
|
||||
|
||||
These issues will be automatically closed when this PR is merged.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
// Add comment to related issues
|
||||
for (const issueNum of issueNumbers) {
|
||||
try {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(issueNum),
|
||||
body: `🔗 Pull request #${pr.number} has been created to address this issue.`
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Could not comment on issue #${issueNum}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -26,9 +26,19 @@ dist-ssr
|
||||
|
||||
.env
|
||||
**/agent-eval-report*
|
||||
packages
|
||||
pids
|
||||
.file-manifest
|
||||
.devcontainer/
|
||||
|
||||
.spark-workbench-id
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
/e2e-results/
|
||||
|
||||
# Test artifacts
|
||||
*.spec.js.map
|
||||
*.spec.ts.map
|
||||
lint-output.txt
|
||||
|
||||
334
CI_CD_SUMMARY.md
Normal file
334
CI_CD_SUMMARY.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# CI/CD Implementation Summary
|
||||
|
||||
This document summarizes the CI/CD workflows, automated testing, and AI-assisted development features added to the metabuilder project.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### 1. ESLint Configuration ✅
|
||||
- **File**: `eslint.config.js`
|
||||
- **Features**:
|
||||
- TypeScript support with recommended rules
|
||||
- React hooks validation
|
||||
- Strict defaults (as warnings for gradual adoption)
|
||||
- No `any` types, proper promise handling, unused variable detection
|
||||
- Code quality rules (no-console, no-debugger, prefer-const, no-var)
|
||||
|
||||
### 2. Playwright E2E Testing ✅
|
||||
- **Configuration**: `playwright.config.ts`
|
||||
- **Test Files**:
|
||||
- `e2e/smoke.spec.ts` - Basic smoke tests
|
||||
- `e2e/login.spec.ts` - Authentication and login flows
|
||||
- `e2e/crud.spec.ts` - CRUD operations and data management
|
||||
- **Features**:
|
||||
- Chromium browser support
|
||||
- Screenshot on failure
|
||||
- Trace on first retry
|
||||
- Automatic dev server startup
|
||||
- HTML test reports
|
||||
|
||||
### 3. CI/CD Pipeline ✅
|
||||
**File**: `.github/workflows/ci.yml`
|
||||
|
||||
**Triggers**: Push to main/master/develop, Pull requests
|
||||
|
||||
**Jobs**:
|
||||
1. **Lint** - ESLint code quality checks
|
||||
2. **Build** - TypeScript compilation and Vite build
|
||||
3. **E2E Tests** - Playwright tests with artifacts
|
||||
4. **Quality Check** - Console.log and TODO detection
|
||||
|
||||
### 4. Automated Code Review ✅
|
||||
**File**: `.github/workflows/code-review.yml`
|
||||
|
||||
**Triggers**: PR opened, synchronized, reopened
|
||||
|
||||
**Features**:
|
||||
- Security vulnerability detection (eval, innerHTML, XSS)
|
||||
- Code quality analysis (console.log, debugger, any types)
|
||||
- React best practices validation
|
||||
- File size warnings
|
||||
- **Auto-approval** if no blocking issues found
|
||||
- Automatic labeling (needs-changes, ready-for-review, has-warnings)
|
||||
|
||||
**Review Status**:
|
||||
- ✅ Auto-approves PRs with no issues
|
||||
- ❌ Requests changes for blocking issues
|
||||
- ⚠️ Provides warnings for improvements
|
||||
|
||||
### 5. Auto-Merge Workflow ✅
|
||||
**File**: `.github/workflows/auto-merge.yml`
|
||||
|
||||
**Triggers**: PR approval, CI workflow completion
|
||||
|
||||
**Features**:
|
||||
- Waits for all required checks to pass (lint, build, e2e tests)
|
||||
- Verifies PR approval status
|
||||
- Uses squash merge strategy
|
||||
- **Automatically deletes branch** after merge
|
||||
- Posts status comments
|
||||
|
||||
**Merge Conditions**:
|
||||
- ✅ PR approved
|
||||
- ✅ All CI checks passed
|
||||
- ✅ No merge conflicts
|
||||
- ✅ Not in draft mode
|
||||
|
||||
### 6. Issue Triage ✅
|
||||
**File**: `.github/workflows/issue-triage.yml`
|
||||
|
||||
**Triggers**: Issues opened, labeled
|
||||
|
||||
**Features**:
|
||||
- Automatic issue categorization
|
||||
- Labels: bug, enhancement, documentation, testing, security, performance
|
||||
- Priority assignment (high, medium, low)
|
||||
- AI-fixable detection
|
||||
- Welcome messages for contributors
|
||||
- Automated fix suggestions
|
||||
|
||||
### 7. PR Management ✅
|
||||
**File**: `.github/workflows/pr-management.yml`
|
||||
|
||||
**Triggers**: PR opened, synchronized, labeled
|
||||
|
||||
**Features**:
|
||||
- Auto-labels based on changed files
|
||||
- Size indicators (small/medium/large)
|
||||
- Type detection from PR title
|
||||
- Description validation
|
||||
- Related issue linking
|
||||
- Cross-referencing PRs and issues
|
||||
|
||||
## Workflow Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Developer │
|
||||
│ Pushes Code │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ GitHub Event │
|
||||
│ (Push, PR Open, Issue Create) │
|
||||
└────────┬────────────────────────────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────┐ ┌──────────────┐
|
||||
│ CI/CD │ │ Code Review │
|
||||
│Pipeline │ │ Workflow │
|
||||
└────┬────┘ └──────┬───────┘
|
||||
│ │
|
||||
│ Pass │ No Issues
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────┐
|
||||
│ Auto Approval │
|
||||
└──────────┬─────────────┘
|
||||
│
|
||||
│ All Checks Pass
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Auto Merge & │
|
||||
│ Branch Deletion │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
## Labels System
|
||||
|
||||
### Automatic Labels
|
||||
| Label | Trigger | Purpose |
|
||||
|-------|---------|---------|
|
||||
| `bug` | Issue/PR with "bug", "error", "crash" | Bug fixes |
|
||||
| `enhancement` | Issue/PR with "feature", "add" | New features |
|
||||
| `documentation` | Changes to .md files | Docs updates |
|
||||
| `tests` | Changes to test files | Test modifications |
|
||||
| `security` | Security keywords detected | Security issues |
|
||||
| `needs-changes` | Code review finds issues | PR requires fixes |
|
||||
| `ready-for-review` | Code review passes | PR ready to review |
|
||||
| `has-warnings` | Non-blocking warnings found | PR has warnings |
|
||||
| `size: small/medium/large` | Lines changed | PR size indicator |
|
||||
| `ai-fixable` | Simple fixable issues | Can be auto-fixed |
|
||||
| `merge-conflict` | Conflicts detected | Merge conflicts present |
|
||||
| `priority: high/medium/low` | Issue classification | Priority level |
|
||||
| `good first issue` | Simple issues | Good for newcomers |
|
||||
|
||||
## NPM Scripts Added
|
||||
|
||||
```json
|
||||
{
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed"
|
||||
}
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
metabuilder/
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ ├── README.md # Workflow documentation
|
||||
│ ├── ci.yml # CI/CD pipeline
|
||||
│ ├── code-review.yml # Automated code review
|
||||
│ ├── auto-merge.yml # Auto-merge & cleanup
|
||||
│ ├── issue-triage.yml # Issue management
|
||||
│ ├── pr-management.yml # PR labeling & linking
|
||||
│ └── merge-conflict-check.yml # Existing conflict checker
|
||||
├── e2e/
|
||||
│ ├── README.md # E2E testing guide
|
||||
│ ├── smoke.spec.ts # Basic smoke tests
|
||||
│ ├── login.spec.ts # Authentication tests
|
||||
│ └── crud.spec.ts # CRUD operation tests
|
||||
├── eslint.config.js # ESLint configuration
|
||||
├── playwright.config.ts # Playwright configuration
|
||||
├── tsconfig.json # Updated to include e2e tests
|
||||
├── package.json # Updated with test scripts
|
||||
└── .gitignore # Updated for test artifacts
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### For Contributors
|
||||
|
||||
1. **Create a branch** for your changes
|
||||
2. **Make changes** and test locally
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
npm run test:e2e
|
||||
```
|
||||
3. **Push your branch** - Workflows trigger automatically
|
||||
4. **Review feedback** - Code review bot comments on PR
|
||||
5. **Address issues** - Fix any blocking issues
|
||||
6. **Wait for approval** - Bot auto-approves if no issues
|
||||
7. **Auto-merge** - PR merges and branch deletes automatically
|
||||
|
||||
### For Maintainers
|
||||
|
||||
1. **Create issues** - Auto-triaged and labeled
|
||||
2. **Tag issues** with `ai-fixable` or `auto-fix`
|
||||
3. **Review PRs** - Bot provides initial review
|
||||
4. **Approve PRs** - Triggers auto-merge workflow
|
||||
5. **Monitor CI** - All workflows visible in Actions tab
|
||||
|
||||
## Security Features
|
||||
|
||||
### Code Review Checks
|
||||
- ✅ `eval()` usage detection
|
||||
- ✅ Direct `innerHTML` usage
|
||||
- ✅ `dangerouslySetInnerHTML` validation
|
||||
- ✅ Debugger statements
|
||||
- ✅ Console statements in production code
|
||||
|
||||
### CI Quality Checks
|
||||
- ✅ ESLint strict mode
|
||||
- ✅ TypeScript type checking
|
||||
- ✅ No `any` types (warnings)
|
||||
- ✅ Promise handling validation
|
||||
|
||||
## Benefits
|
||||
|
||||
### For Developers
|
||||
- 🚀 Faster feedback on code quality
|
||||
- ✅ Automated testing on every PR
|
||||
- 🤖 AI-assisted code reviews
|
||||
- 📝 Better organized issues and PRs
|
||||
- 🔄 No manual merge/cleanup needed
|
||||
|
||||
### For Maintainers
|
||||
- 📊 Consistent code quality
|
||||
- 🏷️ Auto-organized issues and PRs
|
||||
- ⚡ Faster review cycles
|
||||
- 🛡️ Security vulnerability detection
|
||||
- 📈 Better project visibility
|
||||
|
||||
### For the Project
|
||||
- ✨ Higher code quality
|
||||
- 🐛 Fewer bugs in production
|
||||
- 📚 Better documentation
|
||||
- 🤝 Easier for contributors
|
||||
- 🔒 More secure codebase
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Adjusting Auto-Merge
|
||||
Edit `.github/workflows/auto-merge.yml`:
|
||||
```yaml
|
||||
# Change merge strategy
|
||||
merge_method: 'squash' # or 'merge', 'rebase'
|
||||
|
||||
# Required checks
|
||||
requiredChecks: ['lint', 'build', 'test-e2e']
|
||||
```
|
||||
|
||||
### Adjusting ESLint Strictness
|
||||
Edit `eslint.config.js`:
|
||||
```javascript
|
||||
// Change from 'warn' to 'error' for stricter enforcement
|
||||
'@typescript-eslint/no-explicit-any': 'error'
|
||||
```
|
||||
|
||||
### Adjusting Test Timeouts
|
||||
Edit `playwright.config.ts`:
|
||||
```typescript
|
||||
timeout: 30 * 1000, // Per test timeout
|
||||
webServer: {
|
||||
timeout: 120 * 1000, // Server startup timeout
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### CI Failing
|
||||
1. Check workflow logs in GitHub Actions
|
||||
2. Run tests locally: `npm run test:e2e`
|
||||
3. Fix linting: `npm run lint:fix`
|
||||
4. Rebuild: `npm run build`
|
||||
|
||||
### Tests Failing
|
||||
1. Check test report artifacts
|
||||
2. Run with UI: `npm run test:e2e:ui`
|
||||
3. Debug: `npx playwright test --debug`
|
||||
4. Check seed data and credentials
|
||||
|
||||
### PR Not Auto-Merging
|
||||
1. Verify all checks passed
|
||||
2. Check for approval
|
||||
3. Ensure no merge conflicts
|
||||
4. Confirm not in draft mode
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Potential Enhancements
|
||||
1. Add more browsers (Firefox, WebKit)
|
||||
2. Add visual regression testing
|
||||
3. Add performance benchmarks
|
||||
4. Add dependency update automation
|
||||
5. Add release automation
|
||||
6. Add changelog generation
|
||||
|
||||
### Monitoring
|
||||
- View workflow runs in GitHub Actions tab
|
||||
- Check test reports in artifacts
|
||||
- Review code review comments on PRs
|
||||
- Monitor issue triage effectiveness
|
||||
|
||||
## Resources
|
||||
|
||||
- [ESLint Documentation](https://eslint.org/)
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
- [Workflow README](.github/workflows/README.md)
|
||||
- [E2E Testing Guide](e2e/README.md)
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ All workflows implemented and ready for use
|
||||
**Date**: December 24, 2025
|
||||
**Author**: GitHub Copilot
|
||||
137
README.md
137
README.md
@@ -10,14 +10,141 @@ A declarative admin panel generator that creates full-featured CRUD interfaces f
|
||||
- **Advanced Features** - Sorting, filtering, search, validation, relationships
|
||||
- **Persistent Storage** - Data automatically saved using Spark KV storage
|
||||
- **Live Schema Editing** - Edit schemas in real-time through the UI
|
||||
- **CI/CD Pipeline** - Automated testing, linting, and deployment
|
||||
- **AI-Assisted Development** - Automated code reviews and issue triage
|
||||
- **E2E Testing** - Comprehensive Playwright test suite
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Launch the app
|
||||
2. Use the sidebar to navigate between models
|
||||
3. Click "Create New" to add records
|
||||
4. Edit or delete records using the action buttons
|
||||
5. Click "Edit Schema" to customize your data models
|
||||
1. Clone the repository
|
||||
2. Install dependencies: `npm install`
|
||||
3. Launch the app: `npm run dev`
|
||||
4. Use the sidebar to navigate between models
|
||||
5. Click "Create New" to add records
|
||||
6. Edit or delete records using the action buttons
|
||||
7. Click "Edit Schema" to customize your data models
|
||||
|
||||
## Development
|
||||
|
||||
### Available Scripts
|
||||
|
||||
```bash
|
||||
npm run dev # Start development server
|
||||
npm run build # Build for production
|
||||
npm run lint # Run ESLint
|
||||
npm run lint:fix # Auto-fix linting issues
|
||||
npm run test:e2e # Run Playwright e2e tests
|
||||
npm run test:e2e:ui # Run tests with Playwright UI
|
||||
npm run test:e2e:headed # Run tests in headed browser mode
|
||||
npm run preview # Preview production build
|
||||
npm run act # Run GitHub Actions workflows locally with act
|
||||
npm run act:lint # Run only lint job locally
|
||||
npm run act:e2e # Run only e2e tests job locally
|
||||
```
|
||||
|
||||
### Testing GitHub Actions Locally
|
||||
|
||||
You can test GitHub Actions workflows locally before pushing using [act](https://github.com/nektos/act):
|
||||
|
||||
```bash
|
||||
# Install act (macOS)
|
||||
brew install act
|
||||
|
||||
# Run CI workflow locally
|
||||
npm run act
|
||||
|
||||
# Run specific jobs
|
||||
npm run act:lint
|
||||
npm run act:e2e
|
||||
|
||||
# See scripts/README.md for more options
|
||||
```
|
||||
|
||||
This is useful for debugging workflow issues without repeatedly pushing to GitHub.
|
||||
|
||||
### Code Quality
|
||||
|
||||
This project uses strict ESLint rules with TypeScript support:
|
||||
- No explicit `any` types (warnings)
|
||||
- Promise handling required
|
||||
- React hooks dependencies validated
|
||||
- Console statements flagged (except warn/error)
|
||||
|
||||
Run `npm run lint:fix` before committing to auto-fix issues.
|
||||
|
||||
### Testing
|
||||
|
||||
The project includes comprehensive E2E tests using Playwright:
|
||||
- Login and authentication flows
|
||||
- Navigation between sections
|
||||
- CRUD operations
|
||||
- Form validation
|
||||
- Schema editor functionality
|
||||
|
||||
Tests run automatically on every PR via GitHub Actions.
|
||||
|
||||
## CI/CD & Automation
|
||||
|
||||
### Automated Workflows
|
||||
|
||||
1. **CI/CD Pipeline** - Runs on every push and PR
|
||||
- Linting with ESLint
|
||||
- TypeScript compilation
|
||||
- Production build
|
||||
- E2E tests with Playwright
|
||||
- Code quality checks
|
||||
|
||||
2. **Automated Code Review** - Reviews every PR
|
||||
- Security vulnerability checks
|
||||
- Code quality analysis
|
||||
- Type safety validation
|
||||
- Best practices enforcement
|
||||
- **Auto-approves** PRs with no issues
|
||||
|
||||
3. **Auto-Merge** - Merges approved PRs automatically
|
||||
- Waits for all CI checks to pass
|
||||
- Requires PR approval
|
||||
- Uses squash merge
|
||||
- Automatically deletes branch after merge
|
||||
|
||||
4. **Issue Triage** - Categorizes and labels issues
|
||||
- Auto-labels by type (bug, feature, docs)
|
||||
- Assigns priority levels
|
||||
- Flags AI-fixable issues
|
||||
- Welcomes contributors
|
||||
|
||||
5. **PR Management** - Organizes pull requests
|
||||
- Auto-labels based on changed files
|
||||
- Validates PR descriptions
|
||||
- Links related issues
|
||||
- Size indicators (small/medium/large)
|
||||
|
||||
See [.github/workflows/README.md](.github/workflows/README.md) for detailed workflow documentation.
|
||||
|
||||
### Contributing
|
||||
|
||||
1. Create a branch for your changes
|
||||
2. Make your changes and test locally
|
||||
3. Push your branch - workflows run automatically
|
||||
4. Address any review comments
|
||||
5. Once approved and tests pass, PR merges automatically
|
||||
|
||||
The automated workflows will:
|
||||
- Review your code
|
||||
- Run tests
|
||||
- Approve if everything looks good
|
||||
- Merge and cleanup after approval
|
||||
|
||||
## Packages
|
||||
|
||||
This project uses a modular package system. The `packages/` folder contains component packages that are committed to the repository.
|
||||
|
||||
If you need to add a new package, use:
|
||||
```bash
|
||||
npm run setup-packages <package-name>
|
||||
```
|
||||
|
||||
This will create the required package structure with placeholder files.
|
||||
|
||||
## Schema Structure
|
||||
|
||||
|
||||
210
e2e/README.md
Normal file
210
e2e/README.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# E2E Testing Guide
|
||||
|
||||
This directory contains end-to-end tests for the metabuilder application using Playwright.
|
||||
|
||||
## Test Files
|
||||
|
||||
- **`login.spec.ts`** - Tests for login functionality, authentication, and password changes
|
||||
- **`crud.spec.ts`** - Tests for CRUD operations, data tables, and schema editing
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Install Playwright browsers (if not already installed)
|
||||
npx playwright install chromium
|
||||
```
|
||||
|
||||
### Running Tests Locally
|
||||
|
||||
```bash
|
||||
# Run all tests (headless)
|
||||
npm run test:e2e
|
||||
|
||||
# Run tests with UI mode (interactive)
|
||||
npm run test:e2e:ui
|
||||
|
||||
# Run tests in headed mode (see browser)
|
||||
npm run test:e2e:headed
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test e2e/login.spec.ts
|
||||
|
||||
# Run tests in debug mode
|
||||
npx playwright test --debug
|
||||
```
|
||||
|
||||
### Test Output
|
||||
|
||||
After running tests, you can view:
|
||||
- **HTML Report**: `npx playwright show-report`
|
||||
- **Test Results**: Located in `test-results/` directory
|
||||
- **Screenshots**: Captured on test failures
|
||||
- **Videos**: Recorded for failed tests (if enabled)
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Login Tests (`login.spec.ts`)
|
||||
|
||||
Tests the authentication flow:
|
||||
1. Display of login form on initial load
|
||||
2. Error handling for invalid credentials
|
||||
3. Successful login with valid credentials
|
||||
4. Password change requirement on first login
|
||||
5. Navigation after successful authentication
|
||||
|
||||
### CRUD Tests (`crud.spec.ts`)
|
||||
|
||||
Tests data management operations:
|
||||
1. Display of data tables/lists
|
||||
2. Create button visibility
|
||||
3. Form opening and interaction
|
||||
4. Input field validation
|
||||
5. Schema editor access (admin users)
|
||||
|
||||
## Test Configuration
|
||||
|
||||
Configuration is in `playwright.config.ts`:
|
||||
- **Base URL**: `http://localhost:5173` (Vite dev server)
|
||||
- **Browsers**: Chromium (can add Firefox, WebKit)
|
||||
- **Retries**: 2 retries on CI, 0 locally
|
||||
- **Timeout**: 120 seconds for web server startup
|
||||
- **Screenshots**: Taken on failure
|
||||
- **Traces**: Captured on first retry
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
Tests run automatically on:
|
||||
- Every push to main/master/develop branches
|
||||
- Every pull request
|
||||
- Manual workflow dispatch
|
||||
|
||||
The CI workflow:
|
||||
1. Installs dependencies
|
||||
2. Installs Playwright browsers
|
||||
3. Starts the dev server
|
||||
4. Runs all tests
|
||||
5. Uploads test results and reports as artifacts
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
### Basic Test Structure
|
||||
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Feature Name', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Setup code (e.g., login)
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('should do something', async ({ page }) => {
|
||||
// Test code
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('.success')).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use descriptive test names** - Clear, action-oriented descriptions
|
||||
2. **Keep tests isolated** - Each test should be independent
|
||||
3. **Use page object patterns** - For complex pages, create page objects
|
||||
4. **Wait appropriately** - Use `waitForLoadState`, `waitForTimeout` sparingly
|
||||
5. **Use semantic locators** - Prefer `getByRole`, `getByLabel` over CSS selectors
|
||||
6. **Test user flows** - Test complete user journeys, not just individual actions
|
||||
7. **Handle async properly** - Always await async operations
|
||||
8. **Clean up state** - Use `beforeEach`/`afterEach` for setup/teardown
|
||||
|
||||
### Common Patterns
|
||||
|
||||
```typescript
|
||||
// Login helper
|
||||
async function login(page, username, password) {
|
||||
await page.getByLabel(/username/i).fill(username);
|
||||
await page.getByLabel(/password/i).fill(password);
|
||||
await page.getByRole('button', { name: /login/i }).click();
|
||||
}
|
||||
|
||||
// Wait for navigation
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check visibility with timeout
|
||||
await expect(page.locator('.element')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Handle conditional elements
|
||||
if (await page.locator('.dialog').isVisible()) {
|
||||
await page.getByRole('button', { name: /close/i }).click();
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Visual Debugging
|
||||
|
||||
```bash
|
||||
# Open Playwright Inspector
|
||||
npx playwright test --debug
|
||||
|
||||
# Run with UI mode
|
||||
npm run test:e2e:ui
|
||||
|
||||
# Run in headed mode to see browser
|
||||
npm run test:e2e:headed
|
||||
```
|
||||
|
||||
### Tracing
|
||||
|
||||
```bash
|
||||
# View trace for failed tests
|
||||
npx playwright show-trace trace.zip
|
||||
```
|
||||
|
||||
### Verbose Output
|
||||
|
||||
```bash
|
||||
# Run with verbose logging
|
||||
DEBUG=pw:api npx playwright test
|
||||
```
|
||||
|
||||
## Known Issues & Limitations
|
||||
|
||||
1. **Test Credentials** - Tests use default seeded credentials
|
||||
- User: `user` / Password: `password123`
|
||||
- Admin: `admin` / Password: `admin123`
|
||||
|
||||
2. **Test Data** - Tests assume default seed data is present
|
||||
|
||||
3. **Timing** - Some tests may need adjustment for slower environments
|
||||
|
||||
4. **State Management** - Tests use isolated browser contexts but share the same database
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tests Timeout
|
||||
- Increase timeout in `playwright.config.ts`
|
||||
- Check if dev server starts: `npm run dev`
|
||||
- Verify port 5173 is available
|
||||
|
||||
### Tests Fail Locally but Pass in CI
|
||||
- Check Node.js version matches CI
|
||||
- Clear browser cache: `npx playwright install --force`
|
||||
- Delete `node_modules` and reinstall
|
||||
|
||||
### Screenshots/Videos Missing
|
||||
- Check `playwright.config.ts` settings
|
||||
- Ensure `test-results/` directory exists
|
||||
- Verify sufficient disk space
|
||||
|
||||
## Resources
|
||||
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
- [Best Practices](https://playwright.dev/docs/best-practices)
|
||||
- [API Reference](https://playwright.dev/docs/api/class-playwright)
|
||||
- [Debugging Guide](https://playwright.dev/docs/debug)
|
||||
58
e2e/crud.spec.ts
Normal file
58
e2e/crud.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Helper function to navigate to login page
|
||||
async function navigateToLogin(page: any) {
|
||||
await page.goto('/');
|
||||
// Click "Sign In" button to navigate to login page
|
||||
await page.getByRole('button', { name: /sign in|get started/i }).first().click();
|
||||
// Wait for login form to appear
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
test.describe('Application Interface', () => {
|
||||
test('should have landing page with navigation options', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Check for navigation buttons
|
||||
const signInButton = page.getByRole('button', { name: /sign in/i });
|
||||
await expect(signInButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('should navigate to login when clicking sign in', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Click sign in
|
||||
await page.getByRole('button', { name: /sign in|get started/i }).first().click();
|
||||
|
||||
// Should see login form
|
||||
await expect(page.getByLabel(/username/i)).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('should have descriptive content on landing page', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check if landing page has meaningful content
|
||||
const bodyText = await page.textContent('body');
|
||||
expect(bodyText).toBeTruthy();
|
||||
expect(bodyText!.length).toBeGreaterThan(100);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Login Interface', () => {
|
||||
test('should have username and password fields', async ({ page }) => {
|
||||
await navigateToLogin(page);
|
||||
|
||||
// Check for form elements
|
||||
await expect(page.getByLabel(/username/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/password/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('should have submit button', async ({ page }) => {
|
||||
await navigateToLogin(page);
|
||||
|
||||
// Check for login button
|
||||
await expect(page.getByRole('button', { name: /login|sign in/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
49
e2e/login.spec.ts
Normal file
49
e2e/login.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
// Helper function to navigate to login page
|
||||
async function navigateToLogin(page: any) {
|
||||
await page.goto('/');
|
||||
// Click "Sign In" button to navigate to login page
|
||||
await page.getByRole('button', { name: /sign in|get started/i }).first().click();
|
||||
// Wait for login form to appear
|
||||
await page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
test.describe('Login functionality', () => {
|
||||
test('should display login form after navigating from landing page', async ({ page }) => {
|
||||
await navigateToLogin(page);
|
||||
|
||||
// Check if login form is visible
|
||||
await expect(page.getByLabel(/username/i)).toBeVisible({ timeout: 5000 });
|
||||
await expect(page.getByLabel(/password/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /login|sign in/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show error on invalid credentials', async ({ page }) => {
|
||||
await navigateToLogin(page);
|
||||
|
||||
// Try to login with invalid credentials
|
||||
await page.getByLabel(/username/i).fill('invaliduser');
|
||||
await page.getByLabel(/password/i).fill('wrongpassword');
|
||||
await page.getByRole('button', { name: /login|sign in/i }).click();
|
||||
|
||||
// Check for error message or notification
|
||||
await expect(page.getByText(/invalid|error/i)).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
test('should have register/sign up option', async ({ page }) => {
|
||||
await navigateToLogin(page);
|
||||
|
||||
// Check if there's a register or sign up option available
|
||||
const hasRegister = await page.getByText(/register|sign up|create account/i).count();
|
||||
expect(hasRegister).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should have back button to return to landing', async ({ page }) => {
|
||||
await navigateToLogin(page);
|
||||
|
||||
// Check if there's a back or return button
|
||||
const backButton = page.getByRole('button', { name: /back|return/i }).first();
|
||||
await expect(backButton).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
71
e2e/smoke.spec.ts
Normal file
71
e2e/smoke.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Basic Smoke Tests', () => {
|
||||
test('should load the application', async ({ page }) => {
|
||||
// Navigate to the app
|
||||
await page.goto('/');
|
||||
|
||||
// Check if page loads without critical errors
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Verify the page has loaded some content
|
||||
const bodyText = await page.textContent('body');
|
||||
expect(bodyText).toBeTruthy();
|
||||
expect(bodyText!.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should have proper page title', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Check if title is set
|
||||
const title = await page.title();
|
||||
expect(title).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should display MetaBuilder landing page', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Check if the page has loaded
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
// Check if navigation buttons are present (more reliable than text search)
|
||||
await expect(page.getByRole('button', { name: /sign in|get started/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('should not have critical console errors on load', async ({ page }) => {
|
||||
const consoleErrors: string[] = [];
|
||||
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') {
|
||||
consoleErrors.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Filter out known acceptable errors
|
||||
const criticalErrors = consoleErrors.filter(err =>
|
||||
!err.includes('favicon') &&
|
||||
!err.includes('Chrome extension') &&
|
||||
!err.includes('Failed to load resource') && // Network errors are not critical for UI testing
|
||||
!err.includes('403') &&
|
||||
!err.includes('ERR_NAME_NOT_RESOLVED')
|
||||
);
|
||||
|
||||
// Should have no critical console errors (application logic errors)
|
||||
expect(
|
||||
criticalErrors,
|
||||
`Console errors found: ${criticalErrors.join('\n')}`
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
test('should have viewport properly configured', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const viewport = page.viewportSize();
|
||||
expect(viewport).toBeTruthy();
|
||||
expect(viewport!.width).toBeGreaterThan(0);
|
||||
expect(viewport!.height).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
45
eslint.config.js
Normal file
45
eslint.config.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist', 'node_modules', 'packages/*/dist', 'packages/*/node_modules'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
// Strict type checking rules (as warnings for gradual adoption)
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': ['warn', {
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
}],
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-misused-promises': 'warn',
|
||||
// Code quality rules
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
'no-debugger': 'error',
|
||||
'prefer-const': 'error',
|
||||
'no-var': 'error',
|
||||
},
|
||||
},
|
||||
)
|
||||
1545
package-lock.json
generated
1545
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -8,8 +8,17 @@
|
||||
"kill": "fuser -k 5000/tcp",
|
||||
"build": "tsc -b --noCheck && vite build",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"optimize": "vite optimize",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"act": "bash scripts/run-act.sh",
|
||||
"act:lint": "bash scripts/run-act.sh -w ci.yml -j lint",
|
||||
"act:e2e": "bash scripts/run-act.sh -w ci.yml -j test-e2e",
|
||||
"setup-packages": "node scripts/setup-packages.cjs",
|
||||
"postinstall": "node scripts/setup-packages.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@github/spark": ">=0.43.1 <1",
|
||||
@@ -46,7 +55,7 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-query": "^5.83.1",
|
||||
"@types/jszip": "^3.4.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -63,24 +72,25 @@
|
||||
"lucide-react": "^0.484.0",
|
||||
"marked": "^17.0.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"octokit": "^4.1.2",
|
||||
"octokit": "^5.0.5",
|
||||
"react": "^19.0.0",
|
||||
"react-day-picker": "^9.6.7",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-hook-form": "^7.69.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"recharts": "^2.15.1",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"three": "^0.175.0",
|
||||
"tw-animate-css": "^1.2.4",
|
||||
"uuid": "^11.1.0",
|
||||
"uuid": "^13.0.0",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@playwright/test": "^1.57.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
|
||||
61
packages/README.md
Normal file
61
packages/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Packages Folder
|
||||
|
||||
This folder contains modular packages for the MetaBuilder application. Each package is self-contained with its own components, metadata, and examples.
|
||||
|
||||
## Structure
|
||||
|
||||
Each package follows this structure:
|
||||
|
||||
```
|
||||
packages/
|
||||
├── package_name/
|
||||
│ ├── seed/
|
||||
│ │ ├── components.json # Component definitions
|
||||
│ │ ├── metadata.json # Package metadata
|
||||
│ │ └── scripts/ # Optional Lua scripts
|
||||
│ └── static_content/
|
||||
│ └── examples.json # Optional usage examples
|
||||
```
|
||||
|
||||
## Available Packages
|
||||
|
||||
- **admin_dialog**: Admin dialog components for management interfaces
|
||||
- **data_table**: Data table components for displaying tabular data
|
||||
- **form_builder**: Form builder components for creating dynamic forms
|
||||
- **nav_menu**: Navigation menu components
|
||||
- **dashboard**: Dashboard layout components
|
||||
- **notification_center**: Notification center components
|
||||
|
||||
## Package Metadata Format
|
||||
|
||||
Each `metadata.json` file should contain:
|
||||
|
||||
```json
|
||||
{
|
||||
"packageId": "package_name",
|
||||
"name": "Display Name",
|
||||
"version": "1.0.0",
|
||||
"description": "Package description",
|
||||
"author": "Author name",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Components Format
|
||||
|
||||
Each `components.json` file should contain an array of component definitions.
|
||||
|
||||
## Development
|
||||
|
||||
The main application imports from these packages via relative paths in `src/lib/package-glue.ts`.
|
||||
|
||||
To add a new package:
|
||||
|
||||
1. Run `npm run setup-packages <package-name>` to create the package structure
|
||||
2. Add optional `static_content/examples.json` if needed
|
||||
3. Update `src/lib/package-glue.ts` to import the new package
|
||||
4. Commit the new package files to the repository
|
||||
1
packages/admin_dialog/seed/components.json
Normal file
1
packages/admin_dialog/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
12
packages/admin_dialog/seed/metadata.json
Normal file
12
packages/admin_dialog/seed/metadata.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageId": "admin_dialog",
|
||||
"name": "Admin Dialog",
|
||||
"version": "1.0.0",
|
||||
"description": "Admin dialog components",
|
||||
"author": "MetaBuilder",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
1
packages/admin_dialog/static_content/examples.json
Normal file
1
packages/admin_dialog/static_content/examples.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
packages/dashboard/seed/components.json
Normal file
1
packages/dashboard/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
12
packages/dashboard/seed/metadata.json
Normal file
12
packages/dashboard/seed/metadata.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageId": "dashboard",
|
||||
"name": "Dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "Dashboard components",
|
||||
"author": "MetaBuilder",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
1
packages/data_table/seed/components.json
Normal file
1
packages/data_table/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
12
packages/data_table/seed/metadata.json
Normal file
12
packages/data_table/seed/metadata.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageId": "data_table",
|
||||
"name": "Data Table",
|
||||
"version": "1.0.0",
|
||||
"description": "Data table components",
|
||||
"author": "MetaBuilder",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
1
packages/data_table/static_content/examples.json
Normal file
1
packages/data_table/static_content/examples.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
packages/form_builder/seed/components.json
Normal file
1
packages/form_builder/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
12
packages/form_builder/seed/metadata.json
Normal file
12
packages/form_builder/seed/metadata.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageId": "form_builder",
|
||||
"name": "Form Builder",
|
||||
"version": "1.0.0",
|
||||
"description": "Form builder components",
|
||||
"author": "MetaBuilder",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
1
packages/form_builder/static_content/examples.json
Normal file
1
packages/form_builder/static_content/examples.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
packages/nav_menu/seed/components.json
Normal file
1
packages/nav_menu/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
12
packages/nav_menu/seed/metadata.json
Normal file
12
packages/nav_menu/seed/metadata.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageId": "nav_menu",
|
||||
"name": "Navigation Menu",
|
||||
"version": "1.0.0",
|
||||
"description": "Navigation menu components",
|
||||
"author": "MetaBuilder",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
1
packages/notification_center/seed/components.json
Normal file
1
packages/notification_center/seed/components.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
||||
12
packages/notification_center/seed/metadata.json
Normal file
12
packages/notification_center/seed/metadata.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"packageId": "notification_center",
|
||||
"name": "Notification Center",
|
||||
"version": "1.0.0",
|
||||
"description": "Notification center components",
|
||||
"author": "MetaBuilder",
|
||||
"category": "ui",
|
||||
"dependencies": [],
|
||||
"exports": {
|
||||
"components": []
|
||||
}
|
||||
}
|
||||
42
playwright.config.ts
Normal file
42
playwright.config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: 'http://localhost:5000',
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run dev',
|
||||
url: 'http://localhost:5000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 300 * 1000,
|
||||
},
|
||||
});
|
||||
125
scripts/README.md
Normal file
125
scripts/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Scripts Directory
|
||||
|
||||
This directory contains utility scripts for development and testing.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
### `run-act.sh`
|
||||
|
||||
Run GitHub Actions workflows locally using [act](https://github.com/nektos/act).
|
||||
|
||||
**Prerequisites:**
|
||||
- Docker installed and running
|
||||
- `act` CLI tool installed
|
||||
|
||||
**Installing act:**
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew)
|
||||
brew install act
|
||||
|
||||
# Linux (curl)
|
||||
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
|
||||
|
||||
# Windows (Chocolatey)
|
||||
choco install act-cli
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# Run default CI workflow
|
||||
npm run act
|
||||
|
||||
# Or directly:
|
||||
./scripts/run-act.sh
|
||||
|
||||
# Run specific workflow
|
||||
./scripts/run-act.sh -w ci.yml
|
||||
|
||||
# Run only a specific job
|
||||
./scripts/run-act.sh -w ci.yml -j lint
|
||||
./scripts/run-act.sh -w ci.yml -j test-e2e
|
||||
|
||||
# Simulate different events
|
||||
./scripts/run-act.sh -e pull_request
|
||||
|
||||
# List available workflows
|
||||
./scripts/run-act.sh -l
|
||||
|
||||
# Show help
|
||||
./scripts/run-act.sh -h
|
||||
```
|
||||
|
||||
**Common Use Cases:**
|
||||
|
||||
1. **Test CI pipeline before pushing:**
|
||||
```bash
|
||||
npm run act
|
||||
```
|
||||
|
||||
2. **Debug e2e test failures:**
|
||||
```bash
|
||||
./scripts/run-act.sh -w ci.yml -j test-e2e
|
||||
```
|
||||
|
||||
3. **Test lint fixes:**
|
||||
```bash
|
||||
./scripts/run-act.sh -w ci.yml -j lint
|
||||
```
|
||||
|
||||
4. **Simulate PR checks:**
|
||||
```bash
|
||||
./scripts/run-act.sh -e pull_request
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- First run will be slow as Docker images are downloaded
|
||||
- Act runs workflows in Docker containers that simulate GitHub Actions runners
|
||||
- Some features may not work exactly like GitHub Actions (e.g., certain actions, secrets)
|
||||
- Check `.actrc` or pass `-P` flag to customize Docker images used
|
||||
|
||||
**Troubleshooting:**
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. **Docker not running:**
|
||||
```bash
|
||||
# Make sure Docker is running
|
||||
docker ps
|
||||
```
|
||||
|
||||
2. **Permission issues:**
|
||||
```bash
|
||||
# Make sure script is executable
|
||||
chmod +x scripts/run-act.sh
|
||||
```
|
||||
|
||||
3. **Out of disk space:**
|
||||
```bash
|
||||
# Clean up Docker images
|
||||
docker system prune -a
|
||||
```
|
||||
|
||||
4. **Workflow doesn't run:**
|
||||
```bash
|
||||
# List workflows to verify name
|
||||
./scripts/run-act.sh -l
|
||||
```
|
||||
|
||||
### `setup-packages.cjs`
|
||||
|
||||
Sets up the package system for the project. This script is automatically run during `postinstall`.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
npm run setup-packages
|
||||
```
|
||||
|
||||
## Adding New Scripts
|
||||
|
||||
When adding new scripts:
|
||||
1. Make them executable: `chmod +x scripts/your-script.sh`
|
||||
2. Add appropriate help/usage information
|
||||
3. Document them in this README
|
||||
4. Consider adding npm script aliases in `package.json`
|
||||
143
scripts/run-act.sh
Executable file
143
scripts/run-act.sh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to run GitHub Actions workflows locally using act
|
||||
# https://github.com/nektos/act
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}GitHub Actions Local Runner (act)${NC}"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# Check if act is installed
|
||||
if ! command -v act &> /dev/null; then
|
||||
echo -e "${RED}Error: 'act' is not installed.${NC}"
|
||||
echo ""
|
||||
echo "Install act using one of these methods:"
|
||||
echo ""
|
||||
echo " macOS (Homebrew):"
|
||||
echo " brew install act"
|
||||
echo ""
|
||||
echo " Linux (using curl):"
|
||||
echo " curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash"
|
||||
echo ""
|
||||
echo " Windows (using Chocolatey):"
|
||||
echo " choco install act-cli"
|
||||
echo ""
|
||||
echo " Or via GitHub releases:"
|
||||
echo " https://github.com/nektos/act/releases"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Default values
|
||||
WORKFLOW="ci.yml"
|
||||
JOB=""
|
||||
EVENT="push"
|
||||
PLATFORM=""
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-w|--workflow)
|
||||
WORKFLOW="$2"
|
||||
shift 2
|
||||
;;
|
||||
-j|--job)
|
||||
JOB="$2"
|
||||
shift 2
|
||||
;;
|
||||
-e|--event)
|
||||
EVENT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-p|--platform)
|
||||
PLATFORM="$2"
|
||||
shift 2
|
||||
;;
|
||||
-l|--list)
|
||||
echo "Available workflows:"
|
||||
ls -1 .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | sed 's|.github/workflows/||'
|
||||
echo ""
|
||||
echo "To run a workflow:"
|
||||
echo " $0 -w ci.yml"
|
||||
echo ""
|
||||
echo "To list jobs in a workflow:"
|
||||
echo " act -l -W .github/workflows/ci.yml"
|
||||
exit 0
|
||||
;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -w, --workflow <file> Workflow file to run (default: ci.yml)"
|
||||
echo " -j, --job <name> Specific job to run (runs all jobs if not specified)"
|
||||
echo " -e, --event <event> Event type to simulate (default: push)"
|
||||
echo " -p, --platform <image> Docker platform/image to use"
|
||||
echo " -l, --list List available workflows"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Run default CI workflow"
|
||||
echo " $0 -w ci.yml -j lint # Run only the lint job"
|
||||
echo " $0 -w ci.yml -j test-e2e # Run only e2e tests"
|
||||
echo " $0 -e pull_request # Simulate a pull request event"
|
||||
echo " $0 -p catthehacker/ubuntu:act-latest # Use specific Docker image"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown option: $1${NC}"
|
||||
echo "Use -h or --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if workflow file exists
|
||||
WORKFLOW_PATH=".github/workflows/${WORKFLOW}"
|
||||
if [ ! -f "$WORKFLOW_PATH" ]; then
|
||||
echo -e "${RED}Error: Workflow file not found: $WORKFLOW_PATH${NC}"
|
||||
echo ""
|
||||
echo "Available workflows:"
|
||||
ls -1 .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null | sed 's|.github/workflows/||'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build act command
|
||||
ACT_CMD="act $EVENT -W $WORKFLOW_PATH"
|
||||
|
||||
if [ -n "$JOB" ]; then
|
||||
ACT_CMD="$ACT_CMD -j $JOB"
|
||||
fi
|
||||
|
||||
if [ -n "$PLATFORM" ]; then
|
||||
ACT_CMD="$ACT_CMD -P ubuntu-latest=$PLATFORM"
|
||||
fi
|
||||
|
||||
# Add verbose flag for better debugging
|
||||
ACT_CMD="$ACT_CMD --verbose"
|
||||
|
||||
echo -e "${YELLOW}Running workflow: $WORKFLOW${NC}"
|
||||
if [ -n "$JOB" ]; then
|
||||
echo -e "${YELLOW}Job: $JOB${NC}"
|
||||
fi
|
||||
echo -e "${YELLOW}Event: $EVENT${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Command: $ACT_CMD${NC}"
|
||||
echo ""
|
||||
echo "Note: This will run in Docker containers and may take a while on first run."
|
||||
echo "Press Ctrl+C to cancel."
|
||||
echo ""
|
||||
|
||||
# Run act
|
||||
eval $ACT_CMD
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Done!${NC}"
|
||||
168
scripts/setup-packages.cjs
Executable file
168
scripts/setup-packages.cjs
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Setup script for creating package folder structure
|
||||
* Usage:
|
||||
* node scripts/setup-packages.cjs <package-name> - Create a specific package
|
||||
* node scripts/setup-packages.cjs - Verify all required packages exist
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const packagesDir = path.join(__dirname, '..', 'packages');
|
||||
|
||||
// Get package name from command line argument
|
||||
const packageName = process.argv[2];
|
||||
|
||||
// Package definitions
|
||||
const packageTemplates = {
|
||||
'admin_dialog': {
|
||||
id: 'admin_dialog',
|
||||
name: 'Admin Dialog',
|
||||
description: 'Admin dialog components',
|
||||
hasExamples: true
|
||||
},
|
||||
'data_table': {
|
||||
id: 'data_table',
|
||||
name: 'Data Table',
|
||||
description: 'Data table components',
|
||||
hasExamples: true
|
||||
},
|
||||
'form_builder': {
|
||||
id: 'form_builder',
|
||||
name: 'Form Builder',
|
||||
description: 'Form builder components',
|
||||
hasExamples: true
|
||||
},
|
||||
'nav_menu': {
|
||||
id: 'nav_menu',
|
||||
name: 'Navigation Menu',
|
||||
description: 'Navigation menu components',
|
||||
hasExamples: false
|
||||
},
|
||||
'dashboard': {
|
||||
id: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
description: 'Dashboard components',
|
||||
hasExamples: false
|
||||
},
|
||||
'notification_center': {
|
||||
id: 'notification_center',
|
||||
name: 'Notification Center',
|
||||
description: 'Notification center components',
|
||||
hasExamples: false
|
||||
}
|
||||
};
|
||||
|
||||
function createPackage(pkg) {
|
||||
const pkgDir = path.join(packagesDir, pkg.id);
|
||||
const seedDir = path.join(pkgDir, 'seed');
|
||||
|
||||
// Create directories
|
||||
if (!fs.existsSync(packagesDir)) {
|
||||
fs.mkdirSync(packagesDir, { recursive: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(seedDir)) {
|
||||
fs.mkdirSync(seedDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Create components.json
|
||||
const componentsPath = path.join(seedDir, 'components.json');
|
||||
if (!fs.existsSync(componentsPath)) {
|
||||
fs.writeFileSync(componentsPath, '[]', 'utf8');
|
||||
}
|
||||
|
||||
// Create metadata.json
|
||||
const metadataPath = path.join(seedDir, 'metadata.json');
|
||||
if (!fs.existsSync(metadataPath)) {
|
||||
const metadata = {
|
||||
packageId: pkg.id,
|
||||
name: pkg.name,
|
||||
version: '1.0.0',
|
||||
description: pkg.description,
|
||||
author: 'MetaBuilder',
|
||||
category: 'ui',
|
||||
dependencies: [],
|
||||
exports: {
|
||||
components: []
|
||||
}
|
||||
};
|
||||
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf8');
|
||||
}
|
||||
|
||||
// Create examples.json if needed
|
||||
if (pkg.hasExamples) {
|
||||
const staticDir = path.join(pkgDir, 'static_content');
|
||||
if (!fs.existsSync(staticDir)) {
|
||||
fs.mkdirSync(staticDir, { recursive: true });
|
||||
}
|
||||
|
||||
const examplesPath = path.join(staticDir, 'examples.json');
|
||||
if (!fs.existsSync(examplesPath)) {
|
||||
fs.writeFileSync(examplesPath, '{}', 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✓ Created ${pkg.name} package`);
|
||||
}
|
||||
|
||||
// If a specific package name is provided
|
||||
if (packageName) {
|
||||
const pkg = packageTemplates[packageName];
|
||||
|
||||
if (!pkg) {
|
||||
console.error(`Error: Unknown package '${packageName}'`);
|
||||
console.log('\nAvailable packages:');
|
||||
Object.keys(packageTemplates).forEach(key => {
|
||||
console.log(` - ${key}`);
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if package already exists
|
||||
const pkgDir = path.join(packagesDir, pkg.id);
|
||||
if (fs.existsSync(pkgDir)) {
|
||||
console.log(`✓ Package '${pkg.name}' already exists`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Creating package: ${pkg.name}...\n`);
|
||||
createPackage(pkg);
|
||||
console.log('\n✅ Package created successfully!');
|
||||
} else {
|
||||
// No package name provided - verification mode (postinstall or manual check)
|
||||
// Verify that all required packages exist
|
||||
const requiredPackages = Object.keys(packageTemplates);
|
||||
const missingPackages = [];
|
||||
|
||||
if (!fs.existsSync(packagesDir)) {
|
||||
console.error('Error: packages folder does not exist!');
|
||||
console.log('Run this script with a package name to create packages.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const pkgId of requiredPackages) {
|
||||
const componentsPath = path.join(packagesDir, pkgId, 'seed', 'components.json');
|
||||
const metadataPath = path.join(packagesDir, pkgId, 'seed', 'metadata.json');
|
||||
|
||||
if (!fs.existsSync(componentsPath) || !fs.existsSync(metadataPath)) {
|
||||
missingPackages.push(pkgId);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingPackages.length > 0) {
|
||||
console.error('Error: Missing required packages:', missingPackages.join(', '));
|
||||
console.log('\nCreate missing packages with:');
|
||||
missingPackages.forEach(pkg => {
|
||||
console.log(` npm run setup-packages ${pkg}`);
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✓ All required packages exist and are committed to the repository.');
|
||||
}
|
||||
|
||||
@@ -173,7 +173,9 @@ export function ComponentConfigDialog({ node, isOpen, onClose, onSave, nerdMode
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setProps(JSON.parse(e.target.value))
|
||||
} catch {}
|
||||
} catch {
|
||||
// Ignore invalid JSON during typing
|
||||
}
|
||||
}}
|
||||
className="font-mono text-xs"
|
||||
rows={6}
|
||||
@@ -208,7 +210,9 @@ export function ComponentConfigDialog({ node, isOpen, onClose, onSave, nerdMode
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setStyles(JSON.parse(e.target.value))
|
||||
} catch {}
|
||||
} catch {
|
||||
// Ignore invalid JSON during typing
|
||||
}
|
||||
}}
|
||||
className="font-mono text-xs"
|
||||
rows={12}
|
||||
@@ -256,7 +260,9 @@ export function ComponentConfigDialog({ node, isOpen, onClose, onSave, nerdMode
|
||||
onChange={(e) => {
|
||||
try {
|
||||
setEvents(JSON.parse(e.target.value))
|
||||
} catch {}
|
||||
} catch {
|
||||
// Ignore invalid JSON during typing
|
||||
}
|
||||
}}
|
||||
className="font-mono text-xs"
|
||||
rows={6}
|
||||
|
||||
@@ -133,7 +133,7 @@ export function FieldRenderer({ field, value, onChange, error, schema, currentAp
|
||||
</Select>
|
||||
)
|
||||
|
||||
case 'relation':
|
||||
case 'relation': {
|
||||
if (!relatedModel || !relatedModelRecords || relatedModelRecords.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground p-2 border border-dashed rounded">
|
||||
@@ -158,6 +158,7 @@ export function FieldRenderer({ field, value, onChange, error, schema, currentAp
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
|
||||
case 'json':
|
||||
return (
|
||||
|
||||
@@ -121,9 +121,10 @@ export function ModelListView({ model, schema, currentApp }: ModelListViewProps)
|
||||
case 'datetime':
|
||||
return new Date(value).toLocaleString()
|
||||
|
||||
case 'select':
|
||||
case 'select': {
|
||||
const choice = field.choices?.find(c => c.value === value)
|
||||
return <Badge variant="secondary">{choice?.label || value}</Badge>
|
||||
}
|
||||
|
||||
case 'relation':
|
||||
return (
|
||||
|
||||
@@ -134,7 +134,7 @@ export function RenderComponent({ component, isSelected, onSelect, user, context
|
||||
case 'Label':
|
||||
return <Label>{props.children || 'Label'}</Label>
|
||||
|
||||
case 'Heading':
|
||||
case 'Heading': {
|
||||
const level = props.level || '1'
|
||||
const className = props.className
|
||||
const text = props.children || 'Heading'
|
||||
@@ -144,6 +144,7 @@ export function RenderComponent({ component, isSelected, onSelect, user, context
|
||||
if (level === '3') return <h3 className={className}>{text}</h3>
|
||||
if (level === '4') return <h4 className={className}>{text}</h4>
|
||||
return <h1 className={className}>{text}</h1>
|
||||
}
|
||||
|
||||
case 'Text':
|
||||
return (
|
||||
|
||||
@@ -32,7 +32,8 @@ export class LuaEngine {
|
||||
}
|
||||
|
||||
private setupContextAPI() {
|
||||
const self = this
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const _self = this
|
||||
|
||||
const logFunction = function(L: any) {
|
||||
const nargs = lua.lua_gettop(L)
|
||||
@@ -50,7 +51,7 @@ export class LuaEngine {
|
||||
}
|
||||
}
|
||||
|
||||
self.logs.push(messages.join(' '))
|
||||
_self.logs.push(messages.join(' '))
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ export class LuaEngine {
|
||||
}
|
||||
}
|
||||
|
||||
self.logs.push(messages.join('\t'))
|
||||
_self.logs.push(messages.join('\t'))
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ const LUA_MALICIOUS_PATTERNS = [
|
||||
recommendation: 'Use with extreme caution'
|
||||
},
|
||||
{
|
||||
pattern: /\.\.\s*[\[\]]/gi,
|
||||
pattern: /\.\.\s*[[\]]/gi,
|
||||
type: 'suspicious' as const,
|
||||
severity: 'medium' as const,
|
||||
message: 'Potential Lua table manipulation',
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
"src",
|
||||
"packages",
|
||||
"e2e",
|
||||
"playwright.config.ts",
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user