diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..722c590 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,193 @@ +name: nightly + +on: + workflow_dispatch: + inputs: + branch: + description: Branch to build + required: true + default: main + type: string + +concurrency: + group: nightly-${{ inputs.branch }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + preflight: + name: preflight + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 15 + outputs: + nightly_id: ${{ steps.nightly.outputs.nightly_id }} + nightly_version: ${{ steps.nightly.outputs.nightly_version }} + source_ref: ${{ steps.nightly.outputs.source_ref }} + source_sha: ${{ steps.nightly.outputs.source_sha }} + + steps: + - name: Validate branch + shell: bash + env: + INPUT_BRANCH: ${{ inputs.branch }} + run: | + source_ref="${INPUT_BRANCH}" + if [[ -z "$source_ref" ]]; then + echo "Branch is required." >&2 + exit 1 + fi + + if [[ "$source_ref" == refs/* || "$source_ref" == origin/* ]]; then + echo "Enter a branch name such as main or feature/nightly, not $source_ref." >&2 + exit 1 + fi + + git check-ref-format --branch "$source_ref" >/dev/null + + - name: Checkout source branch + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Resolve nightly metadata + id: nightly + shell: bash + env: + INPUT_BRANCH: ${{ inputs.branch }} + RUN_NUMBER: ${{ github.run_number }} + run: | + stamp="$(date -u +'%Y%m%d')" + base_version="$(node -p "require('./package.json').version.replace(/[-+].*$/, '')")" + nightly_id="${stamp}.${RUN_NUMBER}" + nightly_version="${base_version}-nightly.${nightly_id}" + + { + echo "nightly_id=$nightly_id" + echo "nightly_version=$nightly_version" + echo "source_ref=$INPUT_BRANCH" + echo "source_sha=$(git rev-parse HEAD)" + } >> "$GITHUB_OUTPUT" + + build: + name: ${{ matrix.label }} + runs-on: ${{ matrix.os }} + needs: preflight + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - label: windows-x64 + os: blacksmith-2vcpu-windows-2025 + builder_args: "--win nsis portable --x64" + artifact_paths: | + dist-release/*-x64.exe + dist-release/latest*.yml + dist-release/*.blockmap + - label: windows-arm64 + os: blacksmith-2vcpu-windows-2025 + builder_args: "--win nsis portable --arm64" + artifact_paths: | + dist-release/*-arm64.exe + - label: macos-x64 + os: blacksmith-6vcpu-macos-15 + builder_args: "--mac dmg zip --x64" + artifact_paths: | + dist-release/*.dmg + dist-release/*-x64.zip + - label: macos-arm64 + os: blacksmith-6vcpu-macos-15 + builder_args: "--mac dmg zip --arm64" + artifact_paths: | + dist-release/*.dmg + dist-release/*-arm64.zip + - label: linux-x64 + os: blacksmith-2vcpu-ubuntu-2404 + builder_args: "--linux AppImage deb --x64" + artifact_paths: | + dist-release/*-x86_64.AppImage + dist-release/*-linux-x64.AppImage + dist-release/*-amd64.deb + dist-release/*-linux-x64.deb + dist-release/latest-linux.yml + - label: linux-arm64 + os: blacksmith-2vcpu-ubuntu-2404-arm + builder_args: "--linux AppImage deb --arm64" + artifact_paths: | + dist-release/*-arm64.AppImage + dist-release/*-arm64.deb + dist-release/latest-linux-arm64.yml + + env: + CSC_IDENTITY_AUTO_DISCOVERY: "false" + ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron + ELECTRON_BUILDER_CACHE: ${{ github.workspace }}/.cache/electron-builder + NIGHTLY_VERSION: ${{ needs.preflight.outputs.nightly_version }} + npm_config_audit: "false" + npm_config_fund: "false" + USE_SYSTEM_FPM: ${{ matrix.label == 'linux-arm64' && 'true' || 'false' }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ needs.preflight.outputs.source_sha }} + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: package-lock.json + + - name: Cache Electron binaries + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/.cache/electron + ${{ github.workspace }}/.cache/electron-builder + key: ${{ runner.os }}-electron-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-electron- + + - name: Install Linux packaging packages + if: runner.os == 'Linux' + env: + DEBIAN_FRONTEND: noninteractive + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends fakeroot rpm + + - name: Install FPM for arm64 deb builds + if: matrix.label == 'linux-arm64' + run: | + sudo apt-get install -y ruby ruby-dev build-essential + sudo gem install fpm + + - name: Set nightly package version + run: node tools/sync-release-version.mjs "$NIGHTLY_VERSION" + + - name: Install dependencies + run: npm ci --prefer-offline --no-audit --progress=false + + - name: Build app + run: npm run build + + - name: Package installers + run: npx electron-builder --publish never ${{ matrix.builder_args }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: openstroid-nightly-${{ needs.preflight.outputs.nightly_id }}-${{ matrix.label }} + path: ${{ matrix.artifact_paths }} + if-no-files-found: error diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..4e213c6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,44 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1783224372, + "narHash": "sha256-8i/87eeoqiGE4yOTjwSA3Eh/ziJRQEmd/unYU+K27sk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d407951447dcd00442e97087bf374aad70c04cea", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-electron": { + "locked": { + "lastModified": 1782847189, + "narHash": "sha256-twXPFqFsrrY5r28Zh7Homgcp2gUMBgQ6WDS98Q/3xFI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b6018f87da91d19d0ab4cf979885689b469cdd41", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "nixpkgs-electron": "nixpkgs-electron" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..44f98f6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,103 @@ +{ + description = "OpenStroid"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs-electron.url = "github:NixOS/nixpkgs/nixos-25.11"; + }; + + outputs = { + self, + nixpkgs, + nixpkgs-electron, + }: let + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + pkgsFor = system: import nixpkgs {inherit system;}; + electronPkgsFor = system: + import nixpkgs-electron { + inherit system; + config.permittedInsecurePackages = ["electron-37.10.3"]; + }; + linuxElectronConfig = pkgs: electronPackage: { + packages = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [electronPackage]; + env = pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isLinux '' + export ELECTRON_SKIP_BINARY_DOWNLOAD=1 + export ELECTRON_OVERRIDE_DIST_PATH="${electronPackage}/bin" + export npm_config_electron_skip_binary_download=true + ''; + }; + in { + devShells = forAllSystems (system: let + pkgs = pkgsFor system; + linuxElectron = linuxElectronConfig pkgs (electronPkgsFor system).electron_37; + in { + default = pkgs.mkShell { + packages = with pkgs; + [ + bun + nodejs_22 + pkg-config + python3 + ] + ++ linuxElectron.packages + ++ lib.optionals stdenv.hostPlatform.isLinux [ + fakeroot + rpm + ruby + ]; + + shellHook = + '' + export ELECTRON_CACHE="$PWD/.cache/electron" + export ELECTRON_BUILDER_CACHE="$PWD/.cache/electron-builder" + '' + + linuxElectron.env; + }; + }); + + apps = forAllSystems (system: let + pkgs = pkgsFor system; + linuxElectron = linuxElectronConfig pkgs (electronPkgsFor system).electron_37; + openstroidDev = pkgs.writeShellApplication { + name = "openstroid-dev"; + runtimeInputs = + [ + pkgs.bun + pkgs.nodejs_22 + ] + ++ linuxElectron.packages; + text = + linuxElectron.env + + '' + exec bun run dev "$@" + ''; + }; + in { + default = { + type = "app"; + program = "${openstroidDev}/bin/openstroid-dev"; + }; + }); + + formatter = forAllSystems (system: let + pkgs = pkgsFor system; + in + pkgs.writeShellApplication { + name = "openstroid-fmt"; + runtimeInputs = [pkgs.alejandra]; + text = '' + if [ "$#" -eq 0 ]; then + set -- flake.nix + fi + + exec alejandra "$@" + ''; + }); + }; +} diff --git a/tools/fix-electron-install.cjs b/tools/fix-electron-install.cjs index c425885..f535c5a 100644 --- a/tools/fix-electron-install.cjs +++ b/tools/fix-electron-install.cjs @@ -10,6 +10,10 @@ const distPath = path.join(electronDir, 'dist'); const pathFile = path.join(electronDir, 'path.txt'); const platformPath = getPlatformPath(); +function isTruthy(value) { + return /^(1|true|yes)$/i.test(value ?? ''); +} + function getPlatformPath() { const platform = process.env.npm_config_platform || process.platform; @@ -66,6 +70,14 @@ async function extractZip(zipPath, destination) { } (async () => { + if ( + isTruthy(process.env.ELECTRON_SKIP_BINARY_DOWNLOAD) || + isTruthy(process.env.npm_config_electron_skip_binary_download) + ) { + console.log('Skipping Electron binary install because Electron binary downloads are disabled.'); + return; + } + if (isInstalled()) { return; }