diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml new file mode 100644 index 0000000..0379a7f --- /dev/null +++ b/.github/actions/setup-build-env/action.yml @@ -0,0 +1,52 @@ +name: Setup build environment +description: > + Install OS-native electron-builder dependencies (Linux), Node.js, and restore the + Electron/electron-builder download caches. Shared by ci.yml and release.yml so the + Linux package list, Flatpak runtime version, and cache paths have one source of truth. + +runs: + using: composite + steps: + # xvfb is installed unconditionally here (even though only ci.yml's E2E step uses it) + # so both workflows share one Linux package list instead of two lists that can drift. + # It's a tiny package; installing it unused in release.yml costs nothing. + # rpm is electron-builder's packaging tool for the `rpm` Linux target — ubuntu-latest + # doesn't ship it by default (see electron-builder's multi-platform-build docs). + - name: Install Linux native dependencies (electron-builder, xvfb for headless E2E) + if: runner.os == 'Linux' + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf flatpak flatpak-builder xvfb rpm + + - name: Install Flatpak runtime (electron-builder flatpak target) + if: runner.os == 'Linux' + shell: bash + run: | + sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + sudo flatpak install --system -y flathub org.freedesktop.Platform//25.08 org.freedesktop.Sdk//25.08 org.electronjs.Electron2.BaseApp//25.08 + + - name: Setup Node.js 24 + uses: actions/setup-node@v7 + with: + node-version: '24' + cache: 'npm' + + # actions/setup-node's cache:npm only covers the npm registry cache — it doesn't touch + # the Electron binary zip (~100-200MB) or electron-builder's own toolchain downloads + # (NSIS, winCodeSign, etc.), which live in a separate cache dir and get re-downloaded on + # every run otherwise. Cache path list covers all 3 OSes; actions/cache skips whichever + # paths don't exist on the current runner, and the OS-prefixed key keeps them separate. + - name: Cache Electron/electron-builder downloads + uses: actions/cache@v6 + with: + path: | + ~/.cache/electron + ~/.cache/electron-builder + ~/Library/Caches/electron + ~/Library/Caches/electron-builder + ~\AppData\Local\electron\Cache + ~\AppData\Local\electron-builder\Cache + key: ${{ runner.os }}-electron-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-electron- diff --git a/.github/scripts/generate-changelog.mjs b/.github/scripts/generate-changelog.mjs new file mode 100644 index 0000000..2ea67ff --- /dev/null +++ b/.github/scripts/generate-changelog.mjs @@ -0,0 +1,113 @@ +#!/usr/bin/env node +// Generates the GitHub release notes body for the range between the last git tag (or the +// start of history, if this is the first release) and HEAD, grouped by conventional-commit +// type. Writes CHANGELOG_BODY.md for use as the release notes — nothing is persisted to the +// repo (no CHANGELOG.md); the GitHub release itself is the changelog's home. +// +// Usage: node generate-changelog.mjs +// newVersion e.g. "1.2.3" (no leading "v") +// owner/repo e.g. "cchandurkar/electron-angular-template" (for the compare link) + +import { execSync } from 'node:child_process'; +import { writeFileSync } from 'node:fs'; + +const [, , newVersion, repoSlug] = process.argv; +if (!newVersion || !repoSlug) { + console.error('Usage: generate-changelog.mjs '); + process.exit(1); +} + +function sh(cmd) { + return execSync(cmd, { encoding: 'utf8' }).trim(); +} + +const MAX_ENTRIES = 150; +const TYPES = [ + ['feat', 'Features'], + ['fix', 'Bug Fixes'], + ['refactor', 'Refactoring'], + ['perf', 'Performance'], + ['revert', 'Reverts'] +]; + +const typeLabel = new Map(TYPES); + +let lastTag = ''; +try { + lastTag = sh('git describe --tags --abbrev=0'); +} catch { + // No tags yet — this is the first release; changelog covers full history. +} + +const range = lastTag ? `${lastTag}..HEAD` : ''; +const log = sh(`git log ${range} --format=%s`.trim()); +const subjects = log ? log.split('\n') : []; + +// Matches this repo's commit convention: `type(scope): description` (see root AGENTS.md). +const pattern = /^(\w+)(\(.+?\))?(!)?: (.+)$/; +const groups = new Map(); +let matchedCount = 0; + +for (const subject of subjects) { + const m = subject.match(pattern); + if (!m) continue; + const [, type, scopeRaw, , message] = m; + if (!typeLabel.has(type)) continue; + matchedCount++; + const scope = scopeRaw ? scopeRaw.slice(1, -1) : null; + const line = scope ? `- **${scope}**: ${message}` : `- ${message}`; + if (!groups.has(type)) groups.set(type, []); + groups.get(type).push(line); +} + +let truncatedNote = ''; +if (matchedCount > MAX_ENTRIES) { + let kept = 0; + for (const [type] of TYPES) { + const lines = groups.get(type); + if (!lines) continue; + if (kept >= MAX_ENTRIES) { + groups.delete(type); + continue; + } + const remaining = MAX_ENTRIES - kept; + if (lines.length > remaining) { + groups.set(type, lines.slice(0, remaining)); + } + kept += groups.get(type).length; + } + truncatedNote = `\n_…and ${matchedCount - MAX_ENTRIES} more change(s) not shown here — see the full changelog link below._\n`; +} + +const sections = TYPES.filter(([type]) => groups.has(type)) + .map(([type, label]) => `### ${label}\n\n${groups.get(type).join('\n')}`) + .join('\n\n'); + +const compareLink = lastTag + ? `**Full Changelog**: https://github.com/${repoSlug}/compare/${lastTag}...v${newVersion}` + : `**Full Changelog**: https://github.com/${repoSlug}/commits/v${newVersion}`; + +// Fallback for forks that don't use Conventional Commits: `sections` is only empty here when +// zero commits matched the type-prefixed pattern above. If commits exist but none matched, +// list them verbatim instead of silently claiming "no changes" when real work happened. +let sectionsBody = sections; +let noteSuffix = truncatedNote; +if (!sections && subjects.length > 0) { + let rawList = subjects; + if (rawList.length > MAX_ENTRIES) { + noteSuffix = `\n_…and ${rawList.length - MAX_ENTRIES} more change(s) not shown here — see the full changelog link below._\n`; + rawList = rawList.slice(0, MAX_ENTRIES); + } + sectionsBody = `### Changes\n\n${rawList.map(subject => `- ${subject}`).join('\n')}`; +} + +const body = + (sectionsBody || '_No user-facing changes recorded since the last release._') + + noteSuffix + + `\n\n${compareLink}\n`; + +writeFileSync('CHANGELOG_BODY.md', body); + +console.log( + `Release notes generated for v${newVersion} (${matchedCount} matched commit(s), lastTag=${lastTag || ''}).` +); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77f3ca4..3687513 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,17 +57,8 @@ jobs: - name: Checkout code uses: actions/checkout@v7 - - name: Install Linux native dependencies (electron-builder) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf flatpak flatpak-builder xvfb - - - name: Install Flatpak runtime (electron-builder flatpak target) - if: runner.os == 'Linux' - run: | - sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - sudo flatpak install --system -y flathub org.freedesktop.Platform//25.08 org.freedesktop.Sdk//25.08 org.electronjs.Electron2.BaseApp//25.08 + - name: Setup build environment (Node.js, Linux native deps, caches) + uses: ./.github/actions/setup-build-env # ubuntu-latest (Ubuntu 24.04+) restricts unprivileged user namespaces via AppArmor by # default, which breaks Electron's sandbox init entirely (electron.launch() throws before @@ -77,31 +68,6 @@ jobs: if: runner.os == 'Linux' run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - - name: Setup Node.js 24 - uses: actions/setup-node@v7 - with: - node-version: '24' - cache: 'npm' - - # actions/setup-node's cache:npm only covers the npm registry cache — it doesn't touch - # the Electron binary zip (~100-200MB) or electron-builder's own toolchain downloads - # (NSIS, winCodeSign, etc.), which live in a separate cache dir and get re-downloaded on - # every run otherwise. Cache path list covers all 3 OSes; actions/cache skips whichever - # paths don't exist on the current runner, and the OS-prefixed key keeps them separate. - - name: Cache Electron/electron-builder downloads - uses: actions/cache@v6 - with: - path: | - ~/.cache/electron - ~/.cache/electron-builder - ~/Library/Caches/electron - ~/Library/Caches/electron-builder - ~\AppData\Local\electron\Cache - ~\AppData\Local\electron-builder\Cache - key: ${{ runner.os }}-electron-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-electron- - - name: Install dependencies run: npm ci diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..eb7e956 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,90 @@ +name: Create Release + +on: + workflow_dispatch: + inputs: + bump: + description: 'Version bump type' + required: true + type: choice + options: [patch, minor, major] + +permissions: + contents: write + actions: write + +jobs: + create-release: + name: Bump, tag, and kick off release build + runs-on: ubuntu-latest + + steps: + # Anyone with write access can trigger workflow_dispatch; this adds a second gate + # since cutting a release also pushes to main and dispatches the build/publish + # pipeline. Adjust or drop if your team wants other collaborators to release too. + - name: Guard — owner only + if: github.actor != github.repository_owner + run: | + echo "Only the repository owner (${{ github.repository_owner }}) can trigger releases." + exit 1 + + - name: Checkout code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup Node.js 24 + uses: actions/setup-node@v7 + with: + node-version: '24' + cache: 'npm' + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Root package.json is the single source of truth for the app version. + # packages/main/electron-builder.config.js reads it directly (via `extraMetadata.version`) + # at build time, so packages/main, packages/renderer, and packages/shared keep their own + # fixed internal version (0.0.1) — nothing reads those for versioning. See README > Releasing. + - name: Bump root version + id: version + env: + BUMP: ${{ inputs.bump }} + run: | + npm version "$BUMP" --no-git-tag-version + NEW_VERSION=$(node -p "require('./package.json').version") + npm install + echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT" + + - name: Generate changelog + run: node .github/scripts/generate-changelog.mjs "${{ steps.version.outputs.version }}" "${{ github.repository }}" + + - name: Commit and tag + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + git add package.json package-lock.json + git commit -m "chore(release): v${VERSION}" + git tag "v${VERSION}" + git push origin HEAD:main + git push origin "v${VERSION}" + + # Created as a draft: electron-builder's own GitHub publish step (triggered below) + # reuses an existing draft release unconditionally and just uploads its assets to it, + # so our changelog stays as the notes instead of being overwritten. release.yml's + # `finalize` job flips it to published once all 3 OS legs have uploaded successfully. + - name: Create draft GitHub release with changelog notes + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ steps.version.outputs.version }} + run: gh release create "v${VERSION}" --draft --title "v${VERSION}" --notes-file CHANGELOG_BODY.md --repo "${{ github.repository }}" + + # A push made with the default GITHUB_TOKEN does not trigger other workflows' `push` + # events (GitHub's anti-recursion rule) — so the tag push above won't start release.yml + # on its own. workflow_dispatch is exempt from that rule, so we call it explicitly. + - name: Trigger release build + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run release.yml --ref "v${{ steps.version.outputs.version }}" --repo "${{ github.repository }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ac3587f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,71 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + name: Build & publish (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + permissions: + contents: write + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup build environment (Node.js, Linux native deps, caches) + uses: ./.github/actions/setup-build-env + + - name: Install dependencies + run: npm ci + + # Code signing / notarization secrets are scoped to the macOS leg only (via the + # runner.os ternary). CSC_LINK is a *shared* electron-builder var also read on Windows; + # leaving it unset there prevents a mac .p12 from being fed into Windows Authenticode + # signing by mistake. See README > Releasing for what each secret is and how to set it up. + # + # GH_TOKEN authenticates electron-builder's own GitHub publish step (package:release runs + # with --publish always): each OS leg builds its installers and uploads them directly to + # the release matching this repo's package.json version — no separate publish job needed. + - name: Build & package (release) + env: + CSC_LINK: ${{ runner.os == 'macOS' && secrets.MAC_CERTIFICATE_P12_BASE64 || '' }} + CSC_KEY_PASSWORD: ${{ runner.os == 'macOS' && secrets.MAC_CERTIFICATE_PASSWORD || '' }} + APPLE_API_KEY: ${{ runner.os == 'macOS' && secrets.APPLE_API_KEY_BASE64 || '' }} + APPLE_API_KEY_ID: ${{ runner.os == 'macOS' && secrets.APPLE_API_KEY_ID || '' }} + APPLE_API_ISSUER: ${{ runner.os == 'macOS' && secrets.APPLE_API_ISSUER || '' }} + GH_TOKEN: ${{ github.token }} + run: npm run release + + finalize: + name: Publish release (undraft) + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + # electron-builder.json intentionally leaves `publish.releaseType` unset, which + # electron-builder defaults to "draft" — each of the 3 OS legs above reuses that same + # draft and uploads its own assets to it. Only once all 3 have succeeded (this job's + # `needs: build`) do we flip it to published, so a release never goes live half-built. + - name: Publish the release created by create-release.yml + env: + GH_TOKEN: ${{ github.token }} + run: gh release edit "${{ github.ref_name }}" --draft=false --repo "${{ github.repository }}" diff --git a/.gitignore b/.gitignore index a615ea9..6bac981 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,7 @@ opencode.json # OS .DS_Store -Thumbs.db \ No newline at end of file +Thumbs.db + +# Generated by .github/scripts/generate-changelog.mjs — ephemeral release-notes body, never committed +CHANGELOG_BODY.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index c7f0f2b..45a8063 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,7 @@ Package-specific rules live next to the code they govern. Read the relevant one - Keep `package-lock.json` in sync with dependency changes (`npm install`, not hand edits). Do not bump Electron, Angular, or TypeScript majors as a side effect of another change; TypeScript is pinned to `~6.0.3` across all packages on purpose. - Commit messages follow Conventional Commits: `type(scope): description`, where scope is usually `main`, `renderer`, `shared`, `e2e`, or `docs`. See [CONTRIBUTING.md](CONTRIBUTING.md#commit-guidelines). - Formatting is Prettier; run `npm run format` before committing so `format:check` passes in CI. Files use kebab-case, types PascalCase with no `I` prefix. -- Do not hardcode this template repository as an app's publishing destination or update feed; adopters configure their own. `electron-builder.json` keeps `publish: null`. +- Releases are cut via `.github/workflows/create-release.yml` (`workflow_dispatch`, owner-only guard): it bumps all 4 `package.json` versions (root + 3 workspaces) in lockstep, generates release notes from conventional commits, commits, tags, pushes, creates a draft GitHub release, then dispatches `.github/workflows/release.yml` (a plain tag push can't — GITHUB_TOKEN-authored pushes don't trigger other workflows). `release.yml` builds/signs/notarizes/publishes per OS and its `finalize` job un-drafts the release once all 3 legs succeed. No `CHANGELOG.md` is maintained in the repo — the GitHub release notes are the changelog. Don't hand-edit package versions outside that workflow. +- `electron-builder.config.js` publishes to GitHub Releases (`publish.provider: "github"`, no hardcoded `owner`/`repo` — electron-builder auto-detects these from the repo's own git remote, so each fork publishes to its own releases). It's JS, not JSON, specifically so `extraMetadata.version` can read the root `package.json` version at build time — `packages/main/package.json`'s own version is unused (fixed at `0.0.1`), so don't "fix" that drift. Don't add a literal `owner`/`repo` pointing at a specific fork, and don't wire actual auto-update consumption (`electron-updater` in `packages/main`) without the user asking for it — the release pipeline only builds and publishes installers today; see README > Releasing. - When you change behavior, add or update a test at the boundary that proves it (unit test in the owning package, E2E for cross-process flows). Tests should assert behavior, not only that something can be constructed. - If you touch the example (note editor, `note:*` channels, `NoteData`), keep it minimal and removable. Do not grow the example into a product; prefer improving the template's structure, docs, or defaults. diff --git a/README.md b/README.md index 4508a6d..703ae02 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ An Electron starter for developers who want to build a desktop app with Angular. - [Project Layout](#%EF%B8%8F-project-layout) - [Add an IPC Channel](#-add-an-ipc-channel) - [Make It Yours](#-make-it-yours) +- [Releasing (macOS notarization)](#-releasing-macos-notarization) - [Available Scripts](#%EF%B8%8F-available-scripts) - [Troubleshooting](#-troubleshooting) - [How This Compares](#-how-this-compares) @@ -85,7 +86,7 @@ packages/ │ │ ├── storage.ts userData file storage │ │ └── preload/ contextBridge script, bundled to CJS for the sandbox │ ├── assets/icons/ App icons for electron-builder -│ └── electron-builder.json +│ └── electron-builder.config.js ├── renderer/ Angular app (browser sandbox, no Node access) │ └── src/app/ │ ├── components/ Standalone components (note editor, header) @@ -146,7 +147,7 @@ It updates package and repository metadata, the app's display name and ID, the i Then replace the app icons in [`packages/main/assets/icons`](packages/main/assets/icons), the renderer's [`favicon.ico`](packages/renderer/public/favicon.ico), and the generic HTML title in [`packages/renderer/src/index.html`](packages/renderer/src/index.html). Remove the note example once you've used it to understand the wiring. -Before distributing an app, set the identity and release settings you need, including signing and notarization where applicable. The template intentionally does not configure a signing identity, an update server, or automatic updates. +Before distributing an app, set the identity and release settings you need, including signing and notarization where applicable. The template intentionally does not configure a signing identity, an update server, or automatic updates — see [Releasing](#-releasing-macos-notarization) below for the macOS signing/notarization workflow this template ships with. ### What is example, what is template @@ -162,28 +163,60 @@ Everything about notes exists to demonstrate the wiring and can be deleted: The frameless header with custom window controls is also a design choice, not a requirement. If you prefer a native title bar, set `frame: true` in `window.ts` and drop the header component. -Before distributing an app, set the identity and release settings you need, including signing and notarization where applicable. The template intentionally does not configure a signing identity, an update server, or automatic updates. +## 🚀 Versioning and Release Management + +This template ships with the release management workflow. Cutting a release is one manual step; everything after that is automatic: + +**Actions tab → [Create Release](../../actions/workflows/create-release.yml) → Run workflow → pick `patch`/`minor`/`major`.** + +That workflow ( [`.github/workflows/create-release.yml`](.github/workflows/create-release.yml) ) bumps the version, generates changelog and creates the Github release as draft. Then it explicitely dispatches [`release.yml`](.github/workflows/release.yml) workflow that builds installers for macOS, Windows, and Linux and publishes them to the release. + +This release workflow only bumps the root [`package.json`](package.json) version. All `packages/*` versions remain unchanged as internal-only. [`./packages/main/electron-builder.config.js`](./packages/main/electron-builder.config.js) reads the root package version. + +Artifacts produced: + +| OS | Installers | +| ------- | ------------------------------------------------------ | +| MacOs | `.dmg` + `.zip` | +| Windows | `.exe` (NSIS installer + portable), | +| Linux | `.deb` + `.AppImage` + `.rpm` + `.tar.gz` + `.flatpak` | + +The macOS leg additionally signs with a Developer ID Application certificate and notarizes with Apple's `notarytool`, gated on these repository secrets (Settings → Secrets and variables → Actions). Without them the macOS build step fails; Linux and Windows builds don't need them and succeed regardless: + +| Secret | What it is | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MAC_CERTIFICATE_P12_BASE64` | Your Developer ID Application certificate + key (signs the `.app`/`.dmg`/`.zip`), exported as `.p12`, base64-encoded (`base64 -i cert.p12 \| pbcopy`) | +| `MAC_CERTIFICATE_PASSWORD` | The export password for that `.p12` | +| `APPLE_API_KEY_BASE64` | An App Store Connect API key (`.p8`), base64-encoded | +| `APPLE_API_KEY_ID` | The key ID shown next to that API key in App Store Connect | +| `APPLE_API_ISSUER` | Your App Store Connect issuer ID | + +Notes: + +- You need an active [Apple Developer Program](https://developer.apple.com/programs/) membership to create the certificate and API key above. Read more about [macOs Notarization](https://www.electron.build/v26/docs/features/code-signing/notarization/). +- `electron-builder.json` keeps `mac.notarize: false` so local `npm run package` stays fast and unsigned for smoke-testing installers. `packages/main/package.json`'s `package:release` script overrides that to `true` via `-c.mac.notarize=true` — you don't need to edit the config file. ## 🛠️ Available Scripts Run these commands from the repository root: -| Command | Description | -| ---------------------- | -------------------------------------------------------- | -| `npm start` | Start development mode (Angular + Electron) | -| `npm run build` | Compile all packages (no installers) | -| `npm run package` | Compile + package into installers (`.dmg`/`.exe`/etc.) | -| `npm run clean` | Clean all build artifacts | -| `npm run lint` | Lint all packages | -| `npm run lint:fix` | Fix linting issues in all packages | -| `npm run format` | Format code with Prettier | -| `npm run format:check` | Check code formatting | -| `npm run test` | Run tests in all packages | -| `npm run typecheck` | Type-check all packages | -| `npm run verify` | Run format:check + lint + typecheck + tests — same as CI | -| `npm run dev:debug` | Start development mode with remote debugging (port 9222) | - -The CI workflow runs checks and packaging on macOS, Windows, and Linux. +| Command | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------ | +| `npm start` | Start development mode (Angular + Electron) | +| `npm run build` | Compile all packages (no installers) | +| `npm run package` | Compile + package into installers (`.dmg`/`.exe`/etc.) | +| `npm run release` | Like `package`, plus macOS notarization (used by CI — see [Releasing](#-releasing-macos-notarization)) | +| `npm run clean` | Clean all build artifacts | +| `npm run lint` | Lint all packages | +| `npm run lint:fix` | Fix linting issues in all packages | +| `npm run format` | Format code with Prettier | +| `npm run format:check` | Check code formatting | +| `npm run test` | Run tests in all packages | +| `npm run typecheck` | Type-check all packages | +| `npm run verify` | Run format:check + lint + typecheck + tests — same as CI | +| `npm run dev:debug` | Start development mode with remote debugging (port 9222) | + +The CI workflow runs checks and packaging on macOS, Windows, and Linux. The release workflow (tag push) builds and notarizes installers and attaches them to a GitHub Release — see [Releasing](#-releasing-macos-notarization). ## 🧯 Troubleshooting @@ -215,7 +248,7 @@ Launch Electron with the `packages/main` directory, not the compiled `index.js` #### `npm run package` fails on Linux -The default Linux targets include Flatpak, which needs `flatpak`, `flatpak-builder`, and the `org.freedesktop.Platform` 25.08 runtime installed. See the Linux steps in [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for the exact commands, or remove the `flatpak` target from `packages/main/electron-builder.json` if you don't need it. +The default Linux targets include Flatpak, which needs `flatpak`, `flatpak-builder`, and the `org.freedesktop.Platform` 25.08 runtime installed. See the Linux steps in [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for the exact commands, or remove the `flatpak` target from `packages/main/electron-builder.config.js` if you don't need it. #### Default Electron icon in the macOS Dock during development diff --git a/packages/main/electron-builder.config.js b/packages/main/electron-builder.config.js new file mode 100644 index 0000000..8b0daf0 --- /dev/null +++ b/packages/main/electron-builder.config.js @@ -0,0 +1,52 @@ +// Electron-builder config as JS (not JSON) so `extraMetadata.version` can read the version +// straight from the repo root's package.json at build time. + +import path from 'node:path'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootPackageJson = JSON.parse( + readFileSync(path.join(__dirname, '../../package.json'), 'utf8') +); + +export default { + productName: 'Electron Angular Template', + directories: { + output: '../../build/', + buildResources: 'assets/icons' + }, + publish: { + provider: 'github' + }, + extraMetadata: { + version: rootPackageJson.version + }, + artifactName: '${productName}-v${version}.${ext}', + asar: true, + forceCodeSigning: false, + npmRebuild: false, + files: ['./dist/**/*', './assets/**/*', './package.json'], + win: { + target: ['nsis', 'portable'] + }, + nsis: { + artifactName: '${productName}-v${version}-Setup.${ext}' + }, + linux: { + target: ['deb', 'AppImage', 'rpm', 'tar.gz', { target: 'flatpak', arch: ['x64'] }] + }, + flatpak: { + runtime: 'org.freedesktop.Platform', + runtimeVersion: '25.08', + sdk: 'org.freedesktop.Sdk', + base: 'org.electronjs.Electron2.BaseApp', + baseVersion: '25.08' + }, + mac: { + hardenedRuntime: true, + gatekeeperAssess: false, + target: ['dmg', 'zip'], + notarize: false + } +}; diff --git a/packages/main/electron-builder.json b/packages/main/electron-builder.json deleted file mode 100644 index 97709b4..0000000 --- a/packages/main/electron-builder.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "productName": "Electron Angular Template", - "directories": { - "output": "../../build/", - "buildResources": "assets/icons" - }, - "publish": null, - "artifactName": "${productName}-v${version}.${ext}", - "asar": true, - "forceCodeSigning": false, - "npmRebuild": false, - "files": ["./dist/**/*", "./assets/**/*", "./package.json"], - "win": { - "target": ["nsis", "portable"] - }, - "nsis": { - "artifactName": "${productName}-v${version}-Setup.${ext}" - }, - "linux": { - "target": ["deb", "AppImage", { "target": "flatpak", "arch": ["x64"] }] - }, - "flatpak": { - "runtime": "org.freedesktop.Platform", - "runtimeVersion": "25.08", - "sdk": "org.freedesktop.Sdk", - "base": "org.electronjs.Electron2.BaseApp", - "baseVersion": "25.08" - }, - "mac": { - "hardenedRuntime": true, - "gatekeeperAssess": false, - "target": ["dmg"], - "notarize": false - } -} diff --git a/packages/main/package.json b/packages/main/package.json index 4ac7ef8..6ff2ac0 100644 --- a/packages/main/package.json +++ b/packages/main/package.json @@ -1,15 +1,15 @@ { "name": "main", "productName": "Electron Angular Template", - "version": "0.1.0", + "version": "0.0.1", "private": true, "description": "A template for building modern Electron apps with Angular", "main": "dist/main/index.js", "type": "module", "scripts": { "build": "npm run tsc:build && npm run preload:build", - "package": "npm run build && electron-builder --publish never", - "package:release": "npm run build && electron-builder --publish never -c.mac.notarize=true", + "package": "npm run build && electron-builder --config electron-builder.config.js --publish never", + "package:release": "npm run build && electron-builder --config electron-builder.config.js --publish always -c.mac.notarize=true", "serve": "wait-on tcp:4200 && npm-run-all --parallel tsc:watch preload:watch electron:launch", "electron:launch": "wait-on dist/main/index.js dist/main/preload/index.js && electron . --serve", "tsc:watch": "tsc -p tsconfig.json --outDir ./dist/main --watch", diff --git a/packages/renderer/package.json b/packages/renderer/package.json index 6d0abcf..737eac5 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json @@ -1,6 +1,6 @@ { "name": "renderer", - "version": "0.0.0", + "version": "0.0.1", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/packages/shared/package.json b/packages/shared/package.json index 54cf2c0..7f1d489 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@local/shared", - "version": "0.1.0", + "version": "0.0.1", "private": true, "description": "Utility functions and models shared between main and renderer", "license": "MIT", diff --git a/scripts/rebrand.mjs b/scripts/rebrand.mjs index c02b4ed..0b1e5fb 100644 --- a/scripts/rebrand.mjs +++ b/scripts/rebrand.mjs @@ -201,13 +201,13 @@ export async function applyRebrand(answers, root = ROOT) { changed.push('packages/shared/package.json'); } - // packages/main/electron-builder.json — productName + a real appId (was deliberately unset) + // packages/main/electron-builder.config.js — productName + a real appId (was deliberately unset) { - const { full, data } = await readJson(root, 'packages/main/electron-builder.json'); + const { full, data } = await readJson(root, 'packages/main/electron-builder.config.js'); data.productName = productName; const ordered = { appId, ...data }; await writeJson(full, ordered); - changed.push('packages/main/electron-builder.json'); + changed.push('packages/main/electron-builder.config.js'); } // In-app title bar text