diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 704a2129f..ee9f3f0e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,26 @@ jobs: node-version: 22 - run: npm run docs:check + appstream: + name: AppStream metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + # `appstream` is not on the runner image, and it is the only thing this job + # needs — no Node, no npm ci. Same reasoning as the Docs job above. + - run: sudo apt-get update -qq && sudo apt-get install -y -qq appstream + # This gate exists because nothing else reads the file. It is upstream + # metadata with no consumer in this repository yet, so without validation + # here the first thing to discover a broken edit would be a Flathub + # reviewer, weeks later, on a submission that then has to be redone. + # + # --no-net deliberately: with network validation on, this job also fetches + # every screenshot, and an unreachable raw.githubusercontent.com would turn + # a metadata check into a flaky one on unrelated PRs. Dead screenshot URLs + # are caught by Flathub at submission time, which is the only place the + # answer actually matters. + - run: appstreamcli validate --no-net --explain build/com.getopenscreen.OpenScreen.metainfo.xml + test: name: Test runs-on: ubuntu-latest diff --git a/.github/workflows/update-homebrew-cask.yml b/.github/workflows/update-homebrew-cask.yml index 26f04248a..8c867d01f 100644 --- a/.github/workflows/update-homebrew-cask.yml +++ b/.github/workflows/update-homebrew-cask.yml @@ -16,14 +16,49 @@ permissions: jobs: update-cask: runs-on: ubuntu-latest - if: (github.event_name == 'workflow_dispatch' || !github.event.release.prerelease) && vars.HOMEBREW_TAP_OWNER != '' && vars.HOMEBREW_TAP_REPO != '' + # The tap configuration has LEFT this `if:`, and that is the point of the change. + # `vars.HOMEBREW_TAP_OWNER != '' && vars.HOMEBREW_TAP_REPO != ''` here made the job + # `skipped`, and a skipped job is green: every release since this workflow was + # written has "succeeded" without publishing a cask, because neither variable has + # ever existed on the repository. Same failure as #148, where publish-winget.yml + # spent eight releases green and silent for exactly this reason — see the comment + # above its own `if:`. A guard that does nothing quietly guards nothing. + # + # So the job always starts, and a step announces the missing configuration. It costs + # a runner-minute per release; it buys "nothing was published" being visible in the + # run summary instead of inferable from a tap nobody thought to look at. + if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease env: TAP_OWNER: ${{ vars.HOMEBREW_TAP_OWNER }} TAP_REPO: ${{ vars.HOMEBREW_TAP_REPO }} CASK_NAME: ${{ vars.HOMEBREW_CASK_NAME || 'openscreen' }} + # `secrets` is not a context an `if:` can read, at job level or step level — only + # `env` is. Hence this boolean-as-string, which exposes whether the token is set + # without ever exposing its value. Same trick as publish-winget.yml. + HAS_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }} steps: - - name: Resolve tag and version + - name: Check the tap configuration + id: config + run: | + set -euo pipefail + # `homebrew-?*`, not `-n`: the fourth requirement in the warning below is a + # rule about the name itself, and a name test is the one of the four this step + # can actually apply. `getopenscreen/openscreen-tap` would clone, commit and + # push exactly like a real tap and still be untappable — a green run publishing + # to somewhere `brew tap` cannot resolve, which is the failure this whole + # workflow change exists to stop. `?*` also rejects a repository named the bare + # `homebrew-`, which the prefix alone would accept. + if [[ -n "$TAP_OWNER" && "$TAP_REPO" == homebrew-?* && "$HAS_TOKEN" == "true" ]]; then + echo "configured=true" >> "$GITHUB_OUTPUT" + echo "Tap: ${TAP_OWNER}/${TAP_REPO}, cask ${CASK_NAME}." + exit 0 + fi + echo "configured=false" >> "$GITHUB_OUTPUT" + echo "::warning title=Homebrew cask not updated::No cask was published. Needs (1) the repository variable HOMEBREW_TAP_OWNER, currently ${TAP_OWNER:-UNSET}; (2) HOMEBREW_TAP_REPO, currently ${TAP_REPO:-UNSET}; (3) the secret HOMEBREW_TAP_TOKEN, currently $([[ "$HAS_TOKEN" == "true" ]] && echo set || echo UNSET), with contents write on that repository; and (4) the tap repository itself, which must be named homebrew- for Homebrew to recognise it. See https://github.com/getopenscreen/openscreen/issues/335" + + - name: Resolve and validate tag id: meta + if: steps.config.outputs.configured == 'true' env: GH_EVENT_TAG: ${{ github.event.release.tag_name }} INPUT_TAG: ${{ inputs.tag }} @@ -34,11 +69,24 @@ jobs: echo "::error::No tag resolved from release event or workflow input" exit 1 fi + # The `prerelease` filter only covers the `release` event. `workflow_dispatch` + # takes free text — no git ref rule applies to it — so a manual replay of + # `v1.9.4-rc.2` would publish an RC as THE cask, and `brew upgrade` would hand + # it to everyone on stable. Homebrew accepts that version string happily; only + # this check refuses it. The same hole was closed in aur-publish.yml, where the + # free-text input also escaped a `sed` expression; here every use is quoted, so + # what is left is the wrong-version case and the tag reaching a Ruby file that + # users execute. + if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Refusing tag '$TAG' — expected a stable vMAJOR.MINOR.PATCH tag" + exit 1 + fi VERSION="${TAG#v}" echo "tag=$TAG" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Wait for release DMG assets + if: steps.config.outputs.configured == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ steps.meta.outputs.tag }} @@ -73,6 +121,7 @@ jobs: - name: Find macOS DMG assets id: assets + if: steps.config.outputs.configured == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ steps.meta.outputs.tag }} @@ -109,6 +158,7 @@ jobs: - name: Download DMGs and compute sha256 id: shas + if: steps.config.outputs.configured == 'true' env: REPO: ${{ github.repository }} TAG: ${{ steps.meta.outputs.tag }} @@ -125,6 +175,7 @@ jobs: echo "x64_sha=$X64_SHA" >> "$GITHUB_OUTPUT" - name: Checkout tap + if: steps.config.outputs.configured == 'true' uses: actions/checkout@v7 with: repository: ${{ env.TAP_OWNER }}/${{ env.TAP_REPO }} @@ -132,6 +183,7 @@ jobs: path: tap - name: Write cask file + if: steps.config.outputs.configured == 'true' env: REPO: ${{ github.repository }} TAG: ${{ steps.meta.outputs.tag }} @@ -185,6 +237,7 @@ jobs: EOF - name: Commit and push to tap + if: steps.config.outputs.configured == 'true' working-directory: tap env: VERSION: ${{ steps.meta.outputs.version }} diff --git a/build/com.getopenscreen.OpenScreen.metainfo.xml b/build/com.getopenscreen.OpenScreen.metainfo.xml new file mode 100644 index 000000000..4281aafa4 --- /dev/null +++ b/build/com.getopenscreen.OpenScreen.metainfo.xml @@ -0,0 +1,136 @@ + + + + com.getopenscreen.OpenScreen + + OpenScreen + Record your screen and polish the demo + + CC0-1.0 + MIT + + + Etienne Lescot + + + +

