From 8bb33b97f5a71796b8f641d7b184721e695e904a Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 8 Feb 2025 19:33:17 +0100 Subject: [PATCH] feat(ci): build desktop app natively This change uses GitHub's matrix feature to build the desktop app natively. It also moves the build instructions to node.js script, since that runs natively on all OSes. --- .github/workflows/release.yml | 59 +++++++++---------- desktop/build.js | 105 ++++++++++++++++++++++++++++++++++ desktop/package.json | 6 +- desktop/pnpm-lock.yaml | 26 +++++++++ 4 files changed, 161 insertions(+), 35 deletions(-) create mode 100644 desktop/build.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3e7ae96b..bfd72d99f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,20 +6,12 @@ on: - main jobs: - version: + docker: runs-on: ubuntu-latest - outputs: - version: ${{ steps.ghd.outputs.describe }} steps: - name: Git describe id: ghd uses: proudust/gh-describe@v2 - - docker: - runs-on: ubuntu-latest - needs: - - version - steps: - name: Login to GHCR uses: docker/login-action@v3 with: @@ -37,38 +29,39 @@ jobs: push: true tags: ghcr.io/go-vikunja/vikunja:unstable build-args: | - RELEASE_VERSION=${{ needs.version.outputs.version }} + RELEASE_VERSION=${{ steps.ghd.outputs.describe }} desktop: - runs-on: ubuntu-latest - needs: - - version - container: - image: electronuserland/builder:wine + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - - name: Set version - id: version - run: | - if [ "${{ github.ref_type }}" = "tag" ]; then - VERSION="${{ github.ref_name}}" - else - VERSION="unstable" - fi - echo version=$VERSION >> "$GITHUB_OUTPUT" + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + package_json_file: desktop/package.json + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: desktop/pnpm-lock.yaml - name: Build desktop app working-directory: desktop run: | - wget "https://dl.vikunja.io/frontend/vikunja-frontend-${{ steps.version.outputs.version }}.zip" - unzip "vikunja-frontend-${{ steps.version.outputs.version }}.zip" -d frontend - sed -i 's/\/api\/v1//g' frontend/index.html - sed -i "s/\${version}/${{ needs.version.outputs.version }}/g" package.json - sed -i "s/\"version\": \".*\"/\"version\": \"${{ needs.version.outputs.version }}\"/" package.json - npm install -g corepack && corepack enable && pnpm config set store-dir .cache/pnpm pnpm install --fetch-timeout 100000 - pnpm dist --linux --windows + # TODO use the built output from a previous frontend build step + node build.js "${{ steps.ghd.outputs.describe }}" - name: Store release as artifact uses: actions/upload-artifact@v4 with: - name: desktop-release - path: desktop/dist/Vikunja-Desktop* + name: desktop-release-${{ runner.os }} + path: desktop/dist/Vikunja* diff --git a/desktop/build.js b/desktop/build.js new file mode 100644 index 000000000..2e1845924 --- /dev/null +++ b/desktop/build.js @@ -0,0 +1,105 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public Licensee as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public Licensee for more details. +// +// You should have received a copy of the GNU Affero General Public Licensee +// along with this program. If not, see . + +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { execSync } = require('child_process'); +const unzipper = require('unzipper'); + +// Helper function to download a file +async function downloadFile(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + https.get(url, (response) => { + if (response.statusCode !== 200) { + return reject(new Error(`Failed to download file: ${response.statusCode}`)); + } + response.pipe(file); + file.on('finish', () => { + file.close(resolve); + }); + }).on('error', (err) => { + fs.unlink(dest, () => reject(err)); + }); + }); +} + +// Helper function to unzip a file to a directory +async function unzipFile(zipPath, destDir) { + return fs.createReadStream(zipPath) + .pipe(unzipper.Extract({ path: destDir })) + .promise(); +} + +// Helper function to replace text in a file +async function replaceTextInFile(filePath, searchValue, replaceValue) { + const data = await fs.promises.readFile(filePath, 'utf8'); + const result = data.replace(searchValue, replaceValue); + await fs.promises.writeFile(filePath, result, 'utf8'); +} + +// Main function to execute the script steps +async function main() { + const args = process.argv.slice(2); + if (args.length === 0) { + console.error("Error: Version placeholder argument is required."); + console.error("Usage: node build-script.js "); + process.exit(1); + } + + const versionPlaceholder = args[0]; + const frontendZipUrl = "https://dl.vikunja.io/frontend/vikunja-frontend-unstable.zip"; + const zipFilePath = path.resolve(__dirname, 'vikunja-frontend-unstable.zip'); + const frontendDir = path.resolve(__dirname, 'frontend'); + const indexFilePath = path.join(frontendDir, 'index.html'); + const packageJsonPath = path.join(__dirname, 'package.json'); + + console.log(`Building version ${versionPlaceholder}`); + + try { + console.log('Step 1: Downloading frontend zip...'); + await downloadFile(frontendZipUrl, zipFilePath); + + console.log('Step 2: Unzipping frontend package...'); + await unzipFile(zipFilePath, frontendDir); + + console.log('Step 3: Modifying index.html...'); + await replaceTextInFile(indexFilePath, /\/api\/v1/g, ''); + + console.log('Step 4: Updating version in package.json...'); + await replaceTextInFile(packageJsonPath, /\${version}/g, versionPlaceholder); + await replaceTextInFile( + packageJsonPath, + /"version": ".*"/, + `"version": "${versionPlaceholder}"` + ); + + console.log('Step 5: Installing dependencies and building...'); + execSync('pnpm dist', { stdio: 'inherit' }); + + console.log('All steps completed successfully!'); + } catch (err) { + console.error('An error occurred:', err.message); + } finally { + // Cleanup the zip file + if (fs.existsSync(zipFilePath)) { + fs.unlinkSync(zipFilePath); + } + } +} + +main(); diff --git a/desktop/package.json b/desktop/package.json index 89f604c65..0b8a232e8 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,10 +1,11 @@ { "name": "vikunja-desktop", - "version": "0.21.0", + "version": "v0.1.0", "description": "Vikunja's frontend as a standalone desktop application.", "main": "main.js", "repository": "https://code.vikunja.io/desktop", "license": "GPL-3.0-or-later", + "packageManager": "pnpm@9.15.4", "author": { "email": "maintainers@vikunja.io", "name": "Vikunja Team" @@ -52,7 +53,8 @@ }, "devDependencies": { "electron": "34.0.1", - "electron-builder": "25.1.8" + "electron-builder": "25.1.8", + "unzipper": "^0.12.3" }, "dependencies": { "connect-history-api-fallback": "2.0.0", diff --git a/desktop/pnpm-lock.yaml b/desktop/pnpm-lock.yaml index 45e1559c0..109d5fe17 100644 --- a/desktop/pnpm-lock.yaml +++ b/desktop/pnpm-lock.yaml @@ -21,6 +21,9 @@ importers: electron-builder: specifier: 25.1.8 version: 25.1.8(electron-builder-squirrel-windows@24.13.3) + unzipper: + specifier: ^0.12.3 + version: 0.12.3 packages: @@ -561,6 +564,9 @@ packages: resolution: {integrity: sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==} engines: {node: '>=10'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1148,6 +1154,9 @@ packages: engines: {node: ^12.13 || ^14.13 || >=16} hasBin: true + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -1550,6 +1559,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unzipper@0.12.3: + resolution: {integrity: sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2383,6 +2395,10 @@ snapshots: dotenv@9.0.2: {} + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + eastasianwidth@0.2.0: {} ee-first@1.1.1: {} @@ -3074,6 +3090,8 @@ snapshots: - bluebird - supports-color + node-int64@0.4.0: {} + nopt@6.0.0: dependencies: abbrev: 1.1.1 @@ -3497,6 +3515,14 @@ snapshots: unpipe@1.0.0: {} + unzipper@0.12.3: + dependencies: + bluebird: 3.7.2 + duplexer2: 0.1.4 + fs-extra: 11.2.0 + graceful-fs: 4.2.11 + node-int64: 0.4.0 + uri-js@4.4.1: dependencies: punycode: 2.3.1