diff --git a/README.md b/README.md index 3a405e2..1e992f7 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@

Angular 21 Electron 37 - Prisma + Prisma SQLite License: GPL v3

@@ -55,9 +55,15 @@ ## 📥 Installation -### Windows +### Download -Download the latest installer from the [Releases](https://github.com/altaskur/OpenTimeTracker/releases) page. +Download the latest installer for your platform from the [Releases](https://github.com/altaskur/OpenTimeTracker/releases) page: + +| Platform | Format | Architecture | +| -------- | ----------------------- | -------------------------- | +| Windows | `.exe` (NSIS installer) | x64 | +| macOS | `.dmg` | x64, arm64 (Apple Silicon) | +| Linux | `.AppImage`, `.deb` | x64 | ### Build from Source @@ -90,7 +96,10 @@ npm run dev | `npm start` | Start Angular dev server (port 4200) | | `npm run dev` | Build and run Electron app | | `npm run build` | Production build | +| `npm run dist` | Generate installer for current OS | | `npm run dist:win` | Generate Windows installer | +| `npm run dist:mac` | Generate macOS installer (DMG) | +| `npm run dist:linux` | Generate Linux installers | | `npm test` | Run Angular tests | | `npm run test:electron` | Run Electron tests | | `npm run lint` | Run ESLint | diff --git a/electron/src/utils/paths.spec.ts b/electron/src/utils/paths.spec.ts index 07efc0a..6c954d1 100644 --- a/electron/src/utils/paths.spec.ts +++ b/electron/src/utils/paths.spec.ts @@ -1,4 +1,13 @@ -import { describe, it, expect, beforeEach, vi, type Mock, type Mocked } from 'vitest'; +import { + describe, + it, + expect, + beforeEach, + afterEach, + vi, + type Mock, + type Mocked, +} from 'vitest'; import path from 'node:path'; import { app } from 'electron'; import { @@ -17,9 +26,25 @@ import { */ describe('Paths Utility', () => { const mockApp = app as Mocked; + const originalResourcesPath = process.resourcesPath; beforeEach(() => { vi.clearAllMocks(); + // Mock process.resourcesPath for packaged mode tests + Object.defineProperty(process, 'resourcesPath', { + value: '/mock/app/resources', + configurable: true, + writable: true, + }); + }); + + afterEach(() => { + // Restore original value + Object.defineProperty(process, 'resourcesPath', { + value: originalResourcesPath, + configurable: true, + writable: true, + }); }); describe('isPackaged', () => { @@ -150,9 +175,6 @@ describe('Paths Utility', () => { value: true, configurable: true, }); - (mockApp.getPath as Mock).mockReturnValue( - 'C:\\Program Files\\OpenTimeTracker\\OpenTimeTracker.exe', - ); const result = getIndexPath(); @@ -180,9 +202,6 @@ describe('Paths Utility', () => { value: true, configurable: true, }); - (mockApp.getPath as Mock).mockReturnValue( - 'C:\\Program Files\\OpenTimeTracker\\OpenTimeTracker.exe', - ); const result = getPreloadPath(); @@ -210,14 +229,11 @@ describe('Paths Utility', () => { value: true, configurable: true, }); - (mockApp.getPath as Mock).mockReturnValue( - 'C:\\Program Files\\OpenTimeTracker\\OpenTimeTracker.exe', - ); const result = getTemplateDatabasePath(); expect(result).toContain('resources'); - expect(result).toContain('app.asar'); + expect(result).toContain('app.asar.unpacked'); expect(result).toContain('template.db'); }); }); diff --git a/electron/src/utils/paths.ts b/electron/src/utils/paths.ts index fcef0d0..d1c1062 100644 --- a/electron/src/utils/paths.ts +++ b/electron/src/utils/paths.ts @@ -55,12 +55,12 @@ export const getBackupPath = (): string => { * Gets the path to the Angular index.html file. * In development: dist/OpenTimeTracker/browser/index.html * In production: resources/app.asar/dist/OpenTimeTracker/browser/index.html + * Note: Uses process.resourcesPath for cross-platform compatibility (Windows/macOS/Linux) */ export const getIndexPath = (): string => { if (isPackaged()) { return path.join( - path.dirname(app.getPath('exe')), - 'resources', + process.resourcesPath, 'app.asar', 'dist', 'OpenTimeTracker', @@ -80,12 +80,12 @@ export const getIndexPath = (): string => { /** * Gets the path to the preload script. + * Note: Uses process.resourcesPath for cross-platform compatibility (Windows/macOS/Linux) */ export const getPreloadPath = (): string => { if (isPackaged()) { return path.join( - path.dirname(app.getPath('exe')), - 'resources', + process.resourcesPath, 'app.asar', 'dist', 'electron', @@ -100,14 +100,14 @@ export const getPreloadPath = (): string => { * Gets the path to the template database file. * Used to initialize a new database with pre-created schema. * In development: prisma/template.db - * In production: resources/app.asar/prisma/template.db + * In production: resources/app.asar.unpacked/prisma/template.db + * Note: Template is in asarUnpack so we use app.asar.unpacked for cross-platform compatibility */ export const getTemplateDatabasePath = (): string => { if (isPackaged()) { return path.join( - path.dirname(app.getPath('exe')), - 'resources', - 'app.asar', + process.resourcesPath, + 'app.asar.unpacked', 'prisma', 'template.db', ); diff --git a/package.json b/package.json index d95d7f4..13a93e9 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,9 @@ "dist": "npm run build && electron-builder", "dist:win": "npm run build && electron-builder --win", "dist:linux": "npm run build && electron-builder --linux", + "dist:mac": "npm run build && electron-builder --mac", "pack": "electron-builder --dir", - "prisma:generate": "prisma generate", + "prisma:generate": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma generate", "prisma:push": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma db push", "prisma:studio": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma studio", "prisma:migrate": "cross-env DATABASE_URL=file:./dist/data/timetracker.db prisma migrate dev", @@ -57,7 +58,8 @@ "node_modules/@prisma/client/**/*", "node_modules/@prisma/adapter-better-sqlite3/**/*", "node_modules/better-sqlite3/**/*", - "dist/electron/generated/**/*" + "dist/electron/generated/**/*", + "prisma/template.db" ], "win": { "target": [ @@ -99,6 +101,34 @@ "maintainer": "altaskur", "synopsis": "Time tracking application", "description": "Free and open source time tracking application for developers and teams" + }, + "mac": { + "target": [ + { + "target": "dmg", + "arch": [ + "x64", + "arm64" + ] + } + ], + "icon": "public/icon-512.png", + "category": "public.app-category.productivity" + }, + "dmg": { + "title": "${productName} ${version}", + "contents": [ + { + "x": 130, + "y": 220 + }, + { + "x": 410, + "y": 220, + "type": "link", + "path": "/Applications" + } + ] } }, "prettier": { diff --git a/prisma.config.ts b/prisma.config.ts index 941bebb..22cf7be 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,7 +1,9 @@ import path from 'node:path'; -import { defineConfig } from 'prisma/config'; +import { defineConfig, env } from 'prisma/config'; export default defineConfig({ - earlyAccess: true, schema: path.join(import.meta.dirname, 'prisma', 'schema.prisma'), + datasource: { + url: env('DATABASE_URL') ?? 'file:./dist/data/timetracker.db', + }, }); diff --git a/prisma/template.db b/prisma/template.db index 8c07e95..3282ac5 100644 Binary files a/prisma/template.db and b/prisma/template.db differ diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000..188187d Binary files /dev/null and b/public/icon-512.png differ diff --git a/scripts/update-db-template.mjs b/scripts/update-db-template.mjs index 81ae375..7e03053 100644 --- a/scripts/update-db-template.mjs +++ b/scripts/update-db-template.mjs @@ -17,9 +17,9 @@ const templatePath = join(__dirname, "..", "prisma", "template.db"); console.log("Regenerating database template...\n"); try { - console.log("1. Resetting database with migrations..."); + console.log("1. Resetting database with schema..."); execSync( - "cross-env DATABASE_URL=file:./dist/data/timetracker.db npx prisma migrate reset --force --skip-seed --skip-generate", + "cross-env DATABASE_URL=file:./dist/data/timetracker.db npx prisma db push --force-reset --accept-data-loss", { stdio: "inherit", cwd: join(__dirname, ".."),