Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
## Summary

- Describe the user-facing outcome.
- Describe the user-facing outcome and any compatibility or migration impact.
- If the change is internal-only, say so.

## Validation

- List the commands or checks run.

## Changelog

- [ ] Added `changes/<pr-number>.<type>.md`
- [ ] This is dependency maintenance, or it is internal-only and the PR
explains why a maintainer should apply `skip-changelog`:
- List the exact commands or checks run and the real boundaries exercised.
- Do not replace results with “tests passed.”
57 changes: 56 additions & 1 deletion .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ on:
required: false
default: 14
type: number
sign:
description: Sign and notarize the macOS build (requires Apple secrets).
required: false
default: false
type: boolean
workflow_dispatch:
inputs:
checkout_ref:
Expand All @@ -24,6 +29,11 @@ on:
required: false
default: 14
type: number
sign:
description: Sign and notarize the macOS build (requires Apple secrets).
required: false
default: false
type: boolean
permissions:
contents: read

Expand Down Expand Up @@ -116,9 +126,54 @@ jobs:
- name: Test the locked desktop crate
run: cargo test --release --locked --manifest-path desktop/src-tauri/Cargo.toml

- name: Build the unsigned desktop installer
- name: Prepare the Apple notarization key
id: apple_key
if: runner.os == 'macOS' && inputs.sign
shell: bash
env:
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
run: |
if [[ -z "$APPLE_API_KEY_P8" ]]; then
echo "::error::inputs.sign is true but Apple signing secrets are missing." >&2
exit 1
fi
key_path="$RUNNER_TEMP/apple_api_key.p8"
printf '%s' "$APPLE_API_KEY_P8" | openssl base64 -d -A > "$key_path"
echo "path=$key_path" >> "$GITHUB_OUTPUT"

- name: Build the desktop installer
working-directory: desktop
env:
VIDXP_DESKTOP_SIGN: ${{ (runner.os == 'macOS' && inputs.sign) && '1' || '' }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_PATH: ${{ steps.apple_key.outputs.path }}
run: npm run desktop:build

- name: Verify signing and notarization
if: runner.os == 'macOS' && inputs.sign
working-directory: desktop
shell: bash
run: |
dmg="$(find src-tauri/target/release/bundle/dmg -maxdepth 1 -name '*.dmg' | head -n1)"
[[ -n "$dmg" ]]
# Tauri removes the intermediate .app after packaging the DMG.
codesign --verify --strict --verbose=2 "$dmg"
mountpoint="$(mktemp -d "$RUNNER_TEMP/vidxp-dmg.XXXXXX")"
trap 'hdiutil detach "$mountpoint" -quiet >/dev/null 2>&1 || true; rmdir "$mountpoint" >/dev/null 2>&1 || true' EXIT
hdiutil attach "$dmg" -readonly -nobrowse -mountpoint "$mountpoint"
app="$(find "$mountpoint" -maxdepth 1 -name '*.app' | head -n1)"
[[ -n "$app" ]]
# The .app is signed (Developer ID), hardened-runtime, notarized + stapled.
codesign --verify --strict --verbose=2 "$app"
codesign -dvv "$app" 2>&1 | grep -i 'Authority=Developer ID Application'
codesign -dvv "$app" 2>&1 | grep -iE 'flags=.*runtime'
xcrun stapler validate "$app"
spctl -a -t exec -vv "$app"

- name: Verify the Windows app uses the GUI subsystem
if: runner.os == 'Windows'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ jobs:
with:
artifact_retention_days: 30
checkout_ref: ${{ inputs.head_sha }}
sign: true
secrets: inherit

containers:
needs: contract
Expand Down
20 changes: 15 additions & 5 deletions desktop/scripts/build-desktop.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,21 @@ const executable = resolve(
".bin",
process.platform === "win32" ? "tauri.cmd" : "tauri",
);
const result = spawnSync(
executable,
["build", "--bundles", bundleSpec.bundle, "--ci", "--no-sign", "--", "--locked"],
{ shell: process.platform === "win32", stdio: "inherit" },
);

// Sign + notarize only when explicitly requested on macOS (release builds).
const signMacos =
process.platform === "darwin" && process.env.VIDXP_DESKTOP_SIGN === "1";

const buildArgs = ["build", "--bundles", bundleSpec.bundle, "--ci"];
if (!signMacos) {
buildArgs.push("--no-sign");
}
buildArgs.push("--", "--locked");

const result = spawnSync(executable, buildArgs, {
shell: process.platform === "win32",
stdio: "inherit",
});

if (result.error) {
throw result.error;
Expand Down
12 changes: 12 additions & 0 deletions desktop/src-tauri/entitlements.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
4 changes: 2 additions & 2 deletions desktop/src-tauri/src/target_profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ fn collect_probe_output(
)
}

#[cfg(test)]
#[cfg(all(test, windows))]
fn collect_version_output(executable: &Path) -> Result<ProbeOutput, TargetError> {
collect_command_output(executable, &["--version"], "The VidXP version check", None)
}
Expand Down Expand Up @@ -707,7 +707,7 @@ pub fn validate_executable(
validate_executable_with(path, desktop_version, collect_probe_output)
}

#[cfg(test)]
#[cfg(all(test, windows))]
fn inspect_executable(path: &Path, desktop_version: &str) -> Result<TargetInspection, TargetError> {
inspect_executable_with(
path,
Expand Down
3 changes: 2 additions & 1 deletion desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
}
},
"macOS": {
"minimumSystemVersion": "13.0"
"minimumSystemVersion": "13.0",
"entitlements": "entitlements.plist"
}
}
}
57 changes: 41 additions & 16 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,12 @@ boundary, say so.
The repository's
[pull request template](../.github/pull_request_template.md) is the required
starting point. GitHub loads it automatically for new pull requests. Complete
all three sections:
both sections:

