diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 4390d64..cff7aad 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -1,53 +1,78 @@ -# Phase 1: build unsigned Windows installers on demand, upload as run artifacts. -# -# To graduate to Phase 2 (signed + published to GCS): -# 1. Add GCP auth step (`google-github-actions/auth@v2` via WIF) + `setup-gcloud`. -# Swap `yarn make:win` for a new `yarn release:win` script that runs -# `electron-forge publish` per arch and a gsutil cache-control step. -# 2. Add an Azure Trusted Signing setup step that downloads the -# Microsoft.Trusted.Signing.Client dlib and writes a metadata JSON file, -# then exports AZURE_DLIB_PATH + AZURE_METADATA_JSON_PATH. Set -# AZURE_TENANT_ID / AZURE_CLIENT_ID / AZURE_CLIENT_SECRET from secrets. -# The dormant `windowsSign` branch in js/forge.config.ts activates -# automatically once AZURE_METADATA_JSON_PATH is set. - -name: Build Windows - -on: - workflow_dispatch: - -jobs: - build: - runs-on: windows-latest - defaults: - run: - working-directory: js - env: - APP_ENV: prod - - steps: - - uses: actions/checkout@v4 - - - name: Enable corepack - run: corepack enable - shell: pwsh - working-directory: . - - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: yarn - cache-dependency-path: js/yarn.lock - - - name: Install dependencies - run: yarn install --frozen-lockfile --network-timeout 600000 - - - name: Build Windows installers (x64 + arm64, unsigned) - run: yarn make:win - - - name: Upload installers - uses: actions/upload-artifact@v4 - with: - name: flowy-llink-windows-unsigned - path: js/out/make/**/* - if-no-files-found: error +name: Build Windows + +on: + workflow_dispatch: + +permissions: + # Azure Trusted Signing uses OIDC federated credentials from GitHub. + id-token: write + contents: read + +jobs: + build: + runs-on: windows-latest + defaults: + run: + working-directory: js + env: + APP_ENV: prod + AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} + AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} + + steps: + - uses: actions/checkout@v4 + + - name: Enable corepack + run: corepack enable + shell: pwsh + working-directory: . + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + cache-dependency-path: js/yarn.lock + + - name: Install dependencies + run: yarn install --frozen-lockfile --network-timeout 600000 + + - name: Azure login (OIDC) + uses: azure/login@v2 + with: + tenant-id: ${{ vars.AZURE_TENANT_ID }} + client-id: ${{ vars.AZURE_CLIENT_ID }} + allow-no-subscriptions: true + + - name: Install Trusted Signing client dlib + shell: pwsh + working-directory: . + run: | + nuget install Microsoft.Trusted.Signing.Client -Version 1.0.60 -OutputDirectory $env:RUNNER_TEMP\trusted-signing -ExcludeVersion + $dlib = Join-Path $env:RUNNER_TEMP "trusted-signing\Microsoft.Trusted.Signing.Client\bin\x64\Azure.CodeSigning.Dlib.dll" + if (-not (Test-Path $dlib)) { throw "Dlib not found at $dlib" } + "AZURE_DLIB_PATH=$dlib" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + "AZURE_METADATA_JSON_PATH=$env:GITHUB_WORKSPACE\js\build\signing-metadata.json" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.PROD_GKE_SERVICE_ACCOUNT_KEY }} + + - name: Set up gcloud + uses: google-github-actions/setup-gcloud@v2 + + - name: Publish signed installer (x64) + run: yarn publish:win + + - name: Invalidate RELEASES cache + shell: pwsh + working-directory: . + run: gsutil setmeta -h "Cache-Control:no-cache, no-store, must-revalidate" gs://flowy-releases/llink/win32/x64/RELEASES.json + + - name: Upload installers (debug artifact) + if: always() + uses: actions/upload-artifact@v4 + with: + name: flowy-llink-windows-signed + path: js/out/make/**/* + if-no-files-found: warn diff --git a/js/forge.config.ts b/js/forge.config.ts index 9b6ccd3..813891a 100644 --- a/js/forge.config.ts +++ b/js/forge.config.ts @@ -1,121 +1,115 @@ -import type { ForgeConfig } from '@electron-forge/shared-types'; -import { MakerSquirrel } from '@electron-forge/maker-squirrel'; -import { MakerZIP } from '@electron-forge/maker-zip'; -import { MakerDeb } from '@electron-forge/maker-deb'; -import { MakerRpm } from '@electron-forge/maker-rpm'; -import { VitePlugin } from '@electron-forge/plugin-vite'; -import { FusesPlugin } from '@electron-forge/plugin-fuses'; -import { FuseV1Options, FuseVersion } from '@electron/fuses'; - -const config: ForgeConfig = { - packagerConfig: { - asar: true, - icon: './assets/flowy', - appBundleId: 'live.flowy.llink', - protocols: [ - { name: 'llink', schemes: ['llink'] }, - ], - extendInfo: { - NSMicrophoneUsageDescription: 'llink needs microphone access to record audio messages.', - NSCameraUsageDescription: 'llink needs camera access to record video messages.', - }, - osxSign: {}, - osxNotarize: { - keychainProfile: 'default', - } - }, - publishers: [ - { - name: '@electron-forge/publisher-gcs', - config: { - storageOptions: { - projectId: 'flowy-prod-440017' - }, - bucket: 'flowy-releases', - folder: 'llink', - // NOTE: commented out because we do not want to apply per object ACL which conflicts with uniform bucket-level access that's already public - // public: true - } - } - ], - rebuildConfig: {}, - makers: [ - new MakerSquirrel((arch) => ({ - // `remoteReleases` is only useful when producing delta packages against a - // previously-published build. During a plain `make` (no publish), Squirrel - // fetches this URL and fails with a 404 if nothing has been uploaded yet. - // Gate it on SQUIRREL_REMOTE_RELEASES=1 which the publish workflow sets. - ...(process.env.SQUIRREL_REMOTE_RELEASES === '1' ? { - remoteReleases: `https://storage.googleapis.com/flowy-releases/llink/win32/${arch}`, - } : {}), - // Windows code signing via Azure Trusted Signing. Dormant unless the workflow - // provisions the dlib + metadata file and exports these env vars, so local - // `yarn make` and unsigned CI builds both work unchanged. - windowsSign: process.env.AZURE_METADATA_JSON_PATH ? { - signWithParams: [ - '/v', - '/fd', 'SHA256', - '/tr', 'http://timestamp.acs.microsoft.com', - '/td', 'SHA256', - '/dlib', process.env.AZURE_DLIB_PATH, - '/dmdf', process.env.AZURE_METADATA_JSON_PATH, - ].join(' '), - } : undefined, - })), - new MakerZIP((arch) => ({ - macUpdateManifestBaseUrl: `https://storage.googleapis.com/flowy-releases/llink/darwin/${arch}` - }), ['darwin']), - new MakerRpm({}), - new MakerDeb({}), - ], - plugins: [ - new VitePlugin({ - // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. - // If you are familiar with Vite configuration, it will look really familiar. - build: [ - { - // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. - entry: 'src/main.ts', - config: 'vite.main.config.ts', - target: 'main', - }, - { - entry: 'src/preload.ts', - config: 'vite.preload.config.ts', - target: 'preload', - }, - ], - renderer: [ - { - name: 'main_window', - config: 'vite.renderer.config.mts', - }, - { - name: 'autoplay_window', - config: 'vite.autoplay.config.mts', - }, - { - name: 'huddle_window', - config: 'vite.huddle.config.mts', - }, - { - name: 'screen_record_window', - config: 'vite.screen-record.config.mts', - }, - ], - }), - // Fuses are used to enable/disable various Electron functionality - // at package time, before code signing the application - new FusesPlugin({ - version: FuseVersion.V1, - [FuseV1Options.RunAsNode]: false, - [FuseV1Options.EnableCookieEncryption]: true, - [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, - [FuseV1Options.EnableNodeCliInspectArguments]: false, - [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, - [FuseV1Options.OnlyLoadAppFromAsar]: true, - }), - ], -}; - -export default config; +import type { ForgeConfig } from '@electron-forge/shared-types'; +import { MakerSquirrel } from '@electron-forge/maker-squirrel'; +import { MakerZIP } from '@electron-forge/maker-zip'; +import { MakerDeb } from '@electron-forge/maker-deb'; +import { MakerRpm } from '@electron-forge/maker-rpm'; +import { VitePlugin } from '@electron-forge/plugin-vite'; +import { FusesPlugin } from '@electron-forge/plugin-fuses'; +import { FuseV1Options, FuseVersion } from '@electron/fuses'; + +const config: ForgeConfig = { + packagerConfig: { + asar: true, + icon: './assets/flowy', + appBundleId: 'live.flowy.llink', + protocols: [ + { name: 'llink', schemes: ['llink'] }, + ], + extendInfo: { + NSMicrophoneUsageDescription: 'llink needs microphone access to record audio messages.', + NSCameraUsageDescription: 'llink needs camera access to record video messages.', + }, + osxSign: {}, + osxNotarize: { + keychainProfile: 'default', + } + }, + publishers: [ + { + name: '@electron-forge/publisher-gcs', + config: { + storageOptions: { + projectId: 'flowy-prod-440017' + }, + bucket: 'flowy-releases', + folder: 'llink', + // NOTE: commented out because we do not want to apply per object ACL which conflicts with uniform bucket-level access that's already public + // public: true + } + } + ], + rebuildConfig: {}, + makers: [ + new MakerSquirrel((arch) => ({ + remoteReleases: `https://storage.googleapis.com/flowy-releases/llink/win32/${arch}`, + // Windows code signing via Azure Trusted Signing. Dormant unless the workflow + // provisions the dlib + metadata file and exports these env vars, so local + // `yarn make` and unsigned CI builds both work unchanged. + windowsSign: process.env.AZURE_METADATA_JSON_PATH ? { + signWithParams: [ + '/v', + '/fd', 'SHA256', + '/tr', 'http://timestamp.acs.microsoft.com', + '/td', 'SHA256', + '/dlib', process.env.AZURE_DLIB_PATH, + '/dmdf', process.env.AZURE_METADATA_JSON_PATH, + ].join(' '), + } : undefined, + })), + new MakerZIP((arch) => ({ + macUpdateManifestBaseUrl: `https://storage.googleapis.com/flowy-releases/llink/darwin/${arch}` + }), ['darwin']), + new MakerRpm({}), + new MakerDeb({}), + ], + plugins: [ + new VitePlugin({ + // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. + // If you are familiar with Vite configuration, it will look really familiar. + build: [ + { + // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. + entry: 'src/main.ts', + config: 'vite.main.config.ts', + target: 'main', + }, + { + entry: 'src/preload.ts', + config: 'vite.preload.config.ts', + target: 'preload', + }, + ], + renderer: [ + { + name: 'main_window', + config: 'vite.renderer.config.mts', + }, + { + name: 'autoplay_window', + config: 'vite.autoplay.config.mts', + }, + { + name: 'huddle_window', + config: 'vite.huddle.config.mts', + }, + { + name: 'screen_record_window', + config: 'vite.screen-record.config.mts', + }, + ], + }), + // Fuses are used to enable/disable various Electron functionality + // at package time, before code signing the application + new FusesPlugin({ + version: FuseVersion.V1, + [FuseV1Options.RunAsNode]: false, + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + }), + ], +}; + +export default config; diff --git a/js/package.json b/js/package.json index c845ee0..f15f223 100644 --- a/js/package.json +++ b/js/package.json @@ -1,81 +1,82 @@ -{ - "name": "Flowy.llink", - "productName": "Flowy.llink", - "version": "1.1.0", - "description": "Flowy.llink is a team communication app for teams", - "main": ".vite/build/main.js", - "private": true, - "scripts": { - "start": "electron-forge start", - "package": "APP_ENV=prod electron-forge package --arch=arm64 && APP_ENV=prod electron-forge package --arch=x64", - "make": "APP_ENV=prod electron-forge make --arch=arm64 && APP_ENV=prod electron-forge make --arch=x64", - "make:win": "electron-forge make --platform=win32 --arch=x64 && electron-forge make --platform=win32 --arch=arm64", - "release": "echo '\n⚠️ Have you bumped the version in package.json? (current: '$(node -p \"require('./package.json').version\")') [y/N]' && read -r answer && [ \"$answer\" = \"y\" ] && APP_ENV=prod electron-forge publish --arch=arm64 && APP_ENV=prod electron-forge publish --arch=x64 && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/arm64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/x64/RELEASES.json", - "invalidate-gcs-cache": "gsutil -h \"Cache-Control:no-cache, no-store, max-age=0\" cp gs://flowy-releases/llink/darwin/arm64/RELEASES.json gs://flowy-releases/llink/darwin/arm64/RELEASES.json ; gsutil -h \"Cache-Control:no-cache, no-store, max-age=0\" cp gs://flowy-releases/llink/darwin/x64/RELEASES.json gs://flowy-releases/llink/darwin/x64/RELEASES.json", - "lint": "eslint --ext .ts,.tsx .", - "compile": "npx tsc --noEmit 2>&1 | grep '^src/'" - }, - "keywords": [], - "author": { - "name": "talksik", - "email": "patel.arjun50@gmail.com" - }, - "license": "MIT", - "packageManager": "yarn@1.22.22", - "devDependencies": { - "@electron-forge/cli": "^7.11.1", - "@electron-forge/maker-deb": "^7.11.1", - "@electron-forge/maker-rpm": "^7.11.1", - "@electron-forge/maker-squirrel": "^7.11.1", - "@electron-forge/maker-zip": "^7.11.1", - "@electron-forge/plugin-auto-unpack-natives": "^7.11.1", - "@electron-forge/plugin-fuses": "^7.11.1", - "@electron-forge/plugin-vite": "^7.11.1", - "@electron-forge/publisher-gcs": "^7.11.1", - "@electron/fuses": "^1.8.0", - "@tailwindcss/vite": "^4.2.0", - "@tanstack/eslint-plugin-query": "^5.91.4", - "@types/electron-squirrel-startup": "^1.0.2", - "@types/node": "^25.3.0", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", - "@vitejs/plugin-react": "^5.1.4", - "electron": "40.6.0", - "eslint": "^8.57.1", - "eslint-plugin-import": "^2.32.0", - "eslint-plugin-react-hooks": "^7.0.1", - "typescript": "^5.9.3", - "vite": "^5.4.21" - }, - "dependencies": { - "@livekit/components-react": "^2.9.20", - "@livekit/components-styles": "^1.2.0", - "@tanstack/react-query": "^5.90.21", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "electron-squirrel-startup": "^1.0.1", - "firebase": "^12.10.0", - "highlight.js": "^11.11.1", - "livekit-client": "^2.18.0", - "lucide-react": "^0.575.0", - "next-themes": "^0.4.6", - "radix-ui": "^1.4.3", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-markdown": "^10.1.0", - "react-router-dom": "^7.13.0", - "react-use": "^17.6.0", - "rehype-highlight": "^7.0.2", - "remark-gfm": "^4.0.1", - "shadcn": "^3.8.5", - "sonner": "^2.0.7", - "tailwind-merge": "^3.5.0", - "tailwindcss": "^4.2.0", - "tw-animate-css": "^1.4.0", - "update-electron-app": "^3.1.2", - "zod": "^4.3.6", - "zustand": "^5.0.11" - } -} +{ + "name": "Flowy.llink", + "productName": "Flowy.llink", + "version": "1.1.0", + "description": "Flowy.llink is a team communication app for teams", + "main": ".vite/build/main.js", + "private": true, + "scripts": { + "start": "electron-forge start", + "package:mac": "APP_ENV=prod electron-forge package --arch=arm64 && APP_ENV=prod electron-forge package --arch=x64", + "make:mac": "APP_ENV=prod electron-forge make --arch=arm64 && APP_ENV=prod electron-forge make --arch=x64", + "make:win": "electron-forge make --platform=win32 --arch=x64", + "publish:win": "electron-forge publish --platform=win32 --arch=x64", + "publish:mac": "echo '\n⚠️ Have you bumped the version in package.json? (current: '$(node -p \"require('./package.json').version\")') [y/N]' && read -r answer && [ \"$answer\" = \"y\" ] && APP_ENV=prod electron-forge publish --arch=arm64 && APP_ENV=prod electron-forge publish --arch=x64", + "invalidate-gcs-cache": "gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/arm64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/x64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/win32/x64/RELEASES.json", + "lint": "eslint --ext .ts,.tsx .", + "compile": "npx tsc --noEmit 2>&1 | grep '^src/'" + }, + "keywords": [], + "author": { + "name": "talksik", + "email": "patel.arjun50@gmail.com" + }, + "license": "MIT", + "packageManager": "yarn@1.22.22", + "devDependencies": { + "@electron-forge/cli": "^7.11.1", + "@electron-forge/maker-deb": "^7.11.1", + "@electron-forge/maker-rpm": "^7.11.1", + "@electron-forge/maker-squirrel": "^7.11.1", + "@electron-forge/maker-zip": "^7.11.1", + "@electron-forge/plugin-auto-unpack-natives": "^7.11.1", + "@electron-forge/plugin-fuses": "^7.11.1", + "@electron-forge/plugin-vite": "^7.11.1", + "@electron-forge/publisher-gcs": "^7.11.1", + "@electron/fuses": "^1.8.0", + "@tailwindcss/vite": "^4.2.0", + "@tanstack/eslint-plugin-query": "^5.91.4", + "@types/electron-squirrel-startup": "^1.0.2", + "@types/node": "^25.3.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "@vitejs/plugin-react": "^5.1.4", + "electron": "40.6.0", + "eslint": "^8.57.1", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-react-hooks": "^7.0.1", + "typescript": "^5.9.3", + "vite": "^5.4.21" + }, + "dependencies": { + "@livekit/components-react": "^2.9.20", + "@livekit/components-styles": "^1.2.0", + "@tanstack/react-query": "^5.90.21", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "electron-squirrel-startup": "^1.0.1", + "firebase": "^12.10.0", + "highlight.js": "^11.11.1", + "livekit-client": "^2.18.0", + "lucide-react": "^0.575.0", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.13.0", + "react-use": "^17.6.0", + "rehype-highlight": "^7.0.2", + "remark-gfm": "^4.0.1", + "shadcn": "^3.8.5", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.0", + "tw-animate-css": "^1.4.0", + "update-electron-app": "^3.1.2", + "zod": "^4.3.6", + "zustand": "^5.0.11" + } +}