diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58d4b0b4..7b302121 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,10 @@ on: jobs: linux: + strategy: + fail-fast: false + matrix: + arch: [x64, arm64] runs-on: ubuntu-22.04 permissions: contents: write @@ -31,8 +35,12 @@ jobs: run: node electron/download-ipfs && sudo chmod +x bin/linux/ipfs - name: Build React app (with Node v22) run: CI='' NODE_ENV=production yarn build - - name: Build Electron app for Linux (with Node v22) - run: yarn electron:build:linux + - name: Build Electron app for Linux (arm64) + if: ${{ matrix.arch == 'arm64' }} + run: yarn electron:build:linux:arm64 + - name: Build Electron app for Linux (x64) + if: ${{ matrix.arch == 'x64' }} + run: yarn electron:build:linux:x64 - name: List dist directory run: ls dist @@ -41,10 +49,10 @@ jobs: run: node scripts/release-body > release-body.txt - uses: ncipollo/release-action@v1 with: - artifacts: 'dist/seedit*.AppImage,dist/seedit-html*.zip' + artifacts: 'dist/seedit*.AppImage,dist/seedit*-arm64.AppImage,dist/seedit-html*.zip' token: ${{ secrets.GITHUB_TOKEN }} replacesArtifacts: true - bodyFile: "release-body.txt" + omitBody: true allowUpdates: true mac: @@ -69,10 +77,11 @@ jobs: uses: actions/setup-node@v2 with: node-version: 22 + - name: Setup Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' - # install missing dep for sqlite - - run: python3 -m ensurepip - - run: pip install setuptools - name: Install dependencies (with Node v22) run: yarn install --frozen-lockfile --ignore-engines # make sure the ipfs executable is executable @@ -81,7 +90,14 @@ jobs: - name: Build React app (with Node v22) run: CI='' NODE_ENV=production yarn build - name: Build Electron app for Mac (with Node v22) - run: yarn electron:build:mac + env: + CSC_IDENTITY_AUTO_DISCOVERY: 'false' + run: | + if [ "${{ matrix.arch }}" = "arm64" ]; then + yarn electron:build:mac:arm64 + else + yarn electron:build:mac:x64 + fi - name: List dist directory run: ls dist @@ -90,10 +106,10 @@ jobs: run: node scripts/release-body > release-body.txt - uses: ncipollo/release-action@v1 with: - artifacts: 'dist/seedit*.dmg' + artifacts: 'dist/seedit*.dmg,dist/seedit*-arm64.dmg' token: ${{ secrets.GITHUB_TOKEN }} replacesArtifacts: true - bodyFile: "release-body.txt" + omitBody: true allowUpdates: true windows: @@ -114,7 +130,7 @@ jobs: run: yarn install --frozen-lockfile --network-timeout 100000 --network-concurrency 1 - name: Build React app (with Node v22) run: npx cross-env NODE_ENV=production yarn build - - name: Build Electron app for Windows (with Node v22) + - name: Build Electron app for Windows (x64) run: yarn electron:build:windows - name: List dist directory run: dir dist @@ -127,7 +143,7 @@ jobs: artifacts: 'dist/seedit*.exe' token: ${{ secrets.GITHUB_TOKEN }} replacesArtifacts: true - bodyFile: "release-body.txt" + omitBody: true allowUpdates: true android: @@ -181,5 +197,31 @@ jobs: artifacts: 'dist/seedit*.apk' token: ${{ secrets.GITHUB_TOKEN }} replacesArtifacts: true - bodyFile: "release-body.txt" + omitBody: true + allowUpdates: true + + finalize-release: + runs-on: ubuntu-22.04 + needs: [linux, mac, windows, android] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Node.js v22 + uses: actions/setup-node@v2 + with: + node-version: 22 + - name: Install dependencies + run: yarn install --frozen-lockfile --ignore-engines + - name: Generate final release body from GitHub assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_REF_NAME: ${{ github.ref_name }} + run: node scripts/release-body > release-body.txt + - name: Update release body only + uses: ncipollo/release-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true + bodyFile: "release-body.txt" diff --git a/CHANGELOG.md b/CHANGELOG.md index 71cab2c7..1b438320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [0.5.9](https://github.com/plebbit/seedit/compare/v0.5.8...v0.5.9) (2025-08-17) + + +### Bug Fixes + +* **challenge settings:** prevent uncontrolled input error in number fields ([818408c](https://github.com/plebbit/seedit/commit/818408c7d92afd14e05f2921c04c05b4a0c0a4b9)) +* missing error displays in json editors ([06b9598](https://github.com/plebbit/seedit/commit/06b959855476d056d820cc5d9b519a80b9b84e7e)) +* **posts:** handle whitespace-only post titles properly ([1f69dc9](https://github.com/plebbit/seedit/commit/1f69dc940591baa83d0f9a5d40110fa48ed63782)) + + +### Performance Improvements + +* **electron:** reduce prod logging + IPFS noise across OS; disable devtools in release; arch-aware mac builds ([c9f3ba8](https://github.com/plebbit/seedit/commit/c9f3ba88225461f788989c09915b8f2da6321c02)) + + + ## [0.5.8](https://github.com/plebbit/seedit/compare/v0.5.7...v0.5.8) (2025-08-09) @@ -5,7 +21,9 @@ * **android:** implement cross-platform account export ([b0b06db](https://github.com/plebbit/seedit/commit/b0b06db8c55b3e6bb6c1148acbc61d7ddf6c2d2c)) * edit comment state would get stuck at pending ([4251325](https://github.com/plebbit/seedit/commit/4251325b2a131bd64e5b8e8c8f1df20d7c20f581)) +* resolve Android CI build failure with Google Services plugin ([a5373aa](https://github.com/plebbit/seedit/commit/a5373aa9baa44233cde6e9ec02e1b8c95917bcb1)) * resolve Android export dialog and permission issues ([01d7e1c](https://github.com/plebbit/seedit/commit/01d7e1c209d9899ea13502d70f6cf75786b5ec2c)) +* revert Google Services to working config and remove hardcoded Java path ([f7df22a](https://github.com/plebbit/seedit/commit/f7df22a504e241d8d47af849a6b59937f5a3ca4e)) diff --git a/electron/before-pack.js b/electron/before-pack.js index 9497e63b..d77bbf55 100755 --- a/electron/before-pack.js +++ b/electron/before-pack.js @@ -19,11 +19,26 @@ const ipfsClientLinuxPath = path.join(ipfsClientsPath, 'linux'); // official kubo download links https://docs.ipfs.tech/install/command-line/#install-official-binary-distributions const ipfsClientVersion = '0.32.1'; -const ipfsClientWindowsUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_windows-amd64.zip`; -// Choose proper mac binary by builder architecture to avoid Rosetta on Apple Silicon -const macArch = process.arch === 'arm64' ? 'arm64' : 'amd64'; -const ipfsClientMacUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_darwin-${macArch}.tar.gz`; -const ipfsClientLinuxUrl = `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-amd64.tar.gz`; + +// Resolve desired build arch: allow overriding via env (so cross-arch builds pick correct binary) +const resolveBuildArch = () => { + const envArch = process.env.SEEDIT_BUILD_ARCH; + if (envArch === 'arm64' || envArch === 'x64') return envArch; + // fallback to host arch + if (process.arch === 'arm64') return 'arm64'; + return 'x64'; +}; + +const toKuboArch = (arch) => (arch === 'arm64' ? 'arm64' : 'amd64'); + +const getKuboUrl = (platform) => { + const arch = toKuboArch(resolveBuildArch()); + if (platform === 'win32') return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_windows-${arch}.zip`; + if (platform === 'darwin') return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_darwin-${arch}.tar.gz`; + if (platform === 'linux') return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-${arch}.tar.gz`; + // default to linux + return `https://dist.ipfs.io/kubo/v${ipfsClientVersion}/kubo_v${ipfsClientVersion}_linux-${arch}.tar.gz`; +}; const downloadWithProgress = (url) => new Promise((resolve, reject) => { @@ -138,22 +153,17 @@ const downloadAndExtract = async (url, destinationPath) => { export const downloadIpfsClients = async () => { const platform = process.platform; - console.log(`Starting IPFS client download for platform: ${platform}`); - switch (platform) { - case 'win32': - await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath); - break; - case 'darwin': - await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath); - break; - case 'linux': - await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath); - break; - default: - console.warn(`Unknown platform: ${platform}, downloading all IPFS clients`); - await downloadAndExtract(ipfsClientWindowsUrl, ipfsClientWindowsPath); - await downloadAndExtract(ipfsClientMacUrl, ipfsClientMacPath); - await downloadAndExtract(ipfsClientLinuxUrl, ipfsClientLinuxPath); + console.log(`Starting IPFS client download for platform: ${platform}, targetArch: ${resolveBuildArch()}`); + const url = getKuboUrl(platform); + if (platform === 'win32') { + await downloadAndExtract(url, ipfsClientWindowsPath); + } else if (platform === 'darwin') { + await downloadAndExtract(url, ipfsClientMacPath); + } else if (platform === 'linux') { + await downloadAndExtract(url, ipfsClientLinuxPath); + } else { + console.warn(`Unknown platform: ${platform}, defaulting to linux path`); + await downloadAndExtract(url, ipfsClientLinuxPath); } }; diff --git a/package.json b/package.json index 2eb5a8d3..9f52628c 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Seedit", - "version": "0.5.8", + "version": "0.5.9", "description": "A GUI for plebbit similar to old.reddit", "author": "Plebbit Labs", "type": "module", @@ -74,6 +74,12 @@ "electron:build:linux": "yarn build && yarn build:preload && electron-rebuild && electron-builder build --publish never -l", "electron:build:windows": "yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -w", "electron:build:mac": "yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -m", + "electron:build:mac:arm64": "cross-env SEEDIT_BUILD_ARCH=arm64 yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -m --arm64", + "electron:build:mac:x64": "cross-env SEEDIT_BUILD_ARCH=x64 yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -m --x64", + "electron:build:windows:arm64": "cross-env SEEDIT_BUILD_ARCH=arm64 yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -w --arm64", + "electron:build:windows:x64": "cross-env SEEDIT_BUILD_ARCH=x64 yarn build && yarn build:preload && yarn electron-rebuild && electron-builder build --publish never -w --x64", + "electron:build:linux:arm64": "cross-env SEEDIT_BUILD_ARCH=arm64 yarn build && yarn build:preload && electron-rebuild && electron-builder build --publish never -l --arm64", + "electron:build:linux:x64": "cross-env SEEDIT_BUILD_ARCH=x64 yarn build && yarn build:preload && electron-rebuild && electron-builder build --publish never -l --x64", "electron:before": "yarn electron-rebuild && yarn electron:before:download-ipfs && yarn electron:before:delete-data", "electron:before:download-ipfs": "node electron/download-ipfs", "electron:before:delete-data": "rimraf .plebbit", @@ -163,7 +169,8 @@ "mac": { "target": "dmg", "category": "public.app-category.social-networking", - "type": "distribution" + "type": "distribution", + "identity": null }, "win": { "target": [ diff --git a/scripts/release-body.js b/scripts/release-body.js index eb01125e..8888ff4e 100644 --- a/scripts/release-body.js +++ b/scripts/release-body.js @@ -1,34 +1,120 @@ -import {execSync} from 'child_process' -import path from 'path' -import {fileURLToPath} from 'url' -import {readFileSync} from 'fs' +import { execSync } from 'child_process'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { readFileSync, readdirSync } from 'fs'; +import fetch from 'node-fetch'; -const dirname = path.join(path.dirname(fileURLToPath(import.meta.url))) -const conventionalChangelog = path.join(dirname, '..', 'node_modules', '.bin', 'conventional-changelog') -const packageJsonPath = path.join(dirname, '..', 'package.json') -const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) -const version = packageJson.version +const dirname = path.join(path.dirname(fileURLToPath(import.meta.url))); +const conventionalChangelog = path.join(dirname, '..', 'node_modules', '.bin', 'conventional-changelog'); +const version = JSON.parse(readFileSync(path.join(dirname, '..', 'package.json'), 'utf8')).version; -// sometimes release-count 1 is empty -let releaseChangelog = - execSync(`${conventionalChangelog} --preset angular --release-count 1`).toString() || - execSync(`${conventionalChangelog} --preset angular --release-count 2`).toString() +// changelog (use last non-empty) +let releaseChangelog = + execSync(`${conventionalChangelog} --preset angular --release-count 1`).toString() || + execSync(`${conventionalChangelog} --preset angular --release-count 2`).toString(); +releaseChangelog = releaseChangelog.trim().replace(/\n\n+/g, '\n\n'); -// format -releaseChangelog = releaseChangelog.trim().replace(/\n\n+/g, '\n\n') +// discover artifacts +const distDir = path.join(dirname, '..', 'dist'); +let files = []; +try { files = readdirSync(distDir); } catch {} -const releaseBody = `This version fixes a bug that caused extreme slowness in some desktop versions of the app. +// In CI finalization, dist is empty. Prefer GitHub release assets when available. +const getReleaseAssetNames = async () => { + try { + const token = process.env.GITHUB_TOKEN; + const repo = process.env.GITHUB_REPOSITORY; // owner/repo + const tag = process.env.GITHUB_REF_NAME || `v${version}`; + if (!token || !repo || !tag) return []; + const res = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${tag}`, { + headers: { + Authorization: `Bearer ${token}`, + 'User-Agent': 'seedit-release-notes', + 'X-GitHub-Api-Version': '2022-11-28', + Accept: 'application/vnd.github+json', + }, + }); + if (!res.ok) return []; + const data = await res.json(); + return (data.assets || []).map((a) => a.name).filter(Boolean); + } catch (e) { + return []; + } +}; + +const remoteFiles = await getReleaseAssetNames(); +if (remoteFiles.length) { + files = remoteFiles; +} + +const linkTo = (file) => `https://github.com/plebbit/seedit/releases/download/v${version}/${file}`; +const has = (s, sub) => s.toLowerCase().includes(sub); +const isArm = (s) => has(s, 'arm64') || has(s, 'aarch64'); +const isX64 = (s) => (has(s, 'x64') || has(s, 'x86_64')) || !isArm(s); + +// buckets +const androidApk = files.find(f => f.endsWith('.apk')); +const htmlZip = files.find(f => f.includes('seedit-html') && f.endsWith('.zip')); + +const linux = files.filter(f => f.endsWith('.AppImage')); +const mac = files.filter(f => f.endsWith('.dmg')); +const win = files.filter(f => f.toLowerCase().endsWith('.exe')); + +const linuxArm = linux.find(isArm); +const linuxX64 = linux.find(isX64); +const macArm = mac.find(isArm); +const macX64 = mac.find(isX64); + +const winSetupArm = win.find(f => has(f, 'setup') && isArm(f)); +const winSetupX64 = win.find(f => has(f, 'setup') && isX64(f)); +const winPortableArm = win.find(f => has(f, 'portable') && isArm(f)); +const winPortableX64 = win.find(f => has(f, 'portable') && isX64(f)); + +// small section builder without push() +const section = (title, lines) => { + const body = lines.filter(Boolean).join('\n'); + return body ? `### ${title}\n${body}` : ''; +}; + +const macSection = section('macOS', [ + macArm && `- Apple Silicon (arm64): [Download DMG](${linkTo(macArm)})`, + macX64 && `- Intel (x64): [Download DMG](${linkTo(macX64)})`, + (mac.length > 0) && `- If macOS shows "seedit.app is damaged and can't be opened. You should move it to the Trash.", run this in Terminal to fix it: \`xattr -dr com.apple.quarantine "/Applications/seedit.app"\` then open the app again.`, +]); + +const winSection = section('Windows', [ + winSetupX64 && `- Installer (x64): [Download EXE](${linkTo(winSetupX64)})`, + winPortableX64 && `- Portable (x64): [Download EXE](${linkTo(winPortableX64)})`, +]); + +const linuxSection = section('Linux', [ + linuxArm && `- AppImage (arm64): [Download](${linkTo(linuxArm)})`, + linuxX64 && `- AppImage (x64): [Download](${linkTo(linuxX64)})`, +]); + +const androidSection = section('Android', [ + androidApk && `- APK: [Download](${linkTo(androidApk)})`, +]); + +const htmlSection = section('Static HTML build', [ + htmlZip && `- seedit-html (zip): [Download](${linkTo(htmlZip)})`, +]); + +const downloads = [macSection, winSection, linuxSection, androidSection, htmlSection] + .filter(Boolean).join('\n\n'); + +const releaseBody = `This version improves desktop performance and adds native builds. - Web app: https://seedit.app -- Decentralized web app: https://seedit.eth (only works on [Brave Browser](https://brave.com/) or via [IPFS Companion](https://docs.ipfs.tech/install/ipfs-companion/#prerequisites)) +- Decentralized web app via IPFS/IPNS gateways (works on any browser): [seedit.eth.limo](https://seedit.eth.limo), [seedit.eth.link](https://seedit.eth.link), [dweb.link/ipfs.io](https://dweb.link/ipns/seedit.eth) +- Decentralized web app via IPFS node you run: https://seedit.eth (works on [Brave Browser](https://brave.com/) or use [IPFS Companion](https://docs.ipfs.tech/install/ipfs-companion/#prerequisites)) ## Downloads -- Android app: [Download APK](https://github.com/plebbit/seedit/releases/download/v${version}/seedit-${version}.apk) -- Linux app: [Download AppImage](https://github.com/plebbit/seedit/releases/download/v${version}/seedit-${version}.AppImage) -- macOS app: [Download DMG](https://github.com/plebbit/seedit/releases/download/v${version}/seedit-${version}.dmg) -- Windows app: [Download EXE](https://github.com/plebbit/seedit/releases/download/v${version}/seedit.Setup.${version}.exe) +${downloads} + +## Changes -${releaseChangelog}` +${releaseChangelog}`; -console.log(releaseBody) \ No newline at end of file +console.log(releaseBody); \ No newline at end of file