mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2025-12-31 01:48:49 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac3be75082 | ||
|
|
a1663b865a | ||
|
|
c97a416d1e | ||
|
|
d28ab42303 | ||
|
|
fbb2bba3b6 | ||
|
|
08eda22587 |
30
.github/actions/vault-secrets/action.yml
vendored
Normal file
30
.github/actions/vault-secrets/action.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: 'Get Vault Secrets'
|
||||
description: 'Retrieves secrets from HashiCorp Vault using AppRole authentication'
|
||||
inputs:
|
||||
vault_host:
|
||||
description: 'Vault server URL'
|
||||
required: true
|
||||
vault_role_id:
|
||||
description: 'Vault AppRole Role ID'
|
||||
required: true
|
||||
vault_secret_id:
|
||||
description: 'Vault AppRole Secret ID'
|
||||
required: true
|
||||
secrets:
|
||||
description: 'Secrets to retrieve (multiline string, one per line in format: path | output_name)'
|
||||
required: true
|
||||
default: |
|
||||
secrets/data/github repo_readonly_pat | REPO_READONLY_PAT
|
||||
secrets/data/github packages_pat | PACKAGES_PAT
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Get vault secrets
|
||||
uses: hashicorp/vault-action@v2
|
||||
with:
|
||||
url: ${{ inputs.vault_host }}
|
||||
method: approle
|
||||
roleId: ${{ inputs.vault_role_id }}
|
||||
secretId: ${{ inputs.vault_secret_id }}
|
||||
secrets: ${{ inputs.secrets }}
|
||||
23
.github/workflows/build-docker.yml
vendored
23
.github/workflows/build-docker.yml
vendored
@@ -1,14 +1,21 @@
|
||||
name: Build Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
pull_request:
|
||||
paths:
|
||||
- 'code/**'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
push_docker:
|
||||
description: 'Push Docker image to registry'
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
|
||||
# Cancel in-progress runs for the same PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build_app:
|
||||
@@ -115,6 +122,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push docker image
|
||||
id: docker-build
|
||||
timeout-minutes: 15
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
@@ -133,6 +141,9 @@ jobs:
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
push: true
|
||||
push: ${{ inputs.push_docker }}
|
||||
tags: |
|
||||
${{ env.githubTags }}
|
||||
${{ env.githubTags }}
|
||||
# Enable BuildKit cache for faster builds
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
157
.github/workflows/build-executable.yml
vendored
157
.github/workflows/build-executable.yml
vendored
@@ -1,40 +1,55 @@
|
||||
name: Build Executables
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_version:
|
||||
description: 'Application version'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# Build for each platform in parallel using matrix strategy
|
||||
build-platform:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- runtime: win-x64
|
||||
platform: win-amd64
|
||||
- runtime: linux-x64
|
||||
platform: linux-amd64
|
||||
- runtime: linux-arm64
|
||||
platform: linux-arm64
|
||||
- runtime: osx-x64
|
||||
platform: osx-amd64
|
||||
- runtime: osx-arm64
|
||||
platform: osx-arm64
|
||||
|
||||
steps:
|
||||
|
||||
- name: Gate
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') && github.event_name != 'workflow_dispatch' }}
|
||||
run: |
|
||||
echo "This workflow only runs on tag events or manual dispatch. Pipeline finished."
|
||||
exit 0
|
||||
|
||||
- name: Set variables
|
||||
run: |
|
||||
repoFullName=${{ github.repository }}
|
||||
ref=${{ github.ref }}
|
||||
|
||||
# Handle both tag events and manual dispatch
|
||||
if [[ "$ref" =~ ^refs/tags/ ]]; then
|
||||
|
||||
# Use input version if provided, otherwise determine from ref
|
||||
if [[ -n "${{ inputs.app_version }}" ]]; then
|
||||
appVersion="${{ inputs.app_version }}"
|
||||
releaseVersion="v$appVersion"
|
||||
elif [[ "$ref" =~ ^refs/tags/ ]]; then
|
||||
releaseVersion=${ref##refs/tags/}
|
||||
appVersion=${releaseVersion#v}
|
||||
else
|
||||
# For manual dispatch, use a default version
|
||||
releaseVersion="dev-$(date +%Y%m%d-%H%M%S)"
|
||||
appVersion="0.0.1-dev"
|
||||
fi
|
||||
|
||||
repoFullName=${{ github.repository }}
|
||||
repositoryName=${repoFullName#*/}
|
||||
|
||||
echo "githubRepository=${{ github.repository }}" >> $GITHUB_ENV
|
||||
echo "githubRepositoryName=${repoFullName#*/}" >> $GITHUB_ENV
|
||||
echo "githubRepositoryName=$repositoryName" >> $GITHUB_ENV
|
||||
echo "releaseVersion=$releaseVersion" >> $GITHUB_ENV
|
||||
echo "appVersion=$appVersion" >> $GITHUB_ENV
|
||||
echo "executableName=Cleanuparr.Api" >> $GITHUB_ENV
|
||||
@@ -58,27 +73,28 @@ jobs:
|
||||
ref: ${{ github.ref_name }}
|
||||
token: ${{ env.REPO_READONLY_PAT }}
|
||||
|
||||
- name: Setup Node.js for frontend build
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: code/frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd code/frontend
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json', '**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: code/frontend/dist/ui/browser
|
||||
|
||||
- name: Install dependencies and restore
|
||||
run: |
|
||||
dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ secrets.PACKAGES_PAT }} --store-password-in-clear-text --name Cleanuparr https://nuget.pkg.github.com/Cleanuparr/index.json
|
||||
dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ env.PACKAGES_PAT }} --store-password-in-clear-text --name Cleanuparr https://nuget.pkg.github.com/Cleanuparr/index.json
|
||||
dotnet restore code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj
|
||||
|
||||
- name: Copy frontend to backend wwwroot
|
||||
@@ -86,52 +102,49 @@ jobs:
|
||||
mkdir -p code/backend/${{ env.executableName }}/wwwroot
|
||||
cp -r code/frontend/dist/ui/browser/* code/backend/${{ env.executableName }}/wwwroot/
|
||||
|
||||
- name: Build win-x64
|
||||
run: dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj -c Release --runtime win-x64 --self-contained -o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-win-amd64 /p:PublishSingleFile=true /p:Version=${{ env.appVersion }} /p:DebugSymbols=false
|
||||
- name: Build ${{ matrix.platform }}
|
||||
run: |
|
||||
dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj \
|
||||
-c Release \
|
||||
--runtime ${{ matrix.runtime }} \
|
||||
--self-contained \
|
||||
-o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-${{ matrix.platform }} \
|
||||
/p:PublishSingleFile=true \
|
||||
/p:Version=${{ env.appVersion }} \
|
||||
/p:DebugSymbols=false
|
||||
|
||||
- name: Build linux-x64
|
||||
run: dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj -c Release --runtime linux-x64 --self-contained -o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-amd64 /p:PublishSingleFile=true /p:Version=${{ env.appVersion }} /p:DebugSymbols=false
|
||||
|
||||
- name: Build linux-arm64
|
||||
run: dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj -c Release --runtime linux-arm64 --self-contained -o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-arm64 /p:PublishSingleFile=true /p:Version=${{ env.appVersion }} /p:DebugSymbols=false
|
||||
|
||||
- name: Build osx-x64
|
||||
run: dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj -c Release --runtime osx-x64 --self-contained -o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-amd64 /p:PublishSingleFile=true /p:Version=${{ env.appVersion }} /p:DebugSymbols=false
|
||||
|
||||
- name: Build osx-arm64
|
||||
run: dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj -c Release --runtime osx-arm64 --self-contained -o artifacts/${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-arm64 /p:PublishSingleFile=true /p:Version=${{ env.appVersion }} /p:DebugSymbols=false
|
||||
|
||||
- name: Zip win-x64
|
||||
- name: Zip artifact
|
||||
run: |
|
||||
cd ./artifacts
|
||||
zip -r ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-win-amd64.zip ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-win-amd64/
|
||||
zip -r ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-${{ matrix.platform }}.zip ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-${{ matrix.platform }}/
|
||||
|
||||
- name: Zip linux-x64
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: executable-${{ matrix.platform }}
|
||||
path: ./artifacts/*.zip
|
||||
retention-days: 30
|
||||
|
||||
# Consolidate all executable artifacts
|
||||
consolidate:
|
||||
needs: build-platform
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all platform artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: executable-*
|
||||
path: ./artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: List downloaded artifacts
|
||||
run: |
|
||||
cd ./artifacts
|
||||
zip -r ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-amd64.zip ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-amd64/
|
||||
echo "Consolidated executable artifacts:"
|
||||
find ./artifacts -type f -name "*.zip" | sort
|
||||
|
||||
- name: Zip linux-arm64
|
||||
run: |
|
||||
cd ./artifacts
|
||||
zip -r ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-arm64.zip ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-linux-arm64/
|
||||
|
||||
- name: Zip osx-x64
|
||||
run: |
|
||||
cd ./artifacts
|
||||
zip -r ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-amd64.zip ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-amd64/
|
||||
|
||||
- name: Zip osx-arm64
|
||||
run: |
|
||||
cd ./artifacts
|
||||
zip -r ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-arm64.zip ./${{ env.githubRepositoryName }}-${{ env.appVersion }}-osx-arm64/
|
||||
|
||||
- name: Upload artifacts
|
||||
- name: Upload consolidated artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cleanuparr-executables
|
||||
path: |
|
||||
./artifacts/*.zip
|
||||
path: ./artifacts/*.zip
|
||||
retention-days: 30
|
||||
|
||||
# Removed individual release step - handled by main release workflow
|
||||
46
.github/workflows/build-frontend.yml
vendored
Normal file
46
.github/workflows/build-frontend.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Build Frontend
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get vault secrets
|
||||
uses: hashicorp/vault-action@v2
|
||||
with:
|
||||
url: ${{ secrets.VAULT_HOST }}
|
||||
method: approle
|
||||
roleId: ${{ secrets.VAULT_ROLE_ID }}
|
||||
secretId: ${{ secrets.VAULT_SECRET_ID }}
|
||||
secrets:
|
||||
secrets/data/github repo_readonly_pat | REPO_READONLY_PAT
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
timeout-minutes: 1
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: ${{ github.ref_name }}
|
||||
token: ${{ env.REPO_READONLY_PAT }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: code/frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd code/frontend
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Upload frontend artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-dist
|
||||
path: code/frontend/dist/ui/browser
|
||||
retention-days: 1
|
||||
@@ -1,28 +1,47 @@
|
||||
name: Build macOS ARM Installer
|
||||
name: Build macOS Installers
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_version:
|
||||
description: 'Application version'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build-macos-arm-installer:
|
||||
name: Build macOS ARM Installer
|
||||
runs-on: macos-14 # ARM runner for Apple Silicon
|
||||
|
||||
build-macos-installer:
|
||||
name: Build macOS ${{ matrix.arch }} Installer
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: Intel
|
||||
runner: macos-13
|
||||
runtime: osx-x64
|
||||
min_os_version: "10.15"
|
||||
artifact_suffix: intel
|
||||
- arch: ARM
|
||||
runner: macos-14
|
||||
runtime: osx-arm64
|
||||
min_os_version: "11.0"
|
||||
artifact_suffix: arm64
|
||||
|
||||
steps:
|
||||
- name: Set variables
|
||||
run: |
|
||||
repoFullName=${{ github.repository }}
|
||||
ref=${{ github.ref }}
|
||||
|
||||
# Handle both tag events and manual dispatch
|
||||
if [[ "$ref" =~ ^refs/tags/ ]]; then
|
||||
|
||||
# Use input version if provided, otherwise determine from ref
|
||||
if [[ -n "${{ inputs.app_version }}" ]]; then
|
||||
appVersion="${{ inputs.app_version }}"
|
||||
releaseVersion="v$appVersion"
|
||||
elif [[ "$ref" =~ ^refs/tags/ ]]; then
|
||||
releaseVersion=${ref##refs/tags/}
|
||||
appVersion=${releaseVersion#v}
|
||||
else
|
||||
@@ -30,9 +49,9 @@ jobs:
|
||||
releaseVersion="dev-$(date +%Y%m%d-%H%M%S)"
|
||||
appVersion="0.0.1-dev"
|
||||
fi
|
||||
|
||||
|
||||
repositoryName=${repoFullName#*/}
|
||||
|
||||
|
||||
echo "githubRepository=${{ github.repository }}" >> $GITHUB_ENV
|
||||
echo "githubRepositoryName=$repositoryName" >> $GITHUB_ENV
|
||||
echo "releaseVersion=$releaseVersion" >> $GITHUB_ENV
|
||||
@@ -58,18 +77,11 @@ jobs:
|
||||
token: ${{ env.REPO_READONLY_PAT }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js for frontend build
|
||||
uses: actions/setup-node@v4
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: code/frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd code/frontend
|
||||
npm ci
|
||||
npm run build
|
||||
name: frontend-dist
|
||||
path: code/frontend/dist/ui/browser
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
@@ -81,16 +93,16 @@ jobs:
|
||||
dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ env.PACKAGES_PAT }} --store-password-in-clear-text --name Cleanuparr https://nuget.pkg.github.com/Cleanuparr/index.json
|
||||
dotnet restore code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj
|
||||
|
||||
- name: Build macOS ARM executable
|
||||
- name: Build macOS ${{ matrix.arch }} executable
|
||||
run: |
|
||||
# Clean any existing output directory
|
||||
rm -rf dist
|
||||
mkdir -p dist/temp
|
||||
|
||||
|
||||
# Build to a temporary location
|
||||
dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj \
|
||||
-c Release \
|
||||
--runtime osx-arm64 \
|
||||
--runtime ${{ matrix.runtime }} \
|
||||
--self-contained true \
|
||||
-o dist/temp \
|
||||
/p:PublishSingleFile=true \
|
||||
@@ -103,17 +115,17 @@ jobs:
|
||||
/p:_CodeSignDuringBuild=false \
|
||||
/p:PublishTrimmed=false \
|
||||
/p:TrimMode=link
|
||||
|
||||
|
||||
# Create proper app bundle structure
|
||||
mkdir -p dist/Cleanuparr.app/Contents/MacOS
|
||||
|
||||
|
||||
# Copy the built executable (note: AssemblyName is "Cleanuparr" not "Cleanuparr.Api")
|
||||
cp dist/temp/Cleanuparr dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
|
||||
|
||||
# Copy frontend directly to where it belongs in the app bundle
|
||||
mkdir -p dist/Cleanuparr.app/Contents/MacOS/wwwroot
|
||||
cp -r code/frontend/dist/ui/browser/* dist/Cleanuparr.app/Contents/MacOS/wwwroot/
|
||||
|
||||
|
||||
# Copy any additional runtime files if they exist
|
||||
if [ -d "dist/temp" ]; then
|
||||
find dist/temp -name "*.dylib" -exec cp {} dist/Cleanuparr.app/Contents/MacOS/ \; 2>/dev/null || true
|
||||
@@ -124,16 +136,16 @@ jobs:
|
||||
run: |
|
||||
# Make sure the executable is actually executable
|
||||
chmod +x dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
|
||||
|
||||
# Remove any .pdb files that might have been created
|
||||
find dist/Cleanuparr.app/Contents/MacOS -name "*.pdb" -delete 2>/dev/null || true
|
||||
|
||||
|
||||
echo "Checking architecture of built binary:"
|
||||
file dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
if command -v lipo >/dev/null 2>&1; then
|
||||
lipo -info dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
fi
|
||||
|
||||
|
||||
echo "Files in MacOS directory:"
|
||||
ls -la dist/Cleanuparr.app/Contents/MacOS/
|
||||
|
||||
@@ -141,12 +153,12 @@ jobs:
|
||||
run: |
|
||||
# Create proper app bundle structure
|
||||
mkdir -p dist/Cleanuparr.app/Contents/{MacOS,Resources,Frameworks}
|
||||
|
||||
|
||||
# Convert ICO to ICNS for macOS app bundle
|
||||
if command -v iconutil >/dev/null 2>&1; then
|
||||
# Create iconset directory structure
|
||||
mkdir -p Cleanuparr.iconset
|
||||
|
||||
|
||||
# Use existing PNG files from Logo directory for different sizes
|
||||
cp Logo/16.png Cleanuparr.iconset/icon_16x16.png
|
||||
cp Logo/32.png Cleanuparr.iconset/icon_16x16@2x.png
|
||||
@@ -158,14 +170,14 @@ jobs:
|
||||
cp Logo/512.png Cleanuparr.iconset/icon_256x256@2x.png
|
||||
cp Logo/512.png Cleanuparr.iconset/icon_512x512.png
|
||||
cp Logo/1024.png Cleanuparr.iconset/icon_512x512@2x.png
|
||||
|
||||
|
||||
# Create ICNS file
|
||||
iconutil -c icns Cleanuparr.iconset -o dist/Cleanuparr.app/Contents/Resources/Cleanuparr.icns
|
||||
|
||||
|
||||
# Clean up iconset directory
|
||||
rm -rf Cleanuparr.iconset
|
||||
fi
|
||||
|
||||
|
||||
# Create Launch Daemon plist
|
||||
cat > dist/Cleanuparr.app/Contents/Resources/com.cleanuparr.daemon.plist << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -196,7 +208,7 @@ jobs:
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
|
||||
# Create Info.plist with proper configuration
|
||||
cat > dist/Cleanuparr.app/Contents/Info.plist << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -228,7 +240,7 @@ jobs:
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>${{ matrix.min_os_version }}</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
@@ -245,7 +257,7 @@ jobs:
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
|
||||
# Clean up temp directory
|
||||
rm -rf dist/temp
|
||||
|
||||
@@ -255,96 +267,96 @@ jobs:
|
||||
mkdir -p scripts
|
||||
cat > scripts/preinstall << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# Stop and unload existing launch daemon if it exists
|
||||
if launchctl list | grep -q "com.cleanuparr.daemon"; then
|
||||
launchctl stop com.cleanuparr.daemon 2>/dev/null || true
|
||||
launchctl unload /Library/LaunchDaemons/com.cleanuparr.daemon.plist 2>/dev/null || true
|
||||
fi
|
||||
|
||||
|
||||
# Stop any running instances of Cleanuparr
|
||||
pkill -f "Cleanuparr" || true
|
||||
sleep 2
|
||||
|
||||
|
||||
# Remove old installation if it exists
|
||||
if [[ -d "/Applications/Cleanuparr.app" ]]; then
|
||||
rm -rf "/Applications/Cleanuparr.app"
|
||||
fi
|
||||
|
||||
|
||||
# Remove old launch daemon plist if it exists
|
||||
if [[ -f "/Library/LaunchDaemons/com.cleanuparr.daemon.plist" ]]; then
|
||||
rm -f "/Library/LaunchDaemons/com.cleanuparr.daemon.plist"
|
||||
fi
|
||||
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
|
||||
chmod +x scripts/preinstall
|
||||
|
||||
|
||||
# Create postinstall script
|
||||
cat > scripts/postinstall << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# Set proper permissions for the app bundle
|
||||
chmod -R 755 /Applications/Cleanuparr.app
|
||||
chmod +x /Applications/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
|
||||
|
||||
# Install the launch daemon
|
||||
cp /Applications/Cleanuparr.app/Contents/Resources/com.cleanuparr.daemon.plist /Library/LaunchDaemons/
|
||||
chown root:wheel /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
chmod 644 /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
|
||||
|
||||
# Load and start the service
|
||||
launchctl load /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
launchctl start com.cleanuparr.daemon
|
||||
|
||||
|
||||
# Wait a moment for service to start
|
||||
sleep 3
|
||||
|
||||
|
||||
# Display as system notification
|
||||
osascript -e 'display notification "Cleanuparr service started! Visit http://localhost:11011 in your browser." with title "Installation Complete"' 2>/dev/null || true
|
||||
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
|
||||
chmod +x scripts/postinstall
|
||||
|
||||
|
||||
# Create uninstall script (optional, for user reference)
|
||||
cat > scripts/uninstall_cleanuparr.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
# Cleanuparr Uninstall Script
|
||||
# Run this script with sudo to completely remove Cleanuparr
|
||||
|
||||
|
||||
echo "Stopping Cleanuparr service..."
|
||||
launchctl stop com.cleanuparr.daemon 2>/dev/null || true
|
||||
launchctl unload /Library/LaunchDaemons/com.cleanuparr.daemon.plist 2>/dev/null || true
|
||||
|
||||
|
||||
echo "Removing service files..."
|
||||
rm -f /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
|
||||
|
||||
echo "Removing application..."
|
||||
rm -rf /Applications/Cleanuparr.app
|
||||
|
||||
|
||||
echo "Removing logs..."
|
||||
rm -f /var/log/cleanuparr.log
|
||||
rm -f /var/log/cleanuparr.error.log
|
||||
|
||||
|
||||
echo "Cleanuparr has been completely removed."
|
||||
echo "Note: Configuration files in /Applications/Cleanuparr.app/Contents/MacOS/config/ have been removed with the app."
|
||||
EOF
|
||||
|
||||
|
||||
chmod +x scripts/uninstall_cleanuparr.sh
|
||||
|
||||
|
||||
# Copy uninstall script to app bundle for user access
|
||||
cp scripts/uninstall_cleanuparr.sh dist/Cleanuparr.app/Contents/Resources/
|
||||
|
||||
|
||||
# Determine package name
|
||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
pkg_name="Cleanuparr-${{ env.appVersion }}-macos-arm64.pkg"
|
||||
pkg_name="Cleanuparr-${{ env.appVersion }}-macos-${{ matrix.artifact_suffix }}.pkg"
|
||||
else
|
||||
pkg_name="Cleanuparr-${{ env.appVersion }}-macos-arm64-dev.pkg"
|
||||
pkg_name="Cleanuparr-${{ env.appVersion }}-macos-${{ matrix.artifact_suffix }}-dev.pkg"
|
||||
fi
|
||||
|
||||
|
||||
# Create PKG installer with better metadata
|
||||
pkgbuild --root dist/ \
|
||||
--scripts scripts/ \
|
||||
@@ -353,14 +365,12 @@ jobs:
|
||||
--install-location /Applications \
|
||||
--ownership preserve \
|
||||
${pkg_name}
|
||||
|
||||
|
||||
echo "pkgName=${pkg_name}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload installer as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Cleanuparr-macos-arm64-installer
|
||||
name: Cleanuparr-macos-${{ matrix.artifact_suffix }}-installer
|
||||
path: '${{ env.pkgName }}'
|
||||
retention-days: 30
|
||||
|
||||
# Removed individual release step - handled by main release workflow
|
||||
366
.github/workflows/build-macos-intel-installer.yml
vendored
366
.github/workflows/build-macos-intel-installer.yml
vendored
@@ -1,366 +0,0 @@
|
||||
name: Build macOS Intel Installer
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-macos-intel-installer:
|
||||
name: Build macOS Intel Installer
|
||||
runs-on: macos-13 # Intel runner
|
||||
|
||||
steps:
|
||||
- name: Set variables
|
||||
run: |
|
||||
repoFullName=${{ github.repository }}
|
||||
ref=${{ github.ref }}
|
||||
|
||||
# Handle both tag events and manual dispatch
|
||||
if [[ "$ref" =~ ^refs/tags/ ]]; then
|
||||
releaseVersion=${ref##refs/tags/}
|
||||
appVersion=${releaseVersion#v}
|
||||
else
|
||||
# For manual dispatch, use a default version
|
||||
releaseVersion="dev-$(date +%Y%m%d-%H%M%S)"
|
||||
appVersion="0.0.1-dev"
|
||||
fi
|
||||
|
||||
repositoryName=${repoFullName#*/}
|
||||
|
||||
echo "githubRepository=${{ github.repository }}" >> $GITHUB_ENV
|
||||
echo "githubRepositoryName=$repositoryName" >> $GITHUB_ENV
|
||||
echo "releaseVersion=$releaseVersion" >> $GITHUB_ENV
|
||||
echo "appVersion=$appVersion" >> $GITHUB_ENV
|
||||
echo "executableName=Cleanuparr.Api" >> $GITHUB_ENV
|
||||
|
||||
- name: Get vault secrets
|
||||
uses: hashicorp/vault-action@v2
|
||||
with:
|
||||
url: ${{ secrets.VAULT_HOST }}
|
||||
method: approle
|
||||
roleId: ${{ secrets.VAULT_ROLE_ID }}
|
||||
secretId: ${{ secrets.VAULT_SECRET_ID }}
|
||||
secrets:
|
||||
secrets/data/github repo_readonly_pat | REPO_READONLY_PAT;
|
||||
secrets/data/github packages_pat | PACKAGES_PAT
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ env.githubRepository }}
|
||||
ref: ${{ github.ref_name }}
|
||||
token: ${{ env.REPO_READONLY_PAT }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js for frontend build
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: code/frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd code/frontend
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Restore .NET dependencies
|
||||
run: |
|
||||
dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ env.PACKAGES_PAT }} --store-password-in-clear-text --name Cleanuparr https://nuget.pkg.github.com/Cleanuparr/index.json
|
||||
dotnet restore code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj
|
||||
|
||||
- name: Build macOS Intel executable
|
||||
run: |
|
||||
# Clean any existing output directory
|
||||
rm -rf dist
|
||||
mkdir -p dist/temp
|
||||
|
||||
# Build to a temporary location
|
||||
dotnet publish code/backend/${{ env.executableName }}/${{ env.executableName }}.csproj \
|
||||
-c Release \
|
||||
--runtime osx-x64 \
|
||||
--self-contained true \
|
||||
-o dist/temp \
|
||||
/p:PublishSingleFile=true \
|
||||
/p:Version=${{ env.appVersion }} \
|
||||
/p:DebugType=None \
|
||||
/p:DebugSymbols=false \
|
||||
/p:UseAppHost=true \
|
||||
/p:EnableMacOSCodeSign=false \
|
||||
/p:CodeSignOnCopy=false \
|
||||
/p:_CodeSignDuringBuild=false \
|
||||
/p:PublishTrimmed=false \
|
||||
/p:TrimMode=link
|
||||
|
||||
# Create proper app bundle structure
|
||||
mkdir -p dist/Cleanuparr.app/Contents/MacOS
|
||||
|
||||
# Copy the built executable (note: AssemblyName is "Cleanuparr" not "Cleanuparr.Api")
|
||||
cp dist/temp/Cleanuparr dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
|
||||
# Copy frontend directly to where it belongs in the app bundle
|
||||
mkdir -p dist/Cleanuparr.app/Contents/MacOS/wwwroot
|
||||
cp -r code/frontend/dist/ui/browser/* dist/Cleanuparr.app/Contents/MacOS/wwwroot/
|
||||
|
||||
# Copy any additional runtime files if they exist
|
||||
if [ -d "dist/temp" ]; then
|
||||
find dist/temp -name "*.dylib" -exec cp {} dist/Cleanuparr.app/Contents/MacOS/ \; 2>/dev/null || true
|
||||
find dist/temp -name "createdump" -exec cp {} dist/Cleanuparr.app/Contents/MacOS/ \; 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Post-build setup
|
||||
run: |
|
||||
# Make sure the executable is actually executable
|
||||
chmod +x dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
|
||||
# Remove any .pdb files that might have been created
|
||||
find dist/Cleanuparr.app/Contents/MacOS -name "*.pdb" -delete 2>/dev/null || true
|
||||
|
||||
echo "Checking architecture of built binary:"
|
||||
file dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
if command -v lipo >/dev/null 2>&1; then
|
||||
lipo -info dist/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
fi
|
||||
|
||||
echo "Files in MacOS directory:"
|
||||
ls -la dist/Cleanuparr.app/Contents/MacOS/
|
||||
|
||||
- name: Create macOS app bundle structure
|
||||
run: |
|
||||
# Create proper app bundle structure
|
||||
mkdir -p dist/Cleanuparr.app/Contents/{MacOS,Resources,Frameworks}
|
||||
|
||||
# Convert ICO to ICNS for macOS app bundle
|
||||
if command -v iconutil >/dev/null 2>&1; then
|
||||
# Create iconset directory structure
|
||||
mkdir -p Cleanuparr.iconset
|
||||
|
||||
# Use existing PNG files from Logo directory for different sizes
|
||||
cp Logo/16.png Cleanuparr.iconset/icon_16x16.png
|
||||
cp Logo/32.png Cleanuparr.iconset/icon_16x16@2x.png
|
||||
cp Logo/32.png Cleanuparr.iconset/icon_32x32.png
|
||||
cp Logo/64.png Cleanuparr.iconset/icon_32x32@2x.png
|
||||
cp Logo/128.png Cleanuparr.iconset/icon_128x128.png
|
||||
cp Logo/256.png Cleanuparr.iconset/icon_128x128@2x.png
|
||||
cp Logo/256.png Cleanuparr.iconset/icon_256x256.png
|
||||
cp Logo/512.png Cleanuparr.iconset/icon_256x256@2x.png
|
||||
cp Logo/512.png Cleanuparr.iconset/icon_512x512.png
|
||||
cp Logo/1024.png Cleanuparr.iconset/icon_512x512@2x.png
|
||||
|
||||
# Create ICNS file
|
||||
iconutil -c icns Cleanuparr.iconset -o dist/Cleanuparr.app/Contents/Resources/Cleanuparr.icns
|
||||
|
||||
# Clean up iconset directory
|
||||
rm -rf Cleanuparr.iconset
|
||||
fi
|
||||
|
||||
# Create Launch Daemon plist
|
||||
cat > dist/Cleanuparr.app/Contents/Resources/com.cleanuparr.daemon.plist << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.cleanuparr.daemon</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Cleanuparr.app/Contents/MacOS/Cleanuparr</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/cleanuparr.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/cleanuparr.error.log</string>
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/Applications/Cleanuparr.app/Contents/MacOS</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>HTTP_PORTS</key>
|
||||
<string>11011</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# Create Info.plist with proper configuration
|
||||
cat > dist/Cleanuparr.app/Contents/Info.plist << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>Cleanuparr</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.Cleanuparr</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Cleanuparr</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Cleanuparr</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${{ env.appVersion }}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${{ env.appVersion }}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>CLNR</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Cleanuparr</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.15</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
<false/>
|
||||
<key>NSSupportsSuddenTermination</key>
|
||||
<false/>
|
||||
<key>LSBackgroundOnly</key>
|
||||
<false/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
# Clean up temp directory
|
||||
rm -rf dist/temp
|
||||
|
||||
- name: Create PKG installer
|
||||
run: |
|
||||
# Create preinstall script to handle existing installations
|
||||
mkdir -p scripts
|
||||
cat > scripts/preinstall << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Stop and unload existing launch daemon if it exists
|
||||
if launchctl list | grep -q "com.cleanuparr.daemon"; then
|
||||
launchctl stop com.cleanuparr.daemon 2>/dev/null || true
|
||||
launchctl unload /Library/LaunchDaemons/com.cleanuparr.daemon.plist 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Stop any running instances of Cleanuparr
|
||||
pkill -f "Cleanuparr" || true
|
||||
sleep 2
|
||||
|
||||
# Remove old installation if it exists
|
||||
if [[ -d "/Applications/Cleanuparr.app" ]]; then
|
||||
rm -rf "/Applications/Cleanuparr.app"
|
||||
fi
|
||||
|
||||
# Remove old launch daemon plist if it exists
|
||||
if [[ -f "/Library/LaunchDaemons/com.cleanuparr.daemon.plist" ]]; then
|
||||
rm -f "/Library/LaunchDaemons/com.cleanuparr.daemon.plist"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x scripts/preinstall
|
||||
|
||||
# Create postinstall script
|
||||
cat > scripts/postinstall << 'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
# Set proper permissions for the app bundle
|
||||
chmod -R 755 /Applications/Cleanuparr.app
|
||||
chmod +x /Applications/Cleanuparr.app/Contents/MacOS/Cleanuparr
|
||||
|
||||
# Install the launch daemon
|
||||
cp /Applications/Cleanuparr.app/Contents/Resources/com.cleanuparr.daemon.plist /Library/LaunchDaemons/
|
||||
chown root:wheel /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
chmod 644 /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
|
||||
# Load and start the service
|
||||
launchctl load /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
launchctl start com.cleanuparr.daemon
|
||||
|
||||
# Wait a moment for service to start
|
||||
sleep 3
|
||||
|
||||
# Display as system notification
|
||||
osascript -e 'display notification "Cleanuparr service started! Visit http://localhost:11011 in your browser." with title "Installation Complete"' 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
|
||||
chmod +x scripts/postinstall
|
||||
|
||||
# Create uninstall script (optional, for user reference)
|
||||
cat > scripts/uninstall_cleanuparr.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
# Cleanuparr Uninstall Script
|
||||
# Run this script with sudo to completely remove Cleanuparr
|
||||
|
||||
echo "Stopping Cleanuparr service..."
|
||||
launchctl stop com.cleanuparr.daemon 2>/dev/null || true
|
||||
launchctl unload /Library/LaunchDaemons/com.cleanuparr.daemon.plist 2>/dev/null || true
|
||||
|
||||
echo "Removing service files..."
|
||||
rm -f /Library/LaunchDaemons/com.cleanuparr.daemon.plist
|
||||
|
||||
echo "Removing application..."
|
||||
rm -rf /Applications/Cleanuparr.app
|
||||
|
||||
echo "Removing logs..."
|
||||
rm -f /var/log/cleanuparr.log
|
||||
rm -f /var/log/cleanuparr.error.log
|
||||
|
||||
echo "Cleanuparr has been completely removed."
|
||||
echo "Note: Configuration files in /Applications/Cleanuparr.app/Contents/MacOS/config/ have been removed with the app."
|
||||
EOF
|
||||
|
||||
chmod +x scripts/uninstall_cleanuparr.sh
|
||||
|
||||
# Copy uninstall script to app bundle for user access
|
||||
cp scripts/uninstall_cleanuparr.sh dist/Cleanuparr.app/Contents/Resources/
|
||||
|
||||
# Determine package name
|
||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
pkg_name="Cleanuparr-${{ env.appVersion }}-macos-intel.pkg"
|
||||
else
|
||||
pkg_name="Cleanuparr-${{ env.appVersion }}-macos-intel-dev.pkg"
|
||||
fi
|
||||
|
||||
# Create PKG installer with better metadata
|
||||
pkgbuild --root dist/ \
|
||||
--scripts scripts/ \
|
||||
--identifier com.Cleanuparr \
|
||||
--version ${{ env.appVersion }} \
|
||||
--install-location /Applications \
|
||||
--ownership preserve \
|
||||
${pkg_name}
|
||||
|
||||
echo "pkgName=${pkg_name}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload installer as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Cleanuparr-macos-intel-installer
|
||||
path: '${{ env.pkgName }}'
|
||||
retention-days: 30
|
||||
|
||||
# Removed individual release step - handled by main release workflow
|
||||
41
.github/workflows/build-windows-installer.yml
vendored
41
.github/workflows/build-windows-installer.yml
vendored
@@ -1,11 +1,13 @@
|
||||
name: Build Windows Installer
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
inputs:
|
||||
app_version:
|
||||
description: 'Application version'
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build-windows-installer:
|
||||
@@ -17,9 +19,13 @@ jobs:
|
||||
run: |
|
||||
$repoFullName = "${{ github.repository }}"
|
||||
$ref = "${{ github.ref }}"
|
||||
|
||||
# Handle both tag events and manual dispatch
|
||||
if ($ref -match "^refs/tags/") {
|
||||
$inputVersion = "${{ inputs.app_version }}"
|
||||
|
||||
# Use input version if provided, otherwise determine from ref
|
||||
if ($inputVersion -ne "") {
|
||||
$appVersion = $inputVersion
|
||||
$releaseVersion = "v$appVersion"
|
||||
} elseif ($ref -match "^refs/tags/") {
|
||||
$releaseVersion = $ref -replace "refs/tags/", ""
|
||||
$appVersion = $releaseVersion -replace "^v", ""
|
||||
} else {
|
||||
@@ -27,15 +33,15 @@ jobs:
|
||||
$releaseVersion = "dev-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||
$appVersion = "0.0.1-dev"
|
||||
}
|
||||
|
||||
|
||||
$repositoryName = $repoFullName.Split("/")[1]
|
||||
|
||||
|
||||
echo "githubRepository=${{ github.repository }}" >> $env:GITHUB_ENV
|
||||
echo "githubRepositoryName=$repositoryName" >> $env:GITHUB_ENV
|
||||
echo "releaseVersion=$releaseVersion" >> $env:GITHUB_ENV
|
||||
echo "appVersion=$appVersion" >> $env:GITHUB_ENV
|
||||
echo "executableName=Cleanuparr.Api" >> $env:GITHUB_ENV
|
||||
echo "APP_VERSION=$appVersion" >> $env:GITHUB_ENV
|
||||
echo "executableName=Cleanuparr.Api" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Get vault secrets
|
||||
uses: hashicorp/vault-action@v2
|
||||
@@ -55,18 +61,11 @@ jobs:
|
||||
ref: ${{ github.ref_name }}
|
||||
token: ${{ env.REPO_READONLY_PAT }}
|
||||
|
||||
- name: Setup Node.js for frontend build
|
||||
uses: actions/setup-node@v4
|
||||
- name: Download frontend artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: code/frontend/package-lock.json
|
||||
|
||||
- name: Build frontend
|
||||
run: |
|
||||
cd code/frontend
|
||||
npm ci
|
||||
npm run build
|
||||
name: frontend-dist
|
||||
path: code/frontend/dist/ui/browser
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
|
||||
45
.github/workflows/dependency-review.yml
vendored
Normal file
45
.github/workflows/dependency-review.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Dependency Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Cancel in-progress runs for the same PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
# Fail on critical and high severity vulnerabilities
|
||||
fail-on-severity: high
|
||||
# Warn on moderate vulnerabilities
|
||||
warn-on-severity: moderate
|
||||
# Allow licenses
|
||||
# allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD
|
||||
# Comment summarizes the vulnerabilities found
|
||||
comment-summary-in-pr: on-failure
|
||||
# Show dependency changes in PR
|
||||
show-openssf-scorecard: true
|
||||
vulnerability-check: true
|
||||
|
||||
- name: Upload dependency review results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dependency-review-results
|
||||
path: dependency-review-*.json
|
||||
if-no-files-found: ignore
|
||||
retention-days: 30
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 24.x
|
||||
cache: yarn
|
||||
cache-dependency-path: docs/yarn.lock
|
||||
|
||||
|
||||
177
.github/workflows/release.yml
vendored
177
.github/workflows/release.yml
vendored
@@ -10,6 +10,31 @@ on:
|
||||
description: 'Version to release (e.g., 1.0.0)'
|
||||
required: false
|
||||
default: ''
|
||||
runTests:
|
||||
description: 'Run test suite'
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
buildDocker:
|
||||
description: 'Build Docker image'
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
pushDocker:
|
||||
description: 'Push Docker image to registry'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
buildBinaries:
|
||||
description: 'Build executables and installers'
|
||||
type: boolean
|
||||
required: false
|
||||
default: true
|
||||
createRelease:
|
||||
description: 'Create GitHub release'
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
# Validate release
|
||||
@@ -19,7 +44,7 @@ jobs:
|
||||
app_version: ${{ steps.version.outputs.app_version }}
|
||||
release_version: ${{ steps.version.outputs.release_version }}
|
||||
is_tag: ${{ steps.version.outputs.is_tag }}
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -47,40 +72,98 @@ jobs:
|
||||
echo "app_version=$app_version" >> $GITHUB_OUTPUT
|
||||
echo "release_version=$release_version" >> $GITHUB_OUTPUT
|
||||
echo "is_tag=$is_tag" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
echo "🏷️ Release Version: $release_version"
|
||||
echo "📱 App Version: $app_version"
|
||||
echo "🔖 Is Tag: $is_tag"
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
needs: validate
|
||||
if: ${{ needs.validate.outputs.is_tag == 'true' || github.event.inputs.runTests == 'true' }}
|
||||
uses: ./.github/workflows/test.yml
|
||||
secrets: inherit
|
||||
|
||||
# Build frontend once for all build jobs and cache it
|
||||
build-frontend:
|
||||
needs: [validate, test]
|
||||
if: |
|
||||
always() &&
|
||||
needs.validate.result == 'success' &&
|
||||
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
|
||||
(needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true')
|
||||
uses: ./.github/workflows/build-frontend.yml
|
||||
secrets: inherit
|
||||
|
||||
# Build portable executables
|
||||
build-executables:
|
||||
needs: validate
|
||||
needs: [validate, test, build-frontend]
|
||||
if: |
|
||||
always() &&
|
||||
needs.validate.result == 'success' &&
|
||||
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
|
||||
needs.build-frontend.result == 'success' &&
|
||||
(needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true')
|
||||
uses: ./.github/workflows/build-executable.yml
|
||||
with:
|
||||
app_version: ${{ needs.validate.outputs.app_version }}
|
||||
secrets: inherit
|
||||
|
||||
# Build Windows installer
|
||||
build-windows-installer:
|
||||
needs: validate
|
||||
needs: [validate, test, build-frontend]
|
||||
if: |
|
||||
always() &&
|
||||
needs.validate.result == 'success' &&
|
||||
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
|
||||
needs.build-frontend.result == 'success' &&
|
||||
(needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true')
|
||||
uses: ./.github/workflows/build-windows-installer.yml
|
||||
with:
|
||||
app_version: ${{ needs.validate.outputs.app_version }}
|
||||
secrets: inherit
|
||||
|
||||
# Build macOS Intel installer
|
||||
build-macos-intel:
|
||||
needs: validate
|
||||
uses: ./.github/workflows/build-macos-intel-installer.yml
|
||||
# Build macOS installers (Intel and ARM)
|
||||
build-macos:
|
||||
needs: [validate, test, build-frontend]
|
||||
if: |
|
||||
always() &&
|
||||
needs.validate.result == 'success' &&
|
||||
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
|
||||
needs.build-frontend.result == 'success' &&
|
||||
(needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true')
|
||||
uses: ./.github/workflows/build-macos-installer.yml
|
||||
with:
|
||||
app_version: ${{ needs.validate.outputs.app_version }}
|
||||
secrets: inherit
|
||||
|
||||
# Build macOS ARM installer
|
||||
build-macos-arm:
|
||||
needs: validate
|
||||
uses: ./.github/workflows/build-macos-arm-installer.yml
|
||||
# Build and push Docker image(s)
|
||||
build-docker:
|
||||
needs: [validate, test]
|
||||
if: |
|
||||
always() &&
|
||||
needs.validate.result == 'success' &&
|
||||
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
|
||||
(needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildDocker == 'true')
|
||||
uses: ./.github/workflows/build-docker.yml
|
||||
with:
|
||||
push_docker: ${{ needs.validate.outputs.is_tag == 'true' || github.event.inputs.pushDocker == 'true' }}
|
||||
secrets: inherit
|
||||
|
||||
# Create GitHub release
|
||||
create-release:
|
||||
needs: [validate, build-executables, build-windows-installer, build-macos-intel, build-macos-arm]
|
||||
needs: [validate, build-executables, build-windows-installer, build-macos]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
if: |
|
||||
always() &&
|
||||
needs.validate.result == 'success' &&
|
||||
needs.build-executables.result == 'success' &&
|
||||
needs.build-windows-installer.result == 'success' &&
|
||||
needs.build-macos.result == 'success' &&
|
||||
(
|
||||
needs.validate.outputs.is_tag == 'true' ||
|
||||
(github.event.inputs.createRelease == 'true' && github.event.inputs.buildBinaries == 'true')
|
||||
)
|
||||
|
||||
steps:
|
||||
- name: Get vault secrets
|
||||
@@ -119,46 +202,56 @@ jobs:
|
||||
|
||||
# Summary job
|
||||
summary:
|
||||
needs: [validate, build-executables, build-windows-installer, build-macos-intel, build-macos-arm]
|
||||
needs: [validate, test, build-frontend, build-executables, build-windows-installer, build-macos, build-docker]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
|
||||
|
||||
steps:
|
||||
- name: Record workflow start time
|
||||
id: workflow-start
|
||||
run: |
|
||||
# Get workflow start time from GitHub API
|
||||
workflow_start=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }} --jq '.run_started_at')
|
||||
start_epoch=$(date -d "$workflow_start" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$workflow_start" +%s)
|
||||
echo "start=$start_epoch" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Summary
|
||||
run: |
|
||||
# Calculate total workflow duration
|
||||
start_time=${{ steps.workflow-start.outputs.start }}
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
minutes=$((duration / 60))
|
||||
seconds=$((duration % 60))
|
||||
echo "## 🏗️ Cleanuparr Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version**: ${{ needs.validate.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**App Version**: ${{ needs.validate.outputs.app_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Is Tag**: ${{ needs.validate.outputs.is_tag }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Total Duration**: ${minutes}m ${seconds}s" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Build Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check job results
|
||||
if [[ "${{ needs.build-executables.result }}" == "success" ]]; then
|
||||
echo "✅ **Portable Executables**: Success" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Portable Executables**: ${{ needs.build-executables.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.build-windows-installer.result }}" == "success" ]]; then
|
||||
echo "✅ **Windows Installer**: Success" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Windows Installer**: ${{ needs.build-windows-installer.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.build-macos-intel.result }}" == "success" ]]; then
|
||||
echo "✅ **macOS Intel Installer**: Success" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **macOS Intel Installer**: ${{ needs.build-macos-intel.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.build-macos-arm.result }}" == "success" ]]; then
|
||||
echo "✅ **macOS ARM Installer**: Success" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **macOS ARM Installer**: ${{ needs.build-macos-arm.result }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
|
||||
# Helper function to print job result
|
||||
print_result() {
|
||||
local name="$1"
|
||||
local result="$2"
|
||||
case "$result" in
|
||||
success) echo "✅ **$name**: Success" >> $GITHUB_STEP_SUMMARY ;;
|
||||
skipped) echo "⏭️ **$name**: Skipped" >> $GITHUB_STEP_SUMMARY ;;
|
||||
*) echo "❌ **$name**: $result" >> $GITHUB_STEP_SUMMARY ;;
|
||||
esac
|
||||
}
|
||||
|
||||
print_result "Tests" "${{ needs.test.result }}"
|
||||
print_result "Frontend Build" "${{ needs.build-frontend.result }}"
|
||||
print_result "Portable Executables" "${{ needs.build-executables.result }}"
|
||||
print_result "Windows Installer" "${{ needs.build-windows-installer.result }}"
|
||||
print_result "macOS Installers (Intel & ARM)" "${{ needs.build-macos.result }}"
|
||||
print_result "Docker Image Build" "${{ needs.build-docker.result }}"
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "🎉 **Build completed!**" >> $GITHUB_STEP_SUMMARY
|
||||
99
.github/workflows/test.yml
vendored
Normal file
99
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'code/backend/**'
|
||||
- '.github/workflows/test.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'code/backend/**'
|
||||
- '.github/workflows/test.yml'
|
||||
workflow_call:
|
||||
|
||||
# Cancel in-progress runs for the same PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
timeout-minutes: 1
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json', '**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
- name: Get vault secrets
|
||||
uses: hashicorp/vault-action@v2
|
||||
with:
|
||||
url: ${{ secrets.VAULT_HOST }}
|
||||
method: approle
|
||||
roleId: ${{ secrets.VAULT_ROLE_ID }}
|
||||
secretId: ${{ secrets.VAULT_SECRET_ID }}
|
||||
secrets:
|
||||
secrets/data/github packages_pat | PACKAGES_PAT
|
||||
|
||||
- name: Restore dependencies
|
||||
run: |
|
||||
dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ env.PACKAGES_PAT }} --store-password-in-clear-text --name Cleanuparr https://nuget.pkg.github.com/Cleanuparr/index.json
|
||||
dotnet restore code/backend/cleanuparr.sln
|
||||
|
||||
- name: Build solution
|
||||
run: dotnet build code/backend/cleanuparr.sln --configuration Release --no-restore
|
||||
|
||||
- name: Run tests
|
||||
id: run-tests
|
||||
run: dotnet test code/backend/cleanuparr.sln --configuration Release --no-build --verbosity normal --logger trx --collect:"XPlat Code Coverage" --results-directory ./coverage
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: ./coverage/*.trx
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload coverage reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: ./coverage/**/coverage.cobertura.xml
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./coverage/**/coverage.cobertura.xml
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
flags: backend
|
||||
name: backend-coverage
|
||||
|
||||
- name: Test Summary
|
||||
run: |
|
||||
echo "## Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${{ steps.run-tests.outcome }}" == "success" ]; then
|
||||
echo "✅ All tests passed!" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ Tests failed or were cancelled. Status: ${{ steps.run-tests.outcome }}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Test artifacts have been uploaded for detailed analysis." >> $GITHUB_STEP_SUMMARY
|
||||
66
.github/workflows/version-info.yml
vendored
Normal file
66
.github/workflows/version-info.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Get Version Info
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
manual_version:
|
||||
description: 'Manual version override (e.g., 1.0.0)'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
outputs:
|
||||
app_version:
|
||||
description: 'Application version (without v prefix)'
|
||||
value: ${{ jobs.version.outputs.app_version }}
|
||||
release_version:
|
||||
description: 'Release version (with v prefix)'
|
||||
value: ${{ jobs.version.outputs.release_version }}
|
||||
is_tag:
|
||||
description: 'Whether this is a tag event'
|
||||
value: ${{ jobs.version.outputs.is_tag }}
|
||||
repository_name:
|
||||
description: 'Repository name without owner'
|
||||
value: ${{ jobs.version.outputs.repository_name }}
|
||||
|
||||
jobs:
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
app_version: ${{ steps.version.outputs.app_version }}
|
||||
release_version: ${{ steps.version.outputs.release_version }}
|
||||
is_tag: ${{ steps.version.outputs.is_tag }}
|
||||
repository_name: ${{ steps.version.outputs.repository_name }}
|
||||
|
||||
steps:
|
||||
- name: Calculate version info
|
||||
id: version
|
||||
run: |
|
||||
repoFullName="${{ github.repository }}"
|
||||
repositoryName="${repoFullName#*/}"
|
||||
|
||||
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then
|
||||
# Tag event
|
||||
release_version="${GITHUB_REF##refs/tags/}"
|
||||
app_version="${release_version#v}"
|
||||
is_tag="true"
|
||||
elif [[ -n "${{ inputs.manual_version }}" ]]; then
|
||||
# Manual workflow with version
|
||||
app_version="${{ inputs.manual_version }}"
|
||||
release_version="v${app_version}"
|
||||
is_tag="false"
|
||||
else
|
||||
# Development build
|
||||
app_version="0.0.1-dev-$(date +%Y%m%d-%H%M%S)"
|
||||
release_version="v${app_version}"
|
||||
is_tag="false"
|
||||
fi
|
||||
|
||||
echo "app_version=${app_version}" >> $GITHUB_OUTPUT
|
||||
echo "release_version=${release_version}" >> $GITHUB_OUTPUT
|
||||
echo "is_tag=${is_tag}" >> $GITHUB_OUTPUT
|
||||
echo "repository_name=${repositoryName}" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "📦 Repository: ${repositoryName}"
|
||||
echo "🏷️ Release Version: ${release_version}"
|
||||
echo "📱 App Version: ${app_version}"
|
||||
echo "🔖 Is Tag: ${is_tag}"
|
||||
@@ -2,6 +2,11 @@ _Love this project? Give it a ⭐️ and let others know!_
|
||||
|
||||
# <img width="24px" src="./Logo/256.png" alt="Cleanuparr"></img> Cleanuparr
|
||||
|
||||