+ OpenScreen records your screen, webcam and audio, then hands you an editor + built for the thing you actually wanted: a demo worth watching. No + watermark, no subscription, no upload step. +

+

+ Recordings open straight into a timeline where the polish is the point: +

+
    +
  • Automatic zoom that follows the cursor, adjustable per region
  • +
  • Backgrounds, padding, rounded corners and shadows on the frame
  • +
  • Trim, cut and per-region playback speed
  • +
  • Webcam picture-in-picture, cursor smoothing and custom cursor themes
  • +
  • Local speech-to-text for captions — the model runs on your machine
  • +
  • Export to MP4 or GIF, rendered on the GPU
  • +
+

+ Screen capture goes through the desktop portal, so it works on Wayland and + X11 alike and asks for permission the way the rest of your desktop does. +

+
+ + + openscreen.desktop + + https://getopenscreen.com/ + https://github.com/getopenscreen/openscreen/issues + https://github.com/getopenscreen/openscreen + https://github.com/getopenscreen/openscreen/blob/main/CONTRIBUTING.md + + + + + https://raw.githubusercontent.com/getopenscreen/openscreen/main/public/preview4.png + Editing a recording: zoom regions, video effects and export settings + + + https://raw.githubusercontent.com/getopenscreen/openscreen/main/public/preview3.png + The timeline, with trim, speed and zoom regions on separate tracks + + + + + AudioVideo + Video + Recorder + + + + screen recorder + screencast + screen capture + video editor + demo + webcam + + + + pointing + keyboard + + + + + + + + + +
diff --git a/technical-documentation/engineering/ci-workflows.md b/technical-documentation/engineering/ci-workflows.md index 011529447..3ab5e98ea 100644 --- a/technical-documentation/engineering/ci-workflows.md +++ b/technical-documentation/engineering/ci-workflows.md @@ -86,8 +86,11 @@ The STT workflow uploads standalone archives for binary refresh and does not cur | `typecheck` | Ubuntu | `npx tsc --noEmit` | | `test` | Ubuntu | Vitest unit tests, Chromium installation, then browser-mode Vitest | | `build` | Ubuntu | `npx vite build`; this is not electron-builder packaging | +| `appstream` | Ubuntu | `appstreamcli validate` on `build/com.getopenscreen.OpenScreen.metainfo.xml` | | `semantic-pr` | Ubuntu | Validates Conventional Commit-style PR titles | +`build/com.getopenscreen.OpenScreen.metainfo.xml` is upstream AppStream metadata: the name, summary, description, licence, screenshots and release history a software centre shows instead of a bare icon. Nothing in this repository consumes it yet — the shipped deb installs a `.desktop` file and nine icon sizes and no `/usr/share/metainfo/` at all — so the `appstream` job is the only thing that can catch a broken edit before a Flathub reviewer does. Its component ID is `com.getopenscreen.OpenScreen`, deliberately not the Electron `appId` `com.etiennelescot.openscreen`: Flathub requires the ID to map to a domain the project controls, and `getopenscreen.com` is that domain. + Jobs that need the root dependencies use `.github/actions/setup`, which requests Node 22 and runs `npm ci`; callers perform checkout themselves. `docs.yml` is separate from the technical-documentation checker. It installs dependencies in `website/`, type-checks and builds the site, uploads a Pages artifact, and deploys only after a push to `main`. @@ -120,12 +123,12 @@ At a high level, the RC workflow creates or reuses `release/vX.Y.Z`, tags its ti These workflows run for stable published releases and support manual replay with a tag: -- `update-homebrew-cask.yml` waits for both macOS DMGs, hashes them, writes a cask, and pushes to the configured tap. +- `update-homebrew-cask.yml` waits for both macOS DMGs, hashes them, writes a cask, and pushes to the configured tap. Manual replay refuses any tag that is not a stable `vMAJOR.MINOR.PATCH`, because `workflow_dispatch` takes free text and the `prerelease` filter only covers the `release` event. - `publish-winget.yml` passes the matching NSIS release asset to `winget-releaser`. - `bump-nix-package.yml` computes `npmDepsHash`, updates `nix/package.nix`, and opens a PR. - `aur-publish.yml` hashes the pacman release asset, updates `PKGBUILD` and `.SRCINFO`, and pushes over SSH. -Each workflow gates itself on its required variables or credentials. `bump-nix-package.yml` uses the repository `GITHUB_TOKEN`; the others require external registry credentials described in [release and secrets](release-and-secrets.md). +Each workflow needs variables or credentials, and where it checks for them decides whether a missing one is visible. `update-homebrew-cask.yml` and `publish-winget.yml` check inside a step that emits a warning, so an unconfigured channel says so in the run summary; a job-level `if:` would instead report `skipped`, which reads as green and hid #148 for eight releases and the Homebrew cask for its entire existence (#335). `bump-nix-package.yml` uses the repository `GITHUB_TOKEN`; the others require the external registry credentials described in [release and secrets](release-and-secrets.md). ## Tier 4: automation and diagnostics diff --git a/technical-documentation/engineering/release-and-secrets.md b/technical-documentation/engineering/release-and-secrets.md index 4adac7d4d..ffbd4e82d 100644 --- a/technical-documentation/engineering/release-and-secrets.md +++ b/technical-documentation/engineering/release-and-secrets.md @@ -147,6 +147,10 @@ The bot token comes from a Discord application authorized with the `bot` scope. `bump-nix-package.yml` uses the workflow-scoped `GITHUB_TOKEN`; it requires repository contents and pull-request write permissions as declared in the workflow and has no additional long-lived secret. +**Homebrew publishing does not complete yet, and now says so.** `update-homebrew-cask.yml` has never published a cask — not once since it was written for the v1.5.0 pipeline. Neither `HOMEBREW_TAP_OWNER` nor `HOMEBREW_TAP_REPO` has ever existed on this repository, both sat in the job-level `if`, and an unconfigured job resolves to `skipped`, which is green: every release run reads as a success. The same failure as WinGet below, found the same way and fixed the same way — the configuration test now lives in a step that names what is missing (#335). Three things are needed, and the third is the one a variable cannot supply: `HOMEBREW_TAP_OWNER` and `HOMEBREW_TAP_REPO`; the `HOMEBREW_TAP_TOKEN` secret with contents write on that repository; and the tap repository itself, which **must** be named `homebrew-` — that prefix is how `brew tap` resolves a repository at all, so `getopenscreen/openscreen-tap` would be checked out and pushed to successfully and still be untappable. With `getopenscreen/homebrew-openscreen`, the install command is `brew install --cask getopenscreen/openscreen/openscreen`. + +Note what it would publish before turning it on: the two DMGs attached to the release — signed, notarized and stapled when the Apple credentials above are complete, ad-hoc-signed and un-notarized when they are not. A cask does not change either state, because `brew install --cask` runs the same Gatekeeper path as a manual download: on the ad-hoc artifact users still need the `xattr -rd com.apple.quarantine` step the README documents. What the tap buys is discovery and `brew upgrade`, not trust. + **WinGet publishing does not complete yet, and now says so.** `publish-winget.yml` starts on every stable release; whether it publishes depends on four prerequisites, and it names the missing ones in a `::warning::` instead of passing quietly. It used to pass quietly: the configuration test sat in the job-level `if`, an unconfigured job resolved to `skipped`, and a skipped job is green — so eight releases in a row reported success while publishing nothing, which is how #148 stayed open without anyone noticing. The four are: `WINGET_IDENTIFIER` (set, `OpenScreen.OpenScreen`); `WINGET_ACC_TOKEN` (absent — it must be a *classic* PAT with `public_repo`, since `winget-releaser` does not support fine-grained ones); a fork of `microsoft/winget-pkgs` under `getopenscreen`, which is where the action pushes its branch; and at least one version of the package already merged into `winget-pkgs`, because the action writes each manifest from the previous one and refuses to author the first. That first submission is manual, via `wingetcreate new`. Note what it would publish before turning it on: `winget-releaser` submits the **NSIS `.exe`** attached to the release to the community repository, and that installer is unsigned. Users who install through the Microsoft Store, or through `winget --source msstore`, get the Store package that Microsoft signs during certification instead. Publishing to the community source therefore adds a second, unsigned route alongside the signed one — worth doing deliberately rather than by flipping a variable.