Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 72 additions & 1 deletion .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,49 @@ jobs:
${{ steps.find-installer.outputs.deb_path }}
dist/DevNullifier-linux-unpacked.zip

build-macos:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Build application
run: npm run build:mac

- name: Create ZIP archive from unpacked
run: |
cd dist
zip -r DevNullifier-mac-unpacked.zip mac/

- name: Find installer files
id: find-installer
run: |
DMG=$(find dist -name "*.dmg" | head -1)
echo "dmg_path=$DMG" >> $GITHUB_OUTPUT

- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-artifacts
path: |
${{ steps.find-installer.outputs.dmg_path }}
dist/DevNullifier-mac-unpacked.zip

create-release:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
runs-on: ubuntu-latest
needs: [build-windows, build-linux]
needs: [build-windows, build-linux, build-macos]

permissions:
contents: write
Expand All @@ -132,6 +171,12 @@ jobs:
name: linux-artifacts
path: artifacts/linux/

- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos-artifacts
path: artifacts/macos/

- name: Create Release
id: create_release
uses: actions/create-release@v1
Expand Down Expand Up @@ -177,6 +222,12 @@ jobs:
echo "appimage_path=$appimage" >> $GITHUB_OUTPUT
echo "deb_path=$deb" >> $GITHUB_OUTPUT

- name: Find macOS files
id: find-macos-files
run: |
dmg=$(find artifacts/macos -name "*.dmg" | head -1)
echo "dmg_path=$dmg" >> $GITHUB_OUTPUT

- name: Upload Linux AppImage
uses: actions/upload-release-asset@v1
env:
Expand Down Expand Up @@ -206,3 +257,23 @@ jobs:
asset_path: artifacts/linux/DevNullifier-linux-unpacked.zip
asset_name: ${{ github.ref_name == 'develop' && 'DevNullifier-linux-unpacked-dev.zip' || 'DevNullifier-linux-unpacked.zip' }}
asset_content_type: application/zip

- name: Upload macOS DMG
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ steps.find-macos-files.outputs.dmg_path }}
asset_name: ${{ github.ref_name == 'develop' && 'DevNullifier-mac-x64-dev.dmg' || 'DevNullifier-mac-x64.dmg' }}
asset_content_type: application/octet-stream

- name: Upload macOS ZIP Archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: artifacts/macos/DevNullifier-mac-unpacked.zip
asset_name: ${{ github.ref_name == 'develop' && 'DevNullifier-mac-unpacked-dev.zip' || 'DevNullifier-mac-unpacked.zip' }}
asset_content_type: application/zip
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

All notable changes to DevNullifier will be documented in this file.

## [1.2.2] - Dark Mode

### Added

- **Dark Mode**: Add dark mode to the application

## [1.2.1] - macOS Support

### Added

- **macOS Support**: Add support for macOS operating systems
- **DMG Support**: Add support for DMG packaging
- **ZIP Support**: Add support for ZIP packaging

## [1.2.0] - Linux Support

### Added
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

A cross-platform cleaner for application data and developer caches (node_modules, .cache, Library, Binary, Intermediate, etc.) built with Electron, Vue 3, and Vuetify 3.

Now with dark mode!

**Supported Platforms:**

- Windows (AppData: Local, Roaming, LocalLow)
- Linux (User directories: ~/.config, ~/.cache, ~/.local/share, ~/.local/state, /tmp)
- macOS (User directories: ~/Library/Caches, ~/Library/Application Support, ~/Library/Logs)

## Download

Expand All @@ -26,6 +29,7 @@ Cleans application data folders that accumulate temporary files, caches, and jun

- **Windows**: Temp folders, cache folders, crash dumps, pending files, log files in AppData (Local, Roaming, LocalLow)
- **Linux**: Cache and temp folders in ~/.config, ~/.cache, ~/.local/share, ~/.local/state, /tmp
- **macOS**: Cache and temp folders in ~/Library/Caches, ~/Library/Application Support, ~/Library/Logs

## Dev Cleaner

Expand Down Expand Up @@ -87,6 +91,16 @@ build.bat # Build for production
run.bat # Run built application
```

**Using macOS:**

```bash
npm install # Install dependencies
npm run dev # Run in development
npm run build # Build for production
```

**Note for macOS users:** The app is unsigned, so you'll need to right-click → "Open" → "Open" the first time to bypass Gatekeeper.

## Usage

1. Launch the application
Expand All @@ -109,6 +123,7 @@ npm run build:renderer # Build renderer (Vue app)
npm run build # Build entire app
npm run build:win # Build for Windows
npm run build:linux # Build for Linux
npm run build:mac # Build for macOS
```