|
||||

|
||||
[](https://github.com/Cleanuparr/Cleanuparr/actions/workflows/test.yml)
|
||||
|
||||
|
||||
[](https://discord.gg/SCtMCgtsc4)
|
||||
|
||||
Cleanuparr is a tool for automating the cleanup of unwanted or blocked files in Sonarr, Radarr, and supported download clients like qBittorrent. It removes incomplete or blocked downloads, updates queues, and enforces blacklists or whitelists to manage file selection. After removing blocked content, Cleanuparr can also trigger a search to replace the deleted shows/movies.
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Build Angular frontend
|
||||
FROM --platform=$BUILDPLATFORM node:18-alpine AS frontend-build
|
||||
FROM --platform=$BUILDPLATFORM node:24-alpine AS frontend-build
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files first for better layer caching
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm ci && npm install -g @angular/cli
|
||||
# Use cache mount for npm to speed up builds
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci && npm install -g @angular/cli
|
||||
|
||||
# Copy source code
|
||||
COPY frontend/ .
|
||||
@@ -28,14 +30,17 @@ EXPOSE 11011
|
||||
# Copy source code
|
||||
COPY backend/ ./backend/
|
||||
|
||||
# Restore dependencies
|
||||
# Add NuGet source
|
||||
RUN dotnet nuget add source --username ${PACKAGES_USERNAME} --password ${PACKAGES_PAT} --store-password-in-clear-text --name Cleanuparr https://nuget.pkg.github.com/Cleanuparr/index.json
|
||||
|
||||
# Build and publish
|
||||
RUN dotnet publish ./backend/Cleanuparr.Api/Cleanuparr.Api.csproj \
|
||||
# Restore and publish with cache mount
|
||||
RUN --mount=type=cache,target=/root/.nuget/packages,sharing=locked \
|
||||
dotnet restore ./backend/Cleanuparr.Api/Cleanuparr.Api.csproj -a $TARGETARCH && \
|
||||
dotnet publish ./backend/Cleanuparr.Api/Cleanuparr.Api.csproj \
|
||||
-a $TARGETARCH \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore \
|
||||
/p:Version=${VERSION} \
|
||||
/p:PublishSingleFile=true \
|
||||
/p:DebugSymbols=false
|
||||
|
||||
@@ -23,27 +23,24 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MassTransit" Version="8.4.1" />
|
||||
<PackageReference Include="MassTransit" Version="8.5.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.6" />
|
||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.14.0" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.0" />
|
||||
<PackageReference Include="Quartz" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.1" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<!-- API-related packages -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Text.Json.Serialization;
|
||||
using Cleanuparr.Infrastructure.Health;
|
||||
using Cleanuparr.Infrastructure.Hubs;
|
||||
using Microsoft.AspNetCore.Http.Json;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text;
|
||||
using Cleanuparr.Api.Middleware;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -46,20 +45,6 @@ public static class ApiDI
|
||||
|
||||
// Add health status broadcaster
|
||||
services.AddHostedService<HealthStatusBroadcaster>();
|
||||
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Cleanuparr API",
|
||||
Version = "v1",
|
||||
Description = "API for managing media downloads and cleanups",
|
||||
Contact = new OpenApiContact
|
||||
{
|
||||
Name = "Cleanuparr Team"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -83,17 +68,6 @@ public static class ApiDI
|
||||
app.UseCors("Any");
|
||||
app.UseRouting();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint("v1/swagger.json", "Cleanuparr API v1");
|
||||
options.RoutePrefix = "swagger";
|
||||
options.DocumentTitle = "Cleanuparr API Documentation";
|
||||
});
|
||||
}
|
||||
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -16,20 +16,16 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -424,22 +424,6 @@ public class QBitItemTests
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsIgnored_MatchingName_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var torrentInfo = new TorrentInfo { Name = "Test Torrent", Hash = "abc123" };
|
||||
var trackers = new List<TorrentTracker>();
|
||||
var wrapper = new QBitItem(torrentInfo, trackers, false);
|
||||
var ignoredDownloads = new[] { "test" };
|
||||
|
||||
// Act
|
||||
var result = wrapper.IsIgnored(ignoredDownloads);
|
||||
|
||||
// Assert
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsIgnored_MatchingHash_ReturnsTrue()
|
||||
{
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
<PackageReference Include="FLM.QBittorrent" Version="1.0.2" />
|
||||
<PackageReference Include="FLM.Transmission" Version="1.0.3" />
|
||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||
<PackageReference Include="MassTransit.Abstractions" Version="8.4.1" />
|
||||
<PackageReference Include="MassTransit.Abstractions" Version="8.5.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Mono.Unix" Version="7.1.0-final.1.21458.1" />
|
||||
<PackageReference Include="Quartz" Version="3.14.0" />
|
||||
<PackageReference Include="Quartz" Version="3.15.1" />
|
||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="9.0.9" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -87,7 +87,7 @@ public sealed class QBitItem : ITorrentItem
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_torrentInfo.Tags.Contains(pattern, StringComparer.InvariantCultureIgnoreCase))
|
||||
if (_torrentInfo.Tags?.Contains(pattern, StringComparer.InvariantCultureIgnoreCase) is true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
|
||||
<PackageReference Include="Serilog" Version="4.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
5251
code/frontend/package-lock.json
generated
5251
code/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,14 +11,14 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^19.2.0",
|
||||
"@angular/common": "^19.2.16",
|
||||
"@angular/compiler": "^19.2.0",
|
||||
"@angular/core": "^19.2.0",
|
||||
"@angular/forms": "^19.2.0",
|
||||
"@angular/platform-browser": "^19.2.0",
|
||||
"@angular/platform-browser-dynamic": "^19.2.0",
|
||||
"@angular/router": "^19.2.0",
|
||||
"@angular/service-worker": "^19.2.0",
|
||||
"@angular/core": "^19.2.16",
|
||||
"@angular/forms": "^19.2.16",
|
||||
"@angular/platform-browser": "^19.2.16",
|
||||
"@angular/platform-browser-dynamic": "^19.2.16",
|
||||
"@angular/router": "^19.2.16",
|
||||
"@angular/service-worker": "^19.2.16",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@ngrx/signals": "^19.2.0",
|
||||
"@primeng/themes": "^19.1.3",
|
||||
@@ -32,7 +32,7 @@
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.2.12",
|
||||
"@angular/cli": "^19.2.12",
|
||||
"@angular/compiler-cli": "^19.2.0",
|
||||
"@angular/compiler-cli": "^19.2.16",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"angular-eslint": "19.6.0",
|
||||
"eslint": "^9.27.0",
|
||||
|
||||
28
docs/package-lock.json
generated
28
docs/package-lock.json
generated
@@ -5653,9 +5653,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -8351,9 +8352,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
@@ -9425,9 +9427,10 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
@@ -12037,9 +12040,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
|
||||
"integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
|
||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||
"engines": {
|
||||
"node": ">= 6.13.0"
|
||||
}
|
||||
|
||||
@@ -3223,9 +3223,9 @@ boxen@^7.0.0:
|
||||
wrap-ansi "^8.1.0"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
version "1.1.12"
|
||||
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz"
|
||||
integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
@@ -5510,17 +5510,17 @@ joi@^17.9.2:
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.13.1:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz"
|
||||
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
|
||||
version "3.14.2"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz"
|
||||
integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz"
|
||||
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
@@ -6587,9 +6587,9 @@ node-emoji@^2.1.0:
|
||||
skin-tone "^2.0.0"
|
||||
|
||||
node-forge@^1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz"
|
||||
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
||||
version "1.3.2"
|
||||
resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz"
|
||||
integrity sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==
|
||||
|
||||
node-releases@^2.0.26:
|
||||
version "2.0.26"
|
||||
|
||||
Reference in New Issue
Block a user