diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7b7e59fa0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: ci + +on: + pull_request: + push: + tags: + - v* + branches: + - main + +jobs: + test: + name: Test + uses: ./.github/workflows/test.yml + secrets: inherit + + release: + name: Release + if: ${{ github.ref_type == 'tag' || github.ref_name == 'main' }} + uses: ./.github/workflows/release.yml + needs: + - test + secrets: inherit diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml new file mode 100644 index 000000000..84c9069c7 --- /dev/null +++ b/.github/workflows/crowdin.yml @@ -0,0 +1,30 @@ +name: Crowdin Sync + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + synchronize-with-crowdin: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: crowdin action + uses: crowdin/github-action@v2 + with: + crowdin_branch_name: main + dryrun_action: true + upload_sources: true + download_translations: true + export_only_approved: true + push_translations: true + localization_branch_name: main + create_pull_request: false + commit_message: 'chore(i18n): update translations via Crowdin' + env: + GITHUB_TOKEN: ${{ secrets.CROWDIN_GH_TOKEN }} + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/lockdown.yml b/.github/workflows/lockdown.yml deleted file mode 100644 index 4243614d4..000000000 --- a/.github/workflows/lockdown.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: 'Repo Lockdown' - -on: - pull_request_target: - types: opened - -permissions: - issues: write - pull-requests: write - -jobs: - action: - runs-on: ubuntu-latest - steps: - - uses: dessant/repo-lockdown@v4 - with: - pr-comment: 'Hi! Thank you for your contribution. - - This repo is only a mirror and unfortunately we can''t accept PRs made here. Please re-submit your changes to [our Gitea instance](https://kolaente.dev/vikunja/vikunja/pulls). - - Also check out the [contribution guidelines](https://vikunja.io/docs/development/#pull-requests). - - Thank you for your understanding.' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1656bd6e3..943edd7e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,37 @@ -name: release +name: Release on: - push: - branches: - - main + workflow_call: jobs: + mage: + runs-on: ubuntu-latest + name: prepare-mage + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + - name: Cache Mage + id: cache-mage + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-build-mage-${{ hashFiles('magefile.go') }} + path: | + ./mage-static + - name: Compile Mage + if: ${{ steps.cache-mage.outputs.cache-hit != 'true' }} + uses: magefile/mage-action@v3 + with: + version: latest + args: -compile ./mage-static + - name: Store Mage Binary + uses: actions/upload-artifact@v4 + with: + name: mage_bin + path: ./mage-static + docker: runs-on: ubuntu-latest steps: @@ -15,22 +41,220 @@ jobs: - name: Login to GHCR uses: docker/login-action@v3 with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Build and push + - name: Docker meta version + if: ${{ github.ref_type == 'tag' }} + id: meta + uses: docker/metadata-action@v5 + with: + images: | + vikunja/vikunja + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest + - name: Build and push unstable + if: ${{ github.ref_type != 'tag' }} uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 push: true - tags: ghcr.io/go-vikunja/vikunja:unstable + tags: vikunja/vikunja:unstable build-args: | RELEASE_VERSION=${{ steps.ghd.outputs.describe }} + - name: Build and push version + if: ${{ github.ref_type == 'tag' }} + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + RELEASE_VERSION=${{ steps.ghd.outputs.describe }} + + frontend: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + package_json_file: frontend/package.json + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install dependencies + working-directory: frontend + run: | + pnpm install + pnpm build + - name: Store frontend dist + uses: actions/upload-artifact@v4 + with: + name: frontend_dist + path: ./frontend/dist/**/* + + binaries: + runs-on: ubuntu-latest + needs: + - mage + - frontend + steps: + - uses: actions/checkout@v4 + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: get frontend + uses: actions/download-artifact@v4 + with: + name: frontend_dist + path: frontend/dist + - run: chmod +x ./mage-static + - name: install upx + run: | + wget https://github.com/upx/upx/releases/download/v5.0.0/upx-5.0.0-amd64_linux.tar.xz + echo 'b32abf118d721358a50f1aa60eacdbf3298df379c431c3a86f139173ab8289a1 upx-5.0.0-amd64_linux.tar.xz' > upx-5.0.0-amd64_linux.tar.xz.sha256 + sha256sum -c upx-5.0.0-amd64_linux.tar.xz.sha256 + tar xf upx-5.0.0-amd64_linux.tar.xz + mv upx-5.0.0-amd64_linux/upx /usr/local/bin + - name: GPG setup + uses: kolaente/action-gpg@main + with: + gpg-passphrase: "${{ secrets.RELEASE_GPG_PASSPHRASE }}" + gpg-sign-key: "${{ secrets.RELEASE_GPG_SIGN_KEY }}" + - name: build and release + env: + RELEASE_VERSION: ${{ steps.ghd.outputs.describe }} + XGO_OUT_NAME: vikunja-${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} + run: | + export PATH=$PATH:$GOPATH/bin + ./mage-static release + - name: sign + run: | + ls -hal dist/zip/* + for file in dist/zip/*; do + gpg -v --default-key 7D061A4AA61436B40713D42EFF054DACD908493A -b --batch --yes --passphrase "${{ secrets.RELEASE_GPG_PASSPHRASE }}" --pinentry-mode loopback --sign "$file" + done + - name: Upload + uses: kolaente/s3-action@v1.0.1 + with: + s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }} + s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }} + s3-endpoint: 'https://fsn1.your-objectstorage.com' + s3-bucket: 'vikunja' + s3-region: 'fsn1' + target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} + files: 'dist/zip/*' + strip-path-prefix: dist/zip/ + - name: Store Binaries + uses: actions/upload-artifact@v4 + with: + name: vikunja_bins + path: ./dist/binaries/* + + os-package: + runs-on: ubuntu-latest + needs: + - binaries + strategy: + matrix: + package: + - rpm + - deb + - apk + - archlinux + + steps: + - uses: actions/checkout@v4 + - name: Download Vikunja Binary + uses: actions/download-artifact@v4 + with: + name: vikunja_bins + pattern: vikunja-*-linux-amd64 + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: Prepare + env: + RELEASE_VERSION: ${{ steps.ghd.outputs.describe }} + run: | + chmod +x ./mage-static + ./mage-static release:prepare-nfpm-config + mkdir -p ./dist/os-packages + mv ./vikunja-*-linux-amd64 ./vikunja + chmod +x ./vikunja + - name: Create package + id: nfpm + uses: kolaente/action-gh-nfpm@master + with: + packager: ${{ matrix.package }} + target: ./dist/os-packages/vikunja-${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }}-x86_64.${{ matrix.package }} + config: ./nfpm.yaml + - name: Upload + uses: kolaente/s3-action@v1.0.1 + with: + s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }} + s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }} + s3-endpoint: 'https://fsn1.your-objectstorage.com' + s3-bucket: 'vikunja' + s3-region: 'fsn1' + target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} + files: 'dist/os-packages/*' + strip-path-prefix: dist/os-packages/ + + config-yaml: + runs-on: ubuntu-latest + needs: + - mage + steps: + - uses: actions/checkout@v4 + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: generate + run: | + chmod +x ./mage-static + ./mage-static generate:config-yaml 1 + - name: Upload to S3 + uses: kolaente/s3-action@v1.0.1 + with: + s3-access-key-id: ${{ secrets.HETZNER_S3_ACCESS_KEY }} + s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }} + s3-endpoint: 'https://fsn1.your-objectstorage.com' + s3-bucket: 'vikunja' + s3-region: 'fsn1' + target-path: /vikunja/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} + files: 'config.yml.sample' + desktop: + needs: + - frontend strategy: matrix: os: @@ -40,7 +264,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Git describe id: ghd uses: proudust/gh-describe@v2 @@ -57,11 +281,15 @@ jobs: - name: Install Linux dependencies if: ${{ runner.os == 'Linux' }} run: sudo apt-get install --no-install-recommends -y libopenjp2-tools rpm libarchive-tools + - name: get frontend + uses: actions/download-artifact@v4 + with: + name: frontend_dist + path: frontend/dist - name: Build desktop app working-directory: desktop run: | pnpm install --fetch-timeout 100000 - # TODO use the built output from a previous frontend build step node build.js "${{ steps.ghd.outputs.describe }}" ${{ github.ref_type == 'tag' }} - name: Upload to S3 uses: kolaente/s3-action@v1.0.1 @@ -70,8 +298,48 @@ jobs: s3-secret-access-key: ${{ secrets.HETZNER_S3_SECRET_KEY }} s3-endpoint: 'https://fsn1.your-objectstorage.com' s3-bucket: 'vikunja' + s3-region: 'fsn1' files: 'desktop/dist/Vikunja*' target-path: /desktop/${{ github.ref_type == 'tag' && steps.ghd.outputs.describe || 'unstable' }} - s3-region: 'fsn1' strip-path-prefix: desktop/dist/ exclude: 'desktop/dist/*.blockmap' + + generate-swagger-docs: + runs-on: ubuntu-latest + permissions: + contents: write + needs: + - mage + steps: + - uses: actions/checkout@v4 + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + - name: generate + run: | + export PATH=$PATH:$GOPATH/bin + go install github.com/swaggo/swag/cmd/swag + chmod +x ./mage-static + ./mage-static generate:swagger-docs + - name: Check for changes + id: check_changes + run: | + git diff --quiet + echo "changes_exist=$?" >> "$GITHUB_OUTPUT" + - name: Commit files + if: steps.check_changes.outputs.changes_exist != '0' + run: | + git config --local user.email "frederik@vikunja.io" + git config --local user.name "Frederick [Bot]" + git commit -am "[skip ci] Updated swagger docs" + - name: Push changes + if: steps.check_changes.outputs.changes_exist != '0' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..f0c12d661 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,344 @@ +name: Test + +on: + workflow_call: + +jobs: + mage: + runs-on: ubuntu-latest + name: prepare-mage + steps: + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + - name: Cache Mage + id: cache-mage + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-build-mage-${{ hashFiles('magefile.go') }} + path: | + ./mage-static + - name: Compile Mage + if: ${{ steps.cache-mage.outputs.cache-hit != 'true' }} + uses: magefile/mage-action@v3 + with: + version: latest + args: -compile ./mage-static + - name: Store Mage Binary + uses: actions/upload-artifact@v4 + with: + name: mage_bin + path: ./mage-static + + api-build: + runs-on: ubuntu-latest + needs: mage + steps: + - uses: actions/checkout@v4 + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: Git describe + id: ghd + uses: proudust/gh-describe@v2 + - name: Build + env: + RELEASE_VERSION: ${{ steps.ghd.outputs.describe }} + run: | + mkdir -p frontend/dist + touch frontend/dist/index.html + chmod +x ./mage-static + ./mage-static build + - name: Store Vikunja Binary + uses: actions/upload-artifact@v4 + with: + name: vikunja_bin + path: ./vikunja + + api-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: prepare frontend files + run: | + mkdir -p frontend/dist + touch frontend/dist/index.html + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.64.5 + + api-check-translations: + runs-on: ubuntu-latest + needs: mage + steps: + - uses: actions/checkout@v4 + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: Check + run: | + chmod +x ./mage-static + ./mage-static check:translations + + test-migration-smoke: + runs-on: ubuntu-latest + needs: + - api-build + strategy: + matrix: + db: + - sqlite + - postgres + - mysql + services: + migration-smoke-db-mysql: + image: mariadb:11 + env: + MYSQL_ROOT_PASSWORD: vikunjatest + MYSQL_DATABASE: vikunjatest + ports: + - 3307:3306 + migration-smoke-db-postgres: + image: postgres:16 + env: + POSTGRES_PASSWORD: vikunjatest + POSTGRES_DB: vikunjatest + ports: + - 5433:5432 + steps: + - name: Download Unstable + run: | + wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip + unzip vikunja-latest.zip vikunja-unstable-linux-amd64 + - name: Download Vikunja Binary + uses: actions/download-artifact@v4 + with: + name: vikunja_bin + - name: run migration + env: + VIKUNJA_DATABASE_TYPE: ${{ matrix.db }} + VIKUNJA_DATABASE_PATH: ./vikunja-migration-test.db + VIKUNJA_DATABASE_USER: ${{ matrix.db == 'postgres' && 'postgres' || 'root' }} + VIKUNJA_DATABASE_PASSWORD: vikunjatest + VIKUNJA_DATABASE_DATABASE: vikunjatest + VIKUNJA_DATABASE_SSLMODE: disable + VIKUNJA_DATABASE_PORT: ${{ matrix.db == 'postgres' && 5433 || 3307 }} + VIKUNJA_LOG_DATABASE: stdout + VIKUNJA_LOG_DATABASELEVEL: debug + run: | + ./vikunja-unstable-linux-amd64 migrate + # Run the migrations from the binary built in the step before + chmod +x vikunja + ./vikunja migrate + + test-api: + runs-on: ubuntu-latest + needs: + - mage + strategy: + matrix: + db: + - sqlite-in-memory + - sqlite + - postgres + - mysql + test: + - unit + - integration + services: + db-mysql: + image: mariadb:11 + env: + MYSQL_ROOT_PASSWORD: vikunjatest + MYSQL_DATABASE: vikunjatest + ports: + - 3306:3306 + db-postgres: + image: postgres:16 + env: + POSTGRES_PASSWORD: vikunjatest + POSTGRES_DB: vikunjatest + ports: + - 5432:5432 + test-ldap: + image: gitea/test-openldap + ports: + - 389:389 + steps: + - uses: actions/checkout@v4 + - name: Download Mage Binary + uses: actions/download-artifact@v4 + with: + name: mage_bin + - name: test + env: + VIKUNJA_TESTS_USE_CONFIG: ${{ matrix.db != 'sqlite-in-memory' && 1 || 0 }} + VIKUNJA_DATABASE_TYPE: ${{ matrix.db }} + VIKUNJA_DATABASE_USER: ${{ matrix.db == 'postgres' && 'postgres' || 'root' }} + VIKUNJA_DATABASE_PASSWORD: vikunjatest + VIKUNJA_DATABASE_DATABASE: vikunjatest + VIKUNJA_DATABASE_SSLMODE: disable + VIKUNJA_AUTH_LDAP_ENABLED: 1 + VIKUNJA_AUTH_LDAP_HOST: localhost + VIKUNJA_AUTH_LDAP_USETLS: 0 + VIKUNJA_AUTH_LDAP_BASEDN: dc=planetexpress,dc=com + VIKUNJA_AUTH_LDAP_BINDDN: uid=gitea,ou=service,dc=planetexpress,dc=com + VIKUNJA_AUTH_LDAP_BINDPASSWORD: password + VIKUNJA_AUTH_LDAP_USERFILTER: '(&(objectclass=inetorgperson)(uid=%s))' + run: | + mkdir -p frontend/dist + touch frontend/dist/index.html + chmod +x mage-static + ./mage-static test:${{ matrix.test }} + + frontend-dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + package_json_file: frontend/package.json + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Install dependencies + run: | + cd frontend + pnpm install + + frontend-lint: + runs-on: ubuntu-latest + needs: + - frontend-dependencies + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + package_json_file: frontend/package.json + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Lint + run: | + cd frontend + pnpm install + pnpm lint + + frontend-typecheck: + runs-on: ubuntu-latest + needs: + - frontend-dependencies + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + package_json_file: frontend/package.json + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Typecheck + continue-on-error: true + run: | + cd frontend + pnpm install + pnpm typecheck + + test-frontend-unit: + runs-on: ubuntu-latest + needs: + - frontend-dependencies + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + package_json_file: frontend/package.json + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Run unit tests + run: | + cd frontend + pnpm install + pnpm test:unit + + test-frontend-e2e: + runs-on: ubuntu-latest + needs: + - frontend-dependencies + - api-build + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + package_json_file: frontend/package.json + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: frontend/pnpm-lock.yaml + - name: Download Vikunja Binary + uses: actions/download-artifact@v4 + with: + name: vikunja_bin + - name: Build frontend for test + run: | + cd frontend + pnpm install + pnpm cypress install + pnpm build:test + - name: Run api + env: + VIKUNJA_SERVICE_TESTINGTOKEN: averyLongSecretToSe33dtheDB + VIKUNJA_LOG_LEVEL: DEBUG + VIKUNJA_CORS_ENABLE: 1 + VIKUNJA_DATABASE_PATH: memory + VIKUNJA_DATABASE_TYPE: sqlite + run: | + chmod +x ./vikunja + ./vikunja & + - uses: cypress-io/github-action@v6 + with: + working-directory: frontend + browser: chrome + record: true + start: | + pnpm preview:test + wait-on: http://127.0.0.1:4173,http://127.0.0.1:3456/api/v1/info + wait-on-timeout: 10 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + CYPRESS_API_URL: http://127.0.0.1:3456/api/v1 + CYPRESS_TEST_SECRET: averyLongSecretToSe33dtheDB + CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000 diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..109f3b1dd --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,21 @@ +"project_id": "462614" +"api_token_env": "CROWDIN_PERSONAL_TOKEN" +"base_path": "." +"base_url": "https://api.crowdin.com" + +"preserve_hierarchy": true + +files: [ + { + "source": "pkg/i18n/lang/en.json", + "translation": "pkg/i18n/lang/%locale%.json", + "dest": "en-api.json", + "type": "json", + }, + { + "source": "frontend/src/i18n/lang/en.json", + "translation": "frontend/src/i18n/lang/%locale%.json", + "dest": "en.json", + "type": "json", + }, +] \ No newline at end of file diff --git a/desktop/build.js b/desktop/build.js index b00c737cf..76df5d922 100644 --- a/desktop/build.js +++ b/desktop/build.js @@ -16,33 +16,30 @@ 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 copy directory recursively +async function copyDir(src, dest) { + // Create destination directory if it doesn't exist + if (!fs.existsSync(dest)) { + await fs.promises.mkdir(dest, { recursive: true }) + } -// Helper function to unzip a file to a directory -async function unzipFile(zipPath, destDir) { - return fs.createReadStream(zipPath) - .pipe(unzipper.Extract({path: destDir})) - .promise() + // Get all files in source directory + const entries = await fs.promises.readdir(src, { withFileTypes: true }) + + for (const entry of entries) { + const srcPath = path.join(src, entry.name) + const destPath = path.join(dest, entry.name) + + if (entry.isDirectory()) { + // Recursively copy subdirectories + await copyDir(srcPath, destPath) + } else { + // Copy files + await fs.promises.copyFile(srcPath, destPath) + } + } } // Helper function to replace text in a file @@ -78,8 +75,7 @@ async function main() { const versionPlaceholder = args[0] const renameDistFiles = args[1] || false - const frontendZipUrl = 'https://dl.vikunja.io/frontend/vikunja-frontend-unstable.zip' - const zipFilePath = path.resolve(__dirname, 'vikunja-frontend-unstable.zip') + const frontendSourceDir = path.resolve(__dirname, '../frontend/dist') const frontendDir = path.resolve(__dirname, 'frontend') const indexFilePath = path.join(frontendDir, 'index.html') const packageJsonPath = path.join(__dirname, 'package.json') @@ -87,16 +83,19 @@ async function main() { console.log(`Building version ${versionPlaceholder}`) try { - console.log('Step 1: Downloading frontend zip...') - await downloadFile(frontendZipUrl, zipFilePath) + console.log('Step 1: Copying frontend files...') + if (fs.existsSync(frontendDir)) { + console.log('Removing existing frontend directory...') + await fs.promises.rm(frontendDir, { recursive: true, force: true }) + } + await fs.promises.mkdir(frontendDir, { recursive: true }) + + await copyDir(frontendSourceDir, frontendDir) - console.log('Step 2: Unzipping frontend package...') - await unzipFile(zipFilePath, frontendDir) - - console.log('Step 3: Modifying index.html...') + console.log('Step 2: Modifying index.html...') await replaceTextInFile(indexFilePath, /\/api\/v1/g, '') - console.log('Step 4: Updating version in package.json...') + console.log('Step 3: Updating version in package.json...') await replaceTextInFile(packageJsonPath, /\${version}/g, versionPlaceholder) await replaceTextInFile( packageJsonPath, @@ -104,11 +103,11 @@ async function main() { `"version": "${versionPlaceholder}"`, ) - console.log('Step 5: Installing dependencies and building...') + console.log('Step 4: Installing dependencies and building...') execSync('pnpm dist', {stdio: 'inherit'}) if (renameDistFiles) { - console.log('Step 6: Renaming release files...') + console.log('Step 5: Renaming release files...') await renameDistFilesToUnstable(versionPlaceholder) } @@ -116,11 +115,6 @@ async function main() { } catch (err) { console.error('An error occurred:', err.message) process.exit(1) - } finally { - // Cleanup the zip file - if (fs.existsSync(zipFilePath)) { - fs.unlinkSync(zipFilePath) - } } } diff --git a/devenv.nix b/devenv.nix index e17b36f5d..e27c00a13 100644 --- a/devenv.nix +++ b/devenv.nix @@ -11,6 +11,7 @@ in { # General tools git-cliff actionlint + crowdin-cli # API tools golangci-lint mage # Desktop diff --git a/magefile.go b/magefile.go index 540bc704d..cdba44540 100644 --- a/magefile.go +++ b/magefile.go @@ -64,18 +64,19 @@ var ( // Aliases are mage aliases of targets Aliases = map[string]interface{}{ - "build": Build.Build, - "check:got-swag": Check.GotSwag, - "release": Release.Release, - "release:os-package": Release.OsPackage, - "dev:make-migration": Dev.MakeMigration, - "dev:make-event": Dev.MakeEvent, - "dev:make-listener": Dev.MakeListener, - "dev:make-notification": Dev.MakeNotification, - "lint": Check.Golangci, - "lint:fix": Check.GolangciFix, - "generate:config-yaml": Generate.ConfigYAML, - "generate:swagger-docs": Generate.SwaggerDocs, + "build": Build.Build, + "check:got-swag": Check.GotSwag, + "release": Release.Release, + "release:os-package": Release.OsPackage, + "release:prepare-nfpm-config": Release.PrepareNFPMConfig, + "dev:make-migration": Dev.MakeMigration, + "dev:make-event": Dev.MakeEvent, + "dev:make-listener": Dev.MakeListener, + "dev:make-notification": Dev.MakeNotification, + "lint": Check.Golangci, + "lint:fix": Check.GolangciFix, + "generate:config-yaml": Generate.ConfigYAML, + "generate:swagger-docs": Generate.SwaggerDocs, } ) @@ -717,13 +718,17 @@ func runXgo(targets string) error { if strings.HasPrefix(targets, "darwin") { extraLdflags = "" } + outName := os.Getenv("XGO_OUT_NAME") + if outName == "" { + outName = Executable + "-" + Version + } runAndStreamOutput("xgo", "-dest", RootPath+"/"+DIST+"/binaries", "-tags", "netgo "+Tags, "-ldflags", extraLdflags+Ldflags, "-targets", targets, - "-out", Executable+"-"+Version, + "-out", outName, RootPath) if os.Getenv("DRONE_WORKSPACE") != "" { return filepath.Walk("/build/", func(path string, info os.FileInfo, err error) error { @@ -790,10 +795,11 @@ func (Release) Compress(ctx context.Context) error { if !strings.Contains(info.Name(), Executable) { return nil } - // No mips or s390x for you today if strings.Contains(info.Name(), "mips") || strings.Contains(info.Name(), "s390x") || - strings.Contains(info.Name(), "riscv64") { // not supported by upx + strings.Contains(info.Name(), "riscv64") || + strings.Contains(info.Name(), "darwin") { + // not supported by upx return nil } @@ -917,21 +923,10 @@ func (Release) Reprepro() { runAndStreamOutput("reprepro_expect", "debian", "includedeb", "buster", RootPath+"/"+DIST+"/os-packages/"+Executable+"_"+strings.ReplaceAll(VersionNumber, "v0", "0")+"_amd64.deb") } -// Creates deb, rpm and apk packages -func (Release) Packages() error { +// Prepares the nfpm config +func (Release) PrepareNFPMConfig() error { mg.Deps(initVars) var err error - binpath := "nfpm" - err = exec.Command(binpath).Run() - if err != nil && strings.Contains(err.Error(), "executable file not found") { - binpath = "/usr/bin/nfpm" - err = exec.Command(binpath).Run() - } - if err != nil && strings.Contains(err.Error(), "executable file not found") { - fmt.Println("Please manually install nfpm by running") - fmt.Println("curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin") - os.Exit(1) - } // Because nfpm does not support templating, we replace the values in the config file and restore it after running nfpmConfigPath := RootPath + "/nfpm.yaml" @@ -946,18 +941,46 @@ func (Release) Packages() error { return err } + generateConfigYAMLFromJSON(DefaultConfigYAMLSamplePath, true) + + return nil +} + +// Creates deb, rpm and apk packages +func (Release) Packages() error { + mg.Deps(initVars) + + var err error + binpath := os.Getenv("NFPM_BIN_PATH") + if binpath == "" { + binpath = "nfpm" + } + err = exec.Command(binpath).Run() + if err != nil && strings.Contains(err.Error(), "executable file not found") { + binpath = "/usr/bin/nfpm" + err = exec.Command(binpath).Run() + } + if err != nil && strings.Contains(err.Error(), "executable file not found") { + fmt.Println("Please manually install nfpm by running") + fmt.Println("curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin") + os.Exit(1) + } + + err = (Release{}).PrepareNFPMConfig() + if err != nil { + return err + } + releasePath := RootPath + "/" + DIST + "/os-packages/" if err := os.MkdirAll(releasePath, 0755); err != nil { return err } - generateConfigYAMLFromJSON(DefaultConfigYAMLSamplePath, true) - runAndStreamOutput(binpath, "pkg", "--packager", "deb", "--target", releasePath) runAndStreamOutput(binpath, "pkg", "--packager", "rpm", "--target", releasePath) runAndStreamOutput(binpath, "pkg", "--packager", "apk", "--target", releasePath) - return os.WriteFile(nfpmConfigPath, nfpmconfig, 0) + return nil } type Dev mg.Namespace