- **Summary:** describe the user-facing outcome and important compatibility or
migration behavior.
migration behavior. Say explicitly when a change is internal-only.
- **Validation:** list the exact commands and real boundaries exercised. Do not
replace results with “tests passed.”
- **Changelog:** add the correct fragment, or explain why the change is
dependency-only/internal and should receive `skip-changelog`.

Keep the description compact, but include:

Expand All @@ -225,29 +223,56 @@ Keep the description compact, but include:
- validation actually performed; and
- screenshots or sample output for user-interface changes.

Use Conventional Commit prefixes for commits, for example:
Use Conventional Commit prefixes for PR titles and commits that will land on a
release channel, for example:

```text
feat(mcp): add clip artifact discovery
fix(frontend): keep search form submittable
docs: clarify local installation profiles
ci(release): verify signed macOS installers
```

## Release notes and versions

Release Please derives versions and changelog entries from Conventional
Commits. Make the squash-merge commit or the commits retained by a regular
merge describe the public change accurately:
Both release channels use Release Please's Python strategy. Release Please
reads the Conventional Commits that land on `main` or `release`. `main`
produces prereleases and `release` produces stable releases; both use the same
commit-type and changelog rules.

- `feat` creates a feature release.
- `fix` and `perf` create a patch release.
- `!` or a `BREAKING CHANGE:` footer creates a breaking release.
- `docs`, `test`, `ci`, `build`, `refactor`, and `chore` do not publish by
themselves.
| Landed commit | Changelog | Version effect |
|---|---|---|
| `feat(scope): ...` | Included | Minor |
| `fix`, `perf`, `deps`, `revert`, or `docs` | Included | Patch |
| `<type>(scope)!: ...` or a `BREAKING CHANGE:` footer | Included as breaking | Major |
| `style`, `chore`, `refactor`, `test`, `build`, or `ci` | Hidden | None by itself |
| Unrecognized type | Omitted | None |

For example, a breaking API change uses:

```text
feat(api)!: replace the legacy search response
```

Use a hidden type only for work with no user-visible release impact. A public
fix implemented in CI or packaging still needs `fix` or `feat`, not `ci` or
`build`.

For squash merges, the final squash subject—normally the PR title—must be the
intended Conventional Commit. To keep an internal-facing PR title or describe
multiple release entries, add this block to the PR description:

```text
BEGIN_COMMIT_OVERRIDE
feat(desktop): sign and notarize macOS installers

fix(desktop): preserve the signed installer artifact
END_COMMIT_OVERRIDE
```

Release Please uses the block instead of the squash commit message. It does not
rename the PR, and the override does not work with plain merges.

Write user-visible `feat`, `fix`, and `perf` subjects for users, not for the
implementation history. Internal corrections to an unreleased feature should
remain part of that feature rather than appear as fictional public bug fixes.
Release Please prepares the combined version and changelog in a pull request;
do not edit released changelog sections by hand.

Expand Down