feat: dist building

This commit is contained in:
2026-02-22 18:17:30 +01:00
parent 145b3ea0a6
commit 2d451dc1f0
5 changed files with 195 additions and 53 deletions

View File

@@ -120,10 +120,26 @@ npm run test:coverage
# Generates drizzle artifacts, builds main process and renderer # Generates drizzle artifacts, builds main process and renderer
npm run build npm run build
# Package distributables # Package app directory only (no installer)
npx electron-builder npm run package
# Package distributables for all supported systems
npm run dist
# Package by target platform
npm run dist:mac
npm run dist:win
npm run dist:linux
``` ```
### Distribution Notes
- Custom protocol `bds://` is declared in electron-builder metadata for packaged apps.
- macOS signing/notarization is scaffolded via `scripts/notarize.mjs` and runs when these env vars are set:
- `APPLE_ID`
- `APPLE_APP_SPECIFIC_PASSWORD`
- `APPLE_TEAM_ID`
### Database Utilities ### Database Utilities
```bash ```bash

108
package-lock.json generated
View File

@@ -45,11 +45,13 @@
"simple-git": "^3.31.1", "simple-git": "^3.31.1",
"snowball-stemmers": "^0.6.0", "snowball-stemmers": "^0.6.0",
"turndown": "^7.2.2", "turndown": "^7.2.2",
"uuid": "^13.0.0",
"vanilla-calendar-pro": "^3.1.0", "vanilla-calendar-pro": "^3.1.0",
"zod": "^4.3.6", "zod": "^4.3.6",
"zustand": "^5.0.11" "zustand": "^5.0.11"
}, },
"devDependencies": { "devDependencies": {
"@electron/notarize": "^3.1.0",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
@@ -75,7 +77,6 @@
"memfs": "^4.6.0", "memfs": "^4.6.0",
"tsx": "^4.6.0", "tsx": "^4.6.0",
"typescript": "^5.3.0", "typescript": "^5.3.0",
"uuid": "^13.0.0",
"vite": "^7.3.1", "vite": "^7.3.1",
"vitest": "^4.0.18", "vitest": "^4.0.18",
"wait-on": "^9.0.3" "wait-on": "^9.0.3"
@@ -1134,57 +1135,17 @@
} }
}, },
"node_modules/@electron/notarize": { "node_modules/@electron/notarize": {
"version": "2.5.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-3.1.1.tgz",
"integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", "integrity": "sha512-uQQSlOiJnqRkTL1wlEBAxe90nVN/Fc/hEmk0bqpKk8nKjV1if/tXLHKUPePtv9Xsx90PtZU8aidx5lAiOpjkQQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.1.1", "debug": "^4.4.0",
"fs-extra": "^9.0.1",
"promise-retry": "^2.0.1" "promise-retry": "^2.0.1"
}, },
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 22.12.0"
}
},
"node_modules/@electron/notarize/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@electron/notarize/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/@electron/notarize/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
} }
}, },
"node_modules/@electron/osx-sign": { "node_modules/@electron/osx-sign": {
@@ -6350,6 +6311,60 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/app-builder-lib/node_modules/@electron/notarize": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
"integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.1.1",
"fs-extra": "^9.0.1",
"promise-retry": "^2.0.1"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/app-builder-lib/node_modules/@electron/notarize/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/app-builder-lib/node_modules/@electron/notarize/node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/app-builder-lib/node_modules/@electron/notarize/node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/app-builder-lib/node_modules/ci-info": { "node_modules/app-builder-lib/node_modules/ci-info": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
@@ -14913,7 +14928,6 @@
"version": "13.0.0", "version": "13.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
"dev": true,
"funding": [ "funding": [
"https://github.com/sponsors/broofa", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan" "https://github.com/sponsors/ctavan"

View File

@@ -12,6 +12,11 @@
"start": "concurrently --kill-others \"npm run dev:renderer\" \"npm run start:electron\"", "start": "concurrently --kill-others \"npm run dev:renderer\" \"npm run start:electron\"",
"start:electron": "wait-on http://localhost:5173 && cross-env NODE_ENV=development node ./node_modules/electron/cli.js .", "start:electron": "wait-on http://localhost:5173 && cross-env NODE_ENV=development node ./node_modules/electron/cli.js .",
"build": "npm run lint && npm run db:generate && npm run build:main && npm run build:renderer", "build": "npm run lint && npm run db:generate && npm run build:main && npm run build:renderer",
"package": "npm run build && electron-builder --dir",
"dist": "npm run build && electron-builder",
"dist:mac": "npm run build && electron-builder --mac",
"dist:win": "npm run build && electron-builder --win",
"dist:linux": "npm run build && electron-builder --linux",
"build:main": "node ./node_modules/typescript/bin/tsc -p tsconfig.main.json", "build:main": "node ./node_modules/typescript/bin/tsc -p tsconfig.main.json",
"build:renderer": "node ./node_modules/vite/bin/vite.js build", "build:renderer": "node ./node_modules/vite/bin/vite.js build",
"start:prod": "node ./node_modules/electron/cli.js .", "start:prod": "node ./node_modules/electron/cli.js .",
@@ -29,6 +34,7 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@electron/notarize": "^3.1.0",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
@@ -54,7 +60,6 @@
"memfs": "^4.6.0", "memfs": "^4.6.0",
"tsx": "^4.6.0", "tsx": "^4.6.0",
"typescript": "^5.3.0", "typescript": "^5.3.0",
"uuid": "^13.0.0",
"vite": "^7.3.1", "vite": "^7.3.1",
"vitest": "^4.0.18", "vitest": "^4.0.18",
"wait-on": "^9.0.3" "wait-on": "^9.0.3"
@@ -96,6 +101,7 @@
"simple-git": "^3.31.1", "simple-git": "^3.31.1",
"snowball-stemmers": "^0.6.0", "snowball-stemmers": "^0.6.0",
"turndown": "^7.2.2", "turndown": "^7.2.2",
"uuid": "^13.0.0",
"vanilla-calendar-pro": "^3.1.0", "vanilla-calendar-pro": "^3.1.0",
"zod": "^4.3.6", "zod": "^4.3.6",
"zustand": "^5.0.11" "zustand": "^5.0.11"
@@ -108,6 +114,8 @@
"build": { "build": {
"appId": "com.bds.blogging-desktop-server", "appId": "com.bds.blogging-desktop-server",
"productName": "Blogging Desktop Server", "productName": "Blogging Desktop Server",
"asar": true,
"afterSign": "scripts/notarize.mjs",
"directories": { "directories": {
"output": "release" "output": "release"
}, },
@@ -122,14 +130,43 @@
"to": "drizzle" "to": "drizzle"
} }
], ],
"protocols": [
{
"name": "bDS Blogmark Links",
"schemes": [
"bds"
]
}
],
"win": { "win": {
"target": "nsis" "target": [
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
}
]
}, },
"mac": { "mac": {
"target": "dmg" "target": [
"dmg",
"zip"
],
"category": "public.app-category.productivity",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.inherit.plist"
}, },
"linux": { "linux": {
"target": "AppImage" "target": [
"AppImage",
"deb",
"rpm"
],
"category": "Utility"
} }
} }
} }

