diff --git a/.github/workflows/publish-nightly.yml b/.github/workflows/publish-nightly.yml new file mode 100644 index 000000000..7e1c6ef29 --- /dev/null +++ b/.github/workflows/publish-nightly.yml @@ -0,0 +1,169 @@ +# Nightly builds published to Cloudflare R2 with date versioning (e.g. 1.0.0-nightly-20260205). +# Required repo secrets: R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY (from R2 API token in Cloudflare dashboard). +name: Publish Nightly + +on: + workflow_dispatch: + inputs: + version: + description: 'Semantic version number (e.g., 1.0.0) - nightly suffix will be added automatically' + required: false + type: string + schedule: + # Run at 3:00 AM PST daily (11:00 UTC; PST = UTC-8) + - cron: '0 11 * * *' + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout git repo + uses: actions/checkout@v1 + + - name: Install Node and PNPM + uses: pnpm/action-setup@v4.1.0 + with: + version: 9 + + - name: Install dependencies + run: pnpm install + + - name: Set date-based nightly version + id: version + shell: pwsh + run: | + $inputVersion = "${{ github.event.inputs.version }}" + Write-Host "Input version: $inputVersion" + + if ($inputVersion -eq "" -or $inputVersion -eq "null") { + # No input version provided (scheduled run or manual without input), auto-increment patch version + Write-Host "No version provided, auto-incrementing patch version..." + + $currentVersion = (Get-Content package.json | ConvertFrom-Json).version + Write-Host "Current version: $currentVersion" + + $cleanVersion = $currentVersion -replace '-.*$', '' + $versionParts = $cleanVersion.Split('.') + if ($versionParts.Length -ne 3) { + Write-Error "Current version format is invalid: $cleanVersion" + exit 1 + } + + $major = [int]$versionParts[0] + $minor = [int]$versionParts[1] + $patch = [int]$versionParts[2] + $newPatch = $patch + 1 + $inputVersion = "$major.$minor.$newPatch" + Write-Host "Auto-generated version: $inputVersion" + } else { + # Validate semantic version format (major.minor.patch) + $versionPattern = '^\d+\.\d+\.\d+$' + if ($inputVersion -notmatch $versionPattern) { + Write-Error "Invalid version format. Expected semantic version (e.g., 1.0.0), got: $inputVersion" + exit 1 + } + } + + # Date in YYYYMMDD (PST / America/Los_Angeles) + $pst = [TimeZoneInfo]::FindSystemTimeZoneById('America/Los_Angeles') + $dateInPst = [TimeZoneInfo]::ConvertTimeFromUtc([DateTime]::UtcNow, $pst) + $dateStr = $dateInPst.ToString("yyyyMMdd") + $nightlyVersion = "$inputVersion-nightly-$dateStr" + Write-Host "Nightly version: $nightlyVersion" + + # Update package.json + $packageJson = Get-Content package.json | ConvertFrom-Json + $packageJson.version = $nightlyVersion + $packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json + + echo "version=$nightlyVersion" >> $env:GITHUB_OUTPUT + + cleanup: + needs: prepare + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + R2_ENDPOINT_URL: ${{ env.R2_ENDPOINT_URL }} + steps: + - name: Delete all objects in R2 bucket + run: | + aws s3 rm s3://feishin-nightly --recursive --endpoint-url $R2_ENDPOINT_URL + + publish: + needs: [prepare, cleanup] + runs-on: ${{ matrix.os }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + + strategy: + matrix: + os: [windows-latest, macos-latest, ubuntu-latest] + + steps: + - name: Checkout git repo + uses: actions/checkout@v1 + + - name: Install Node and PNPM + uses: pnpm/action-setup@v4.1.0 + with: + version: 9 + + - name: Install dependencies + run: pnpm install + + - name: Set version from prepare job + shell: pwsh + run: | + $version = "${{ needs.prepare.outputs.version }}" + Write-Host "Setting version: $version" + $packageJson = Get-Content package.json | ConvertFrom-Json + $packageJson.version = $version + $packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json + + - name: Build and Publish to R2 (Windows) + if: matrix.os == 'windows-latest' + uses: nick-invision/retry@v2.8.2 + with: + timeout_minutes: 30 + max_attempts: 3 + retry_on: error + command: | + pnpm run publish:win:nightly + on_retry_command: pnpm cache delete + + - name: Build and Publish to R2 (macOS) + if: matrix.os == 'macos-latest' + uses: nick-invision/retry@v2.8.2 + with: + timeout_minutes: 30 + max_attempts: 3 + retry_on: error + command: | + pnpm run publish:mac:nightly + on_retry_command: pnpm cache delete + + - name: Build and Publish to R2 (Linux) + if: matrix.os == 'ubuntu-latest' + uses: nick-invision/retry@v2.8.2 + with: + timeout_minutes: 30 + max_attempts: 3 + retry_on: error + command: | + pnpm run publish:linux:nightly + on_retry_command: pnpm cache delete + + - name: Build and Publish to R2 (Linux ARM64) + if: matrix.os == 'ubuntu-latest' + uses: nick-invision/retry@v2.8.2 + with: + timeout_minutes: 30 + max_attempts: 3 + retry_on: error + command: | + pnpm run publish:linux-arm64:nightly + on_retry_command: pnpm cache delete diff --git a/electron-builder-nightly.yml b/electron-builder-nightly.yml new file mode 100644 index 000000000..4d4175ac3 --- /dev/null +++ b/electron-builder-nightly.yml @@ -0,0 +1,59 @@ +appId: org.jeffvli.feishin +productName: Feishin +artifactName: ${productName}-${version}-${os}-${arch}.${ext} +electronVersion: 39.4.0 +directories: + buildResources: assets +files: + - 'out/**/*' + - 'package.json' +extraResources: + - assets/** +asarUnpack: + - resources/** +win: + target: + - zip + - nsis + icon: assets/icons/icon.png + +nsis: + allowToChangeInstallationDirectory: true + oneClick: false + shortcutName: ${productName} + uninstallDisplayName: ${productName} + createDesktopShortcut: always + +mac: + target: + target: default + arch: + - arm64 + - x64 + icon: assets/icons/icon.icns + type: distribution + hardenedRuntime: true + entitlements: assets/entitlements.mac.plist + entitlementsInherit: assets/entitlements.mac.plist + gatekeeperAssess: false + notarize: false + +dmg: + contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }] + +linux: + target: + - AppImage + - deb + - tar.xz + category: AudioVideo;Audio;Player + icon: assets/icons/icon.png + artifactName: ${productName}-${os}-${arch}.${ext} + +npmRebuild: false + +publish: + provider: s3 + bucket: feishin-nightly + channel: alpha + endpoint: https://065f090c64de2dc707dd70ac72db9669.r2.cloudflarestorage.com diff --git a/package.json b/package.json index fd223bd8f..15d7c5ea2 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,15 @@ "publish:linux": "pnpm run build && electron-builder --publish always --linux", "publish:linux-arm64": "pnpm run build && electron-builder --publish always --linux --arm64", "publish:linux-arm64:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux --arm64", + "publish:linux-arm64:nightly": "pnpm run build && electron-builder --config electron-builder-nightly.yml --publish always --linux --arm64", "publish:linux:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux", + "publish:linux:nightly": "pnpm run build && electron-builder --config electron-builder-nightly.yml --publish always --linux", "publish:mac": "pnpm run build && electron-builder --publish always --mac", "publish:mac:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --mac", + "publish:mac:nightly": "pnpm run build && electron-builder --config electron-builder-nightly.yml --publish always --mac", "publish:win": "pnpm run build && electron-builder --publish always --win", "publish:win:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --win", + "publish:win:nightly": "pnpm run build && electron-builder --config electron-builder-nightly.yml --publish always --win", "start": "electron-vite preview", "typecheck": "pnpm run typecheck:node && pnpm run typecheck:web", "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",