filebase 0.2.0

Query a directory of Slipcase containers by their flyleaf and look at what comes back
Documentation
# The Windows checks a machine can run: that the tree builds and passes here,
# that the shipped binary imports nothing Windows does not have, that the
# committed icons still match the drawings they were generated from, and, on a
# release, the unsigned Store package. The suite is repeated from the fleet's
# `ci.yml` because that runs on Linux and three things here are compiled only
# for this target — `build.rs`'s manifest arm, the `ico` dependency, and the
# window icon `include_bytes!` reaches. Out of reach: the window, the installer
# scripts, a file association, the certification kit, and the icon as a person
# sees it.
#
# Author: David M. Anderson
# Built with AI assistance (Claude, Anthropic)

name: windows

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  # The release event is what attaches the package. A person's token creates
  # the release, so the event fires.
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      # Optional, because a screenshots run has no tag to give. With one the
      # package is attached to that release; without one it is built and kept
      # as an artefact.
      tag:
        description: 'Tag to package for (e.g. v0.1.4)'
        required: false
        type: string
      # A choice rather than the boolean `apple-silicon.yml` has, because this
      # lane has a third thing to ask for. Three of the controls the recipes
      # name have never been measured on this platform, so `reference` takes
      # the one plain frame they get measured off and `full` takes the set.
      screenshots:
        description: 'Store screenshots'
        required: false
        default: 'no'
        type: choice
        options: ['no', 'reference', 'full']

permissions:
  contents: write

concurrency:
  # Keyed by the tag when this is releasing, and by the event and the ref when
  # it is not. `github.ref` is refs/heads/main for a workflow_dispatch as much
  # as for a push, so keying on it alone lets a push to main cancel a dispatched
  # run - and a dispatch asking only for screenshots has no tag to key on.
  group: windows-${{ github.event.release.tag_name || inputs.tag || format('{0}-{1}', github.event_name, github.ref) }}
  # A superseded check is worth cancelling; a half-built release is not.
  cancel-in-progress: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}

jobs:
  windows:
    runs-on: windows-latest

    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.release.tag_name || inputs.tag || github.ref }}

      # The packaging scripts are the only code here that nothing else compiles
      # or runs, and there is no PowerShell on the Linux lane. The step is
      # excelano/.github's because it is the same one in every repository that
      # has them.
      - name: The packaging scripts parse
        uses: excelano/.github/.github/actions/powershell-parses@main

      - name: Install Rust
        run: rustup toolchain install stable --profile minimal

      - uses: Swatinem/rust-cache@v2

      - name: What is installed
        run: rustc -V; cargo -V

      - name: Build
        run: cargo build --workspace --all-targets

      - name: Test
        run: cargo test --workspace

      # `CLAUDE.md` says clippy must be silent, so a warning is an error here.
      - name: Clippy
        run: cargo clippy --workspace --all-targets -- -D warnings

      # The check Slipcase 0.1.1 needed and did not have. It was packaged,
      # certified by the Windows App Certification Kit, submitted, and failed
      # Store policy 10.2.4.1 because it imported VCRUNTIME140.dll from the
      # Visual C++ Redistributable - so on the tester's clean machine it
      # installed and then would not start. Nothing else in this file could
      # catch it: a GitHub runner has Visual Studio, exactly like every machine
      # this project has ever built on.
      #
      # `.cargo/config.toml` is the fix and this is the guard on it. A
      # dependency added on any of the three platforms can pull in a DLL that is
      # not part of Windows without anybody here noticing, and the day that
      # happens is the day this step has to be the thing that says so.
      #
      # The release binary rather than the debug one, because the release binary
      # is what ships and is the only one whose import table is the artefact's.
      - name: Every DLL the shipped binary imports comes with Windows
        run: |
          cargo build --release -p filebase
          powershell -ExecutionPolicy Bypass -File packaging/windows/check-imports.ps1

      # The release binary is also the only place two other properties exist. The
      # GUI subsystem comes from an attribute that is off under
      # `debug_assertions`, and a console-subsystem build puts a console window
      # behind the application when a file manager launches it. The DPI
      # declaration comes from `build.rs` handing the linker a manifest, and the
      # certification kit reads it out of the binary rather than out of the
      # running process. `build-msix.ps1` refuses on the first and the kit
      # reports the second, and neither of those runs here - so both are read
      # straight out of the artefact instead.
      - name: The shipped binary is a GUI subsystem one and declares its DPI awareness
        shell: pwsh
        run: |
          $meta = cargo metadata --format-version 1 --no-deps | ConvertFrom-Json
          $exe = Join-Path $meta.target_directory 'release\filebase.exe'
          $bytes = [System.IO.File]::ReadAllBytes($exe)
          $pe = [BitConverter]::ToInt32($bytes, 0x3C)
          $subsystem = [BitConverter]::ToUInt16($bytes, $pe + 92)
          if ($subsystem -ne 2) {
            throw "subsystem $subsystem, and a shipped build must be 2 (Windows GUI)"
          }
          $text = [System.Text.Encoding]::UTF8.GetString($bytes)
          if (-not $text.Contains('PerMonitorV2')) {
            throw 'no PerMonitorV2 in the binary - build.rs did not embed the application manifest'
          }
          Write-Host 'subsystem 2, and the application manifest is embedded'

      # **No install check, where every sibling has one.** That step drives
      # `install.ps1` and reads the registry back to prove an association goes
      # on and comes off cleanly. This application declares no file type and
      # ships no install scripts: there is no association to register, because
      # it opens a folder and Windows has no way to say that. Nothing was
      # dropped here; there is nothing to check.

      # The Store takes the package unsigned, and the runner assembles the one that
      # is uploaded. The steps are excelano/.github's, because they were the same in
      # every repository that has this target; the publisher is the organisation
      # secret, being the one value in the identity that is an account's rather than
      # this product's.
      - name: The Store package
        uses: excelano/.github/.github/actions/store-package@main
        with:
          publisher: ${{ secrets.STORE_PUBLISHER }}
          tag: ${{ github.event.release.tag_name || inputs.tag }}
          token: ${{ secrets.GITHUB_TOKEN }}
      # Off unless asked for. A bug fix should not pay for a set of Store
      # screenshots, and a set nobody asked for is a set nobody looks at.
      # `shots.ps1` carries the recipes and is the same command that retakes
      # them by hand on the Windows machine; `screenshot.ps1` beside it is the
      # driver and knows nothing about Filebase.
      #
      # `shots.ps1` launches the executable directly with a folder as an
      # argument, so no association has to be installed first. The siblings
      # install one here because the shell is what routes their document.

      - name: A folder of containers to ask about
        if: inputs.screenshots && inputs.screenshots != 'no'
        shell: bash
        run: |
          cargo install slipcase --quiet
          ./packaging/demo-corpus.sh
          ./packaging/demo-corpus.sh --lang de

      # Both languages, because the listing is in both. `shots.ps1` refuses a
      # language it has no set for, and opens a German corpus for the German
      # set: this application's frames are almost entirely a container's flyleaf,
      # so the window's language alone would not make a German frame. The
      # language is also the directory the set lands in, which is how the Store
      # files a frame and what `ship` refuses a set for the want of.
      - name: The Store screenshots
        if: inputs.screenshots && inputs.screenshots != 'no'
        uses: excelano/.github/.github/actions/store-screenshots@main
        with:
          mode: ${{ inputs.screenshots }}
          languages: en de

      - name: The committed icons and Store assets still match their generator
        run: |
          cargo run --manifest-path packaging/make-icons/Cargo.toml
          git diff --exit-code -- packaging/icons packaging/windows/filebase.ico packaging/windows/assets