30
scripts/notarize.mjs Normal file
View File

@@ -0,0 +1,30 @@
import { notarize } from '@electron/notarize';
export default async function notarizeIfConfigured(context) {
const { electronPlatformName, appOutDir, packager } = context;
if (electronPlatformName !== 'darwin') {
return;
}
const appleId = process.env.APPLE_ID;
const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD;
const teamId = process.env.APPLE_TEAM_ID;
if (!appleId || !appleIdPassword || !teamId) {
console.log('[notarize] Skipped: APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID not set');
return;
}
const appName = packager.appInfo.productFilename;
const appPath = `${appOutDir}/${appName}.app`;
console.log(`[notarize] Submitting ${appPath}`);
await notarize({
appPath,
appleId,
appleIdPassword,
teamId,
});
console.log('[notarize] Completed');
}

View File

@@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest';
import fs from 'fs';
import path from 'path';
describe('package.json packaging configuration', () => {
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as Record<string, any>;
it('defines cross-platform distribution scripts', () => {
const scripts = packageJson.scripts as Record<string, string>;
expect(scripts['dist']).toBeTypeOf('string');
expect(scripts['dist:mac']).toBeTypeOf('string');
expect(scripts['dist:win']).toBeTypeOf('string');
expect(scripts['dist:linux']).toBeTypeOf('string');
});
it('registers bds protocol for packaged apps', () => {
const build = packageJson.build as Record<string, any>;
const protocols = build.protocols as Array<{ name: string; schemes: string[] }>;
expect(Array.isArray(protocols)).toBe(true);
expect(protocols.some((entry) => Array.isArray(entry.schemes) && entry.schemes.includes('bds'))).toBe(true);
});
it('configures macOS, Windows, and Linux targets', () => {
const build = packageJson.build as Record<string, any>;
expect(build.mac).toBeTruthy();
expect(build.win).toBeTruthy();
expect(build.linux).toBeTruthy();
expect(build.mac.target).toBeTruthy();
expect(build.win.target).toBeTruthy();
expect(build.linux.target).toBeTruthy();
});
it('keeps runtime modules in dependencies (not devDependencies)', () => {
const dependencies = packageJson.dependencies as Record<string, string>;
const devDependencies = packageJson.devDependencies as Record<string, string>;
expect(dependencies.uuid).toBeTypeOf('string');
expect(devDependencies.uuid).toBeUndefined();
});
});