## License
Expand Down
25 changes: 23 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "devnullifier",
"version": "1.2.0",
"version": "1.2.2",
"description": "DevNullifier: Clean application data, dev caches (node_modules, .cache, Library, Binary, Intermediate, etc) with Electron, Vue 3, and Vuetify 3.",
"main": "src/main/main.js",
"scripts": {
Expand All @@ -11,7 +11,8 @@
"build:renderer": "vite build",
"build:main": "electron-builder --publish=never",
"build:linux": "npm run build:renderer && electron-builder --linux --publish=never",
"build:win": "npm run build:renderer && electron-builder --win --publish=never"
"build:win": "npm run build:renderer && electron-builder --win --publish=never",
"build:mac": "npm run build:renderer && electron-builder --mac --publish=never"
},
"keywords": [
"electron",
Expand Down Expand Up @@ -98,6 +99,26 @@
"category": "Utility",
"icon": "src/assets/icon-256.png"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": [
"x64",
"arm64"
]
},
{
"target": "dir",
"arch": [
"x64",
"arm64"
]
}
],
"category": "public.app-category.utilities",
"icon": "src/assets/icon-512.png"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true
Expand Down
20 changes: 19 additions & 1 deletion src/main/appDataCleaner.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const KEYWORDS = [
"pending"
];

// Get AppData paths (Windows) or equivalent user directories (Linux)
// Get AppData paths (Windows) or equivalent user directories (Linux/macOS)
function getAppDataPaths() {
const paths = [];

Expand Down Expand Up @@ -48,6 +48,24 @@ function getAppDataPaths() {
paths.push(dirPath);
}
});
} else if (process.platform === "darwin") {
const homeDir = os.homedir();

// Common macOS user directories where apps store data
const macOSPaths = [
path.join(homeDir, "Library/Caches"), // Application caches
path.join(homeDir, "Library/Application Support"), // Application data
path.join(homeDir, "Library/Logs"), // Application logs
path.join(homeDir, "Library/Preferences"), // Application preferences
"/tmp" // System temporary files
];

// Add paths that exist
macOSPaths.forEach(dirPath => {
if (fsSync.existsSync(dirPath)) {
paths.push(dirPath);
}
});
}

return paths;
Expand Down
98 changes: 95 additions & 3 deletions src/renderer/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@
<v-icon left>mdi-broom</v-icon>
DevNullifier
</v-app-bar-title>

<v-spacer></v-spacer>

<v-menu>
<template v-slot:activator="{ props }">
<v-btn icon v-bind="props">
<v-icon>{{ getThemeIcon() }}</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item v-for="mode in themeModes" :key="mode.value" @click="setThemeMode(mode.value)"
:class="{ 'v-list-item--active': themeMode === mode.value }">
<template v-slot:prepend>
<v-icon>{{ mode.icon }}</v-icon>
</template>
<v-list-item-title>{{ mode.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app-bar>

<v-main>
Expand Down Expand Up @@ -32,18 +51,91 @@
</v-container>
</v-main>



<NotificationSnackbar v-model="showSnackbar" :text="snackbarText" :color="snackbarColor" />
</v-app>
</template>

<script setup>
import { ref } from 'vue'
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
Comment thread
Hermesiss marked this conversation as resolved.
import { useTheme } from 'vuetify'
import AppDataCleaner from './components/AppDataCleaner.vue'
import DeveloperCleaner from './components/DeveloperCleaner.vue'
import NotificationSnackbar from './components/NotificationSnackbar.vue'

// Theme functionality
const theme = useTheme()
const themeMode = ref('system') // 'light', 'dark', 'system'
let mediaQuery = null

// Theme mode options
const themeModes = [
{ value: 'light', title: 'Light', icon: 'mdi-brightness-7' },
{ value: 'dark', title: 'Dark', icon: 'mdi-brightness-4' },
{ value: 'system', title: 'System', icon: 'mdi-brightness-auto' }
]

// Get system theme preference
const getSystemTheme = () => {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}

// Apply theme based on mode
const applyTheme = () => {
if (themeMode.value === 'system') {
theme.global.name.value = getSystemTheme()
} else {
theme.global.name.value = themeMode.value
}
}

// Get appropriate icon for current theme
const getThemeIcon = () => {
if (themeMode.value === 'system') {
return 'mdi-brightness-auto'
}
return theme.global.name.value === 'dark' ? 'mdi-brightness-4' : 'mdi-brightness-7'
}

// Set theme mode
const setThemeMode = (mode) => {
themeMode.value = mode
applyTheme()
}

// Handle system theme changes
const handleSystemThemeChange = (e) => {
if (themeMode.value === 'system') {
theme.global.name.value = e.matches ? 'dark' : 'light'
}
}

// Load theme mode from localStorage on startup
onMounted(() => {
const savedThemeMode = localStorage.getItem('devnullifier-theme-mode')
if (savedThemeMode && ['light', 'dark', 'system'].includes(savedThemeMode)) {
themeMode.value = savedThemeMode
}

// Apply initial theme
applyTheme()

// Set up system theme change listener
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
mediaQuery.addEventListener('change', handleSystemThemeChange)
})

// Cleanup on unmount
onUnmounted(() => {
if (mediaQuery) {
mediaQuery.removeEventListener('change', handleSystemThemeChange)
}
})

// Watch for theme mode changes and save to localStorage
watch(themeMode, (newMode) => {
localStorage.setItem('devnullifier-theme-mode', newMode)
})

// Reactive state
const activeTab = ref('appdata')
const showSnackbar = ref(false)
Expand Down
Loading
Loading