diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000..41344e9cb --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,198 @@ +# GitHub Actions Workflows + +This directory contains GitHub Actions workflows for automated CI/CD tasks. + +## Available Workflows + +### 1. Production Release (`release.yml`) + +**Trigger:** Automatically runs on every push to the `master` branch + +**Purpose:** Creates a production-ready release package of InvoicePlane v2 and publishes it as a GitHub Release + +**What it does:** +1. **Downloads translations from Crowdin** - Retrieves the latest translations +2. **Builds frontend assets** - Runs `yarn install --frozen-lockfile && yarn build` +3. **Installs PHP dependencies** - Runs `composer install --no-dev` for production +4. **Cleans up node_modules** - Removes Node.js dependencies +5. **Optimizes vendor directory** - Removes unnecessary files (tests, docs, etc.) +6. **Creates release archive** - Packages everything into a timestamped ZIP file +7. **Generates version tag** - Creates a new version tag (alpha/beta/stable) +8. **Creates GitHub Release** - Publishes release with changelog and artifacts + +**Release Types:** + +The workflow supports configurable release types (set in workflow file): +- `alpha` - Pre-release versions (increments patch, adds -alpha suffix) +- `beta` - Beta versions (increments patch, adds -beta suffix) +- `stable` - Stable releases (increments minor version) + +To change the release type, edit the `RELEASE_TYPE` environment variable at the top of `release.yml`. + +**Versioning:** + +The workflow automatically: +- Detects the latest tag (or starts from v0.0.0) +- Increments version based on release type +- Creates a new tag (e.g., v0.1.0-alpha, v0.2.0-beta, v1.0.0) +- Generates release notes showing changes since the previous tag + +**Security:** + +The workflow uses minimal permissions: +- `contents: write` - Required for creating releases and tags +- `actions: write` - Required for uploading workflow artifacts + +**Required Secrets:** + +Before using this workflow, you need to configure these GitHub secrets: + +- `CROWDIN_PROJECT_ID` - Your Crowdin project ID +- `CROWDIN_PERSONAL_TOKEN` - Your Crowdin personal access token + +To add these secrets: +1. Go to your repository Settings +2. Navigate to Secrets and variables → Actions +3. Click "New repository secret" +4. Add each secret with its corresponding value + +**Crowdin Setup:** + +To get your Crowdin credentials: +1. Log in to [Crowdin](https://crowdin.com/) +2. Navigate to your InvoicePlane project +3. Go to Settings → API +4. Generate a Personal Access Token +5. Copy your Project ID from the project settings + +**Accessing Releases:** + +After the workflow runs: +1. Go to the **Releases** section of your repository +2. Find the latest release (e.g., "Release v0.1.0-alpha") +3. Download the ZIP file and checksums from the release assets +4. Review the automated changelog + +Artifacts are also available in the Actions tab for 90 days. + +### 2. PHPUnit Tests (`phpunit.yml`) + +**Trigger:** Manual dispatch only + +Runs the PHPUnit test suite against a MySQL database. + +### 3. Laravel Pint (`pint.yml`) + +**Trigger:** Manual dispatch only + +Runs Laravel Pint for code formatting checks. + +### 4. PHPStan (`phpstan.yml`) + +**Trigger:** Manual dispatch only + +Runs PHPStan static analysis. + +### 5. Docker Compose Check (`docker.yml`) + +**Trigger:** Manual dispatch only + +Tests Docker Compose configuration. + +### 6. Quickstart (`quickstart.yml`) + +**Trigger:** Manual dispatch only + +Provides a quick setup for development environments. + +## Workflow Optimization + +### Vendor Directory Cleanup + +The release workflow aggressively cleans the vendor directory to minimize file size: + +- Removes all test directories (`tests`, `Tests`, `test`, `Test`) +- Removes all documentation (`docs`, `doc`, `*.md`, `*.txt`) +- Removes all Git metadata (`.git`, `.gitignore`, `.gitattributes`) +- Removes build files (`composer.json`, `composer.lock`, `phpunit.xml`, etc.) +- Removes code quality files (`.php_cs`, `phpstan.neon`, etc.) + +This typically reduces the vendor directory size by 40-60%. + +### ZIP Exclusions + +The following files and directories are excluded from the release archive: + +- Development files: `.github/*`, `tests/*`, `README.md` +- Configuration files: `phpunit.xml`, `phpstan.neon`, `pint.json`, `rector.php` +- Build tools: `package.json`, `yarn.lock`, `vite.config.js`, `tailwind.config.js` +- Docker files: `docker-compose.yml` +- Environment files: `.env*` +- Storage: `storage/logs/*`, `storage/framework/cache/*` +- Node modules: `node_modules/*` (already removed in cleanup step) + +## Troubleshooting + +### Crowdin Download Fails + +If the Crowdin step fails, check: +1. Secrets are correctly configured +2. Your Crowdin personal token has not expired +3. The project ID is correct +4. Your Crowdin project is properly configured + +### Build Fails + +If the frontend build fails: +1. Ensure `package.json` is up to date +2. Check for syntax errors in Vite/Tailwind config +3. Verify all dependencies are correctly specified + +### Composer Install Fails + +If Composer installation fails: +1. Check `composer.json` for syntax errors +2. Ensure all required PHP extensions are available +3. Verify package versions are compatible + +## Customization + +### Changing PHP Version + +Edit line 49 in `release.yml`: +```yaml +php-version: '8.3' # Using 8.3 for latest features; composer.json requires ^8.2 +``` + +### Changing Node.js Version + +Edit line 36 in `release.yml`: +```yaml +node-version: '20' # Change to your desired version +``` + +### Adjusting Artifact Retention + +Edit line 121 in `release.yml`: +```yaml +retention-days: 90 # Change to your desired retention period (1-90 days) +``` + +### Custom ZIP Exclusions + +Add or remove exclusions in the "Create release zip" step (lines 86-110). + +## Best Practices + +1. **Test locally first** - Before relying on the workflow, test the build process locally +2. **Monitor workflow runs** - Check the Actions tab regularly for failures +3. **Keep secrets secure** - Never commit secrets to the repository +4. **Update dependencies** - Keep GitHub Actions and dependencies up to date +5. **Tag releases** - Use semantic versioning for production releases + +## Support + +For issues or questions about these workflows: +- Create an issue in the repository +- Join the [Community Forums](https://community.invoiceplane.com) +- Visit the [Discord server](https://discord.gg/PPzD2hTrXt) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..b172fcfaa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,314 @@ +name: Build Production Release + +on: + push: + branches: + - master + +env: + RELEASE_TYPE: 'alpha' # Options: alpha, beta, stable + +jobs: + build-release: + name: Build and Package Production Release + runs-on: ubuntu-latest + + permissions: + contents: write # Required for creating releases and tags + actions: write # Required for uploading artifacts + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 # Fetch only last commit for faster checkout + fetch-tags: true # Fetch tags for versioning + + # Step 1: Download translations from Crowdin + - name: Download translations from Crowdin + uses: crowdin/github-action@v2 + with: + download_translations: true + localization_branch_name: master + create_pull_request: false + crowdin_branch_name: master + config: 'crowdin.yml' + env: + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + # Step 2: Validate translations + - name: Validate translations directory + run: | + echo "Validating translations structure..." + if [ ! -d "resources/lang/en" ]; then + echo "ERROR: Missing required language directory: resources/lang/en" + exit 1 + fi + echo "✓ Translations structure validated" + + # Step 3: Set up Node.js for frontend build + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'yarn' + + - name: Install frontend dependencies (production mode) + run: | + echo "Installing frontend dependencies..." + yarn install --frozen-lockfile --production=false + echo "✓ Frontend dependencies installed" + + - name: Build frontend assets for production + run: | + echo "Building frontend assets..." + yarn build + echo "✓ Frontend assets built successfully" + + # Step 4: Set up PHP and Composer + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: gd, bcmath, dom, intl, xml, zip, mbstring, pdo_mysql + coverage: none + + - name: Display tool versions + run: | + echo "=========================================" + echo "Tool Versions" + echo "=========================================" + echo "PHP version: $(php -v | head -n 1)" + echo "Composer version: $(composer --version)" + echo "Node.js version: $(node -v)" + echo "npm version: $(npm -v)" + echo "Yarn version: $(yarn -v)" + echo "=========================================" + + - name: Install Composer dependencies (production) + run: | + echo "Installing Composer dependencies (production mode)..." + composer install --no-dev --optimize-autoloader \ + --no-interaction --prefer-dist + echo "✓ Composer dependencies installed" + + - name: Install vendor cleaner and optimize + run: | + echo "Installing composer-vendor-cleaner..." + composer require liborm85/composer-vendor-cleaner --dev \ + --no-interaction --no-update + composer update liborm85/composer-vendor-cleaner --no-interaction + echo "Running vendor cleanup..." + composer vendor-cleaner + echo "✓ Vendor directory optimized" + + # Step 5: Cleanup workspace + - name: Clean workspace + run: | + echo "=========================================" + echo "Cleaning Workspace" + echo "=========================================" + + echo "Removing npm dependencies (node_modules)..." + rm -rf node_modules + + echo "Removing .DS_Store files..." + find . -type f -name '.DS_Store' -delete + + echo "Cleaning mPDF fonts (keeping only DejaVu)..." + find vendor/mpdf/mpdf/ttfonts -type f ! -name "DejaVu*" \ + -delete 2>/dev/null || true + + echo "Cleaning mPDF QR code data..." + rm -rf vendor/mpdf/mpdf/src/QrCode/data/* 2>/dev/null || true + + echo "Removing .git directories from vendor..." + find vendor -name '.git' -type d -exec rm -rf {} + 2>/dev/null || true + + echo "Removing .github directories..." + rm -rf .github + + echo "✓ Workspace cleaned successfully" + echo "=========================================" + + # Step 6: Create release archive + - name: Package InvoicePlane + run: | + echo "=========================================" + echo "Creating Release Package" + echo "=========================================" + + # Create a timestamp for the release + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + RELEASE_NAME="invoiceplane-v2-${TIMESTAMP}" + RELEASE_FILE="${RELEASE_NAME}.zip" + + echo "Package name: ${RELEASE_FILE}" + + # Create zip excluding unnecessary files + echo "Creating ZIP archive (this may take a moment)..." + zip -q -r -9 "${RELEASE_FILE}" . \ + -x "*.git*" \ + -x "node_modules/*" \ + -x "tests/*" \ + -x ".env*" \ + -x "*.sqlite" \ + -x "storage/logs/*" \ + -x "storage/framework/cache/*" \ + -x "storage/framework/sessions/*" \ + -x "storage/framework/views/*" \ + -x ".phpunit*" \ + -x "phpunit.xml" \ + -x "phpstan.neon" \ + -x "phpstan-baseline.neon" \ + -x "pint.json" \ + -x "rector.php" \ + -x ".editorconfig" \ + -x ".prettierrc" \ + -x "docker-compose.yml" \ + -x "yarn.lock" \ + -x "package.json" \ + -x "vite.config.js" \ + -x "tailwind.config.js" \ + -x ".DS_Store" + + echo "✓ Package created successfully" + + echo "Generating checksums..." + sha256sum "${RELEASE_FILE}" > sha256.txt + md5sum "${RELEASE_FILE}" > md5.txt + + echo "SHA256: $(cat sha256.txt)" + echo "MD5: $(cat md5.txt)" + echo "✓ Checksums generated" + + echo "RELEASE_NAME=${RELEASE_NAME}" >> $GITHUB_ENV + echo "RELEASE_FILE=${RELEASE_FILE}" >> $GITHUB_ENV + + echo "=========================================" + echo "Package Details" + echo "=========================================" + ls -lh "${RELEASE_FILE}" + echo "=========================================" + + # Step 7: Generate version tag and release notes + - name: Generate version tag and release notes + id: version + run: | + echo "=========================================" + echo "Generating Version Tag" + echo "=========================================" + + # Get the latest tag or start with v0.0.0 + PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "Previous tag: ${PREVIOUS_TAG}" + + # Extract version numbers + VERSION=${PREVIOUS_TAG#v} + IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" + + # Increment version based on release type + case "${{ env.RELEASE_TYPE }}" in + alpha) + PATCH=$((PATCH + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}-alpha" + ;; + beta) + PATCH=$((PATCH + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}-beta" + ;; + stable) + MINOR=$((MINOR + 1)) + NEW_VERSION="${MAJOR}.${MINOR}.0" + ;; + *) + echo "ERROR: Invalid RELEASE_TYPE: ${{ env.RELEASE_TYPE }}" + exit 1 + ;; + esac + + NEW_TAG="v${NEW_VERSION}" + echo "New tag: ${NEW_TAG}" + + # Store for later steps + echo "NEW_TAG=${NEW_TAG}" >> $GITHUB_ENV + echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_ENV + echo "new_tag=${NEW_TAG}" >> $GITHUB_OUTPUT + + echo "✓ Version tag generated" + echo "=========================================" + + # Generate release notes between tags + echo "Generating release notes..." + echo "# Release ${NEW_TAG}" > RELEASE_NOTES.md + echo "" >> RELEASE_NOTES.md + echo "**Release Type:** ${{ env.RELEASE_TYPE }}" >> RELEASE_NOTES.md + echo "" >> RELEASE_NOTES.md + + if [ "$PREVIOUS_TAG" = "v0.0.0" ]; then + echo "## Initial Release" >> RELEASE_NOTES.md + echo "" >> RELEASE_NOTES.md + echo "This is the first release of InvoicePlane v2." >> RELEASE_NOTES.md + else + echo "## Changes since ${PREVIOUS_TAG}" >> RELEASE_NOTES.md + echo "" >> RELEASE_NOTES.md + + # Fetch previous tag for comparison + git fetch --depth=1 origin tag ${PREVIOUS_TAG} 2>/dev/null || true + + # Generate changelog + git log ${PREVIOUS_TAG}..HEAD --pretty=format:"- %s (%h)" >> RELEASE_NOTES.md 2>/dev/null || \ + echo "- Initial release" >> RELEASE_NOTES.md + fi + + echo "" >> RELEASE_NOTES.md + echo "---" >> RELEASE_NOTES.md + echo "Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> RELEASE_NOTES.md + + echo "✓ Release notes generated" + cat RELEASE_NOTES.md + + # Step 8: Upload release artifact + - name: Upload release artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.RELEASE_NAME }} + path: | + ${{ env.RELEASE_FILE }} + sha256.txt + md5.txt + RELEASE_NOTES.md + retention-days: 90 + + # Step 9: Create GitHub Release + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.NEW_TAG }} + name: Release ${{ env.NEW_TAG }} + body_path: RELEASE_NOTES.md + files: | + ${{ env.RELEASE_FILE }} + sha256.txt + md5.txt + draft: false + prerelease: ${{ env.RELEASE_TYPE != 'stable' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Step 10: Workflow complete + - name: Workflow summary + run: | + echo "=========================================" + echo "INVOICEPLANE V2 WORKFLOW COMPLETED" + echo "=========================================" + echo "Release: ${{ env.NEW_TAG }}" + echo "Type: ${{ env.RELEASE_TYPE }}" + echo "Package: ${{ env.RELEASE_FILE }}" + echo "Artifact name: ${{ env.RELEASE_NAME }}" + echo "Checksums: sha256.txt, md5.txt" + echo "Release notes: RELEASE_NOTES.md" + echo "=========================================" + echo "GitHub Release created successfully!" + echo "=========================================" diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..75bc9759b --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,7 @@ +project_id_env: CROWDIN_PROJECT_ID +api_token_env: CROWDIN_PERSONAL_TOKEN +preserve_hierarchy: true + +files: + - source: /resources/lang/en/**/*.php + translation: /resources/lang/%language%/**/%original_file_name%