mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat: add beta release channel
This commit is contained in:
4
.github/workflows/deploy-app.yml
vendored
4
.github/workflows/deploy-app.yml
vendored
@@ -4,9 +4,9 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
- '!v*-rc.*'
|
- '!v*-beta.*'
|
||||||
- 'app-v*'
|
- 'app-v*'
|
||||||
- '!app-v*-rc.*'
|
- '!app-v*-beta.*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
14
.github/workflows/desktop-release.yml
vendored
14
.github/workflows/desktop-release.yml
vendored
@@ -145,6 +145,7 @@ jobs:
|
|||||||
if [[ "$IS_SMOKE_TAG" != "true" ]]; then
|
if [[ "$IS_SMOKE_TAG" != "true" ]]; then
|
||||||
publish_mode="always"
|
publish_mode="always"
|
||||||
publish_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
publish_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
||||||
|
publish_args+=("-c.publish.channel=$RELEASE_CHANNEL")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run build --workspace="$DESKTOP_WORKSPACE" -- --publish "$publish_mode" --mac --${{ matrix.electron_arch }} "${publish_args[@]}"
|
npm run build --workspace="$DESKTOP_WORKSPACE" -- --publish "$publish_mode" --mac --${{ matrix.electron_arch }} "${publish_args[@]}"
|
||||||
@@ -154,7 +155,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: mac-manifest-${{ matrix.electron_arch }}
|
name: mac-manifest-${{ matrix.electron_arch }}
|
||||||
path: ${{ env.DESKTOP_PACKAGE_PATH }}/release/latest-mac.yml
|
path: ${{ env.DESKTOP_PACKAGE_PATH }}/release/${{ env.RELEASE_CHANNEL }}-mac.yml
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
finalize-mac-manifest:
|
finalize-mac-manifest:
|
||||||
@@ -227,8 +228,9 @@ jobs:
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
const arm64Text = fs.readFileSync('mac-manifest-arm64/latest-mac.yml', 'utf8');
|
const manifestName = `${process.env.RELEASE_CHANNEL}-mac.yml`;
|
||||||
const x64Text = fs.readFileSync('mac-manifest-x64/latest-mac.yml', 'utf8');
|
const arm64Text = fs.readFileSync(`mac-manifest-arm64/${manifestName}`, 'utf8');
|
||||||
|
const x64Text = fs.readFileSync(`mac-manifest-x64/${manifestName}`, 'utf8');
|
||||||
|
|
||||||
const arm64 = parseManifest(arm64Text);
|
const arm64 = parseManifest(arm64Text);
|
||||||
const x64 = parseManifest(x64Text);
|
const x64 = parseManifest(x64Text);
|
||||||
@@ -243,7 +245,7 @@ jobs:
|
|||||||
};
|
};
|
||||||
|
|
||||||
const output = toYaml(merged);
|
const output = toYaml(merged);
|
||||||
fs.writeFileSync('latest-mac.yml', output);
|
fs.writeFileSync(manifestName, output);
|
||||||
console.log('Merged manifest:\n' + output);
|
console.log('Merged manifest:\n' + output);
|
||||||
NODE
|
NODE
|
||||||
|
|
||||||
@@ -251,7 +253,7 @@ jobs:
|
|||||||
if: env.IS_SMOKE_TAG != 'true'
|
if: env.IS_SMOKE_TAG != 'true'
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: gh release upload "$RELEASE_TAG" latest-mac.yml --clobber --repo "${{ github.repository }}"
|
run: gh release upload "$RELEASE_TAG" "$RELEASE_CHANNEL-mac.yml" --clobber --repo "${{ github.repository }}"
|
||||||
|
|
||||||
publish-linux:
|
publish-linux:
|
||||||
needs: [create-release]
|
needs: [create-release]
|
||||||
@@ -316,6 +318,7 @@ jobs:
|
|||||||
if [[ "$IS_SMOKE_TAG" != "true" ]]; then
|
if [[ "$IS_SMOKE_TAG" != "true" ]]; then
|
||||||
publish_mode="always"
|
publish_mode="always"
|
||||||
publish_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
publish_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
||||||
|
publish_args+=("-c.publish.channel=$RELEASE_CHANNEL")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run build --workspace="$DESKTOP_WORKSPACE" -- --publish "$publish_mode" --linux --x64 "${publish_args[@]}"
|
npm run build --workspace="$DESKTOP_WORKSPACE" -- --publish "$publish_mode" --linux --x64 "${publish_args[@]}"
|
||||||
@@ -391,6 +394,7 @@ jobs:
|
|||||||
if [[ "$IS_SMOKE_TAG" != "true" ]]; then
|
if [[ "$IS_SMOKE_TAG" != "true" ]]; then
|
||||||
publish_mode="always"
|
publish_mode="always"
|
||||||
publish_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
publish_args+=("-c.publish.releaseType=$RELEASE_TYPE")
|
||||||
|
publish_args+=("-c.publish.channel=$RELEASE_CHANNEL")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run build --workspace="$DESKTOP_WORKSPACE" -- --publish "$publish_mode" --win --x64 "${publish_args[@]}"
|
npm run build --workspace="$DESKTOP_WORKSPACE" -- --publish "$publish_mode" --win --x64 "${publish_args[@]}"
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.60-beta.1 - 2026-04-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Beta release channel for desktop updates, with a Settings toggle for receiving beta builds before they are promoted to stable.
|
||||||
|
|
||||||
## 0.1.59 - 2026-04-16
|
## 0.1.59 - 2026-04-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Stable tag pushes like `v0.1.0` trigger:
|
|||||||
- `packages/app/.eas/workflows/release-mobile.yml` on Expo servers (iOS + Android build + submit)
|
- `packages/app/.eas/workflows/release-mobile.yml` on Expo servers (iOS + Android build + submit)
|
||||||
- `.github/workflows/android-apk-release.yml` on GitHub Actions (APK asset on GitHub Release)
|
- `.github/workflows/android-apk-release.yml` on GitHub Actions (APK asset on GitHub Release)
|
||||||
|
|
||||||
Release candidate tags like `v0.1.1-rc.1` only trigger the GitHub APK workflow. They publish a GitHub prerelease APK for testing and do not submit to the stores.
|
Beta tags like `v0.1.1-beta.1` only trigger the GitHub APK workflow. They publish a GitHub prerelease APK for testing and do not submit to the stores.
|
||||||
|
|
||||||
### Useful commands
|
### Useful commands
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ All workspaces share one version and release together.
|
|||||||
There are two supported ways to ship from `main`:
|
There are two supported ways to ship from `main`:
|
||||||
|
|
||||||
1. **Direct stable release**: you are ready to ship the current `main` commit to everyone immediately.
|
1. **Direct stable release**: you are ready to ship the current `main` commit to everyone immediately.
|
||||||
2. **Release candidate flow**: you want public test builds first, but you are not ready for the website, npm, or production mobile release flows to move yet.
|
2. **Beta flow**: you want public test builds first, but you are not ready for the website, npm, or production mobile release flows to move yet.
|
||||||
|
|
||||||
## Standard release (patch)
|
## Standard release (patch)
|
||||||
|
|
||||||
@@ -37,31 +37,32 @@ npm run release:publish # Publish to npm
|
|||||||
npm run release:push # Push HEAD + tag (triggers CI workflows)
|
npm run release:push # Push HEAD + tag (triggers CI workflows)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Release candidate flow
|
## Beta flow
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run release:rc:patch # Bump to X.Y.Z-rc.1, push commit + tag
|
npm run release:beta:patch # Bump to X.Y.Z-beta.1, push commit + tag
|
||||||
# ... test desktop and APK prerelease assets from GitHub Releases ...
|
# ... test desktop and APK prerelease assets from GitHub Releases ...
|
||||||
npm run release:rc:next # Optional: cut X.Y.Z-rc.2, rc.3, ...
|
npm run release:beta:next # Optional: cut X.Y.Z-beta.2, beta.3, ...
|
||||||
npm run release:promote # Promote X.Y.Z-rc.N to stable X.Y.Z
|
npm run release:promote # Promote X.Y.Z-beta.N to stable X.Y.Z
|
||||||
```
|
```
|
||||||
|
|
||||||
- RC tags are published GitHub prereleases like `v0.1.41-rc.1`
|
- Beta tags are published GitHub prereleases like `v0.1.41-beta.1`
|
||||||
- RCs publish desktop assets and APKs for testing, but they do not publish npm packages and do not trigger the production web/mobile release flows
|
- Betas publish desktop assets and APKs for testing, but they do not publish npm packages and do not trigger the production web/mobile release flows
|
||||||
- `release:promote` creates a fresh stable tag like `v0.1.41`; the final release never reuses the RC tag
|
- `release:promote` creates a fresh stable tag like `v0.1.41`; the final release never reuses the beta tag
|
||||||
- Desktop assets now come from the Electron package at `packages/desktop`
|
- Desktop assets now come from the Electron package at `packages/desktop`
|
||||||
- **Do NOT create a changelog entry for RCs.** The changelog remains stable-only. RC release notes are generated automatically so the website stays pinned to the latest published stable release.
|
- Beta releases use Electron's `beta` update channel. Users on the stable channel only receive stable releases; users on the beta channel receive beta releases and the final stable release when it is published.
|
||||||
|
- **Do create a changelog entry for betas.** The beta entry is temporary and gets updated in place until promotion.
|
||||||
|
|
||||||
Use the RC path when you need to:
|
Use the beta path when you need to:
|
||||||
|
|
||||||
- test a build manually in a Linux or Windows VM
|
- test a build manually in a Linux or Windows VM
|
||||||
- send a build to a user who is hitting a specific problem
|
- send a build to a user who is hitting a specific problem
|
||||||
- iterate on `rc.1`, `rc.2`, `rc.3`, and so on before deciding to ship broadly
|
- iterate on `beta.1`, `beta.2`, `beta.3`, and so on before deciding to ship broadly
|
||||||
|
|
||||||
## Website behavior
|
## Website behavior
|
||||||
|
|
||||||
- The website download page points to GitHub's latest published **stable** release.
|
- The website download page points to GitHub's latest published **stable** release.
|
||||||
- Published RC prereleases are public on GitHub Releases, but they do **not** become the website download target.
|
- Published beta prereleases are public on GitHub Releases, but they do **not** become the website download target.
|
||||||
- The website only moves when you publish the final stable release tag like `v0.1.41`.
|
- The website only moves when you publish the final stable release tag like `v0.1.41`.
|
||||||
|
|
||||||
## Fixing a failed release build
|
## Fixing a failed release build
|
||||||
@@ -88,13 +89,13 @@ git tag -f desktop-windows-v0.1.28 HEAD && git push origin desktop-windows-v0.1.
|
|||||||
# Android APK
|
# Android APK
|
||||||
git tag -f android-v0.1.28 HEAD && git push origin android-v0.1.28 --force
|
git tag -f android-v0.1.28 HEAD && git push origin android-v0.1.28 --force
|
||||||
|
|
||||||
# RC
|
# Beta
|
||||||
git tag -f v0.1.29-rc.2 HEAD && git push origin v0.1.29-rc.2 --force
|
git tag -f v0.1.29-beta.2 HEAD && git push origin v0.1.29-beta.2 --force
|
||||||
```
|
```
|
||||||
|
|
||||||
This ensures the checkout ref matches the actual code on `main` with the fix included.
|
This ensures the checkout ref matches the actual code on `main` with the fix included.
|
||||||
|
|
||||||
- `vX.Y.Z` or `vX.Y.Z-rc.N` rebuilds the full tagged release
|
- `vX.Y.Z` or `vX.Y.Z-beta.N` rebuilds the full tagged release
|
||||||
- `desktop-vX.Y.Z` rebuilds desktop for all desktop platforms only
|
- `desktop-vX.Y.Z` rebuilds desktop for all desktop platforms only
|
||||||
- `desktop-macos-vX.Y.Z`, `desktop-linux-vX.Y.Z`, and `desktop-windows-vX.Y.Z` rebuild only that desktop platform
|
- `desktop-macos-vX.Y.Z`, `desktop-linux-vX.Y.Z`, and `desktop-windows-vX.Y.Z` rebuild only that desktop platform
|
||||||
- `android-vX.Y.Z` rebuilds the Android APK release only
|
- `android-vX.Y.Z` rebuilds the Android APK release only
|
||||||
@@ -105,24 +106,26 @@ This ensures the checkout ref matches the actual code on `main` with the fix inc
|
|||||||
- `release:prepare` refreshes workspace `node_modules` links to prevent stale types
|
- `release:prepare` refreshes workspace `node_modules` links to prevent stale types
|
||||||
- `npm run dev:desktop` and `npm run build:desktop` target the Electron desktop package in `packages/desktop`
|
- `npm run dev:desktop` and `npm run build:desktop` target the Electron desktop package in `packages/desktop`
|
||||||
- If `release:publish` partially fails, re-run it — npm skips already-published versions
|
- If `release:publish` partially fails, re-run it — npm skips already-published versions
|
||||||
- The website uses GitHub's latest published release API for download links, so published RC prereleases do not replace the stable download target.
|
- The website uses GitHub's latest published release API for download links, so published beta prereleases do not replace the stable download target.
|
||||||
|
|
||||||
## Changelog format
|
## Changelog format
|
||||||
|
|
||||||
Stable release notes depend on the changelog heading format. The heading **must** be strictly followed:
|
Release notes depend on the changelog heading format. The heading **must** be strictly followed:
|
||||||
|
|
||||||
```
|
```
|
||||||
## X.Y.Z - YYYY-MM-DD
|
## X.Y.Z - YYYY-MM-DD
|
||||||
|
## X.Y.Z-beta.N - YYYY-MM-DD
|
||||||
```
|
```
|
||||||
|
|
||||||
No prefix (`v`), no extra text. The parser matches the first `## X.Y.Z` line to extract the version. A malformed heading will break download links on the homepage.
|
No prefix (`v`), no extra text. The parser matches the first `## X.Y.Z` line to extract the version. A malformed heading will break download links on the homepage.
|
||||||
|
|
||||||
## Changelog policy
|
## Changelog policy
|
||||||
|
|
||||||
- `CHANGELOG.md` is for **final stable releases only**.
|
- `CHANGELOG.md` includes stable releases and the current beta line.
|
||||||
- Do not add or edit changelog entries while iterating on RCs.
|
- The first beta inserts a top entry like `## 0.1.60-beta.1 - YYYY-MM-DD`.
|
||||||
- Write the proper changelog entry when you are cutting the final stable release that comes after the RC cycle.
|
- The next beta updates that same top entry in place, for example from `0.1.60-beta.1` to `0.1.60-beta.2`.
|
||||||
- Between stable releases, keep changelog work out of the repo until the final release is ready.
|
- Stable promotion updates that same entry in place, for example from `0.1.60-beta.2` to `0.1.60`.
|
||||||
|
- Do not create duplicate entries for each beta on the same version line.
|
||||||
|
|
||||||
## Changelog ownership
|
## Changelog ownership
|
||||||
|
|
||||||
@@ -178,7 +181,7 @@ Entries within each section (Added, Improved, Fixed) are ordered by user impact:
|
|||||||
|
|
||||||
## Pre-release sanity check
|
## Pre-release sanity check
|
||||||
|
|
||||||
Before cutting any release (RC or stable), run a Codex review of the diff as a last line of defence against shipping bugs.
|
Before cutting any release (beta or stable), run a Codex review of the diff as a last line of defence against shipping bugs.
|
||||||
|
|
||||||
Load the `paseo` skill and launch a **Codex 5.4** agent with a prompt like:
|
Load the `paseo` skill and launch a **Codex 5.4** agent with a prompt like:
|
||||||
|
|
||||||
@@ -196,10 +199,10 @@ The agent's job is a deep sanity check, not a full code review. If it flags anyt
|
|||||||
|
|
||||||
The changelog always covers **stable-to-HEAD**:
|
The changelog always covers **stable-to-HEAD**:
|
||||||
|
|
||||||
- **RC release**: the diff and release notes cover `latest stable tag → HEAD`. RC release notes are auto-generated and not added to `CHANGELOG.md`.
|
- **Beta release**: the diff and release notes cover `latest stable tag -> HEAD`. The current beta changelog entry is updated in place.
|
||||||
- **Stable release**: the diff and changelog entry cover `latest stable tag → HEAD`. Any intermediate RCs are skipped — the changelog captures the full delta from the previous stable release, not just what changed since the last RC.
|
- **Stable release**: the same changelog entry is promoted in place. It still captures the full delta from the previous stable release, not just what changed since the last beta.
|
||||||
|
|
||||||
In other words, RCs are checkpoints along the way; the changelog only records the final jump from one stable version to the next.
|
In other words, betas are checkpoints along the way; the changelog entry remains the single record for the final jump from one stable version to the next.
|
||||||
|
|
||||||
## Completion checklist
|
## Completion checklist
|
||||||
|
|
||||||
|
|||||||
38
package-lock.json
generated
38
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "paseo",
|
"name": "paseo",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "paseo",
|
"name": "paseo",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -37303,16 +37303,16 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@getpaseo/app",
|
"name": "@getpaseo/app",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@floating-ui/react-native": "^0.10.7",
|
"@floating-ui/react-native": "^0.10.7",
|
||||||
"@getpaseo/expo-two-way-audio": "0.1.60-rc.3",
|
"@getpaseo/expo-two-way-audio": "0.1.60-beta.1",
|
||||||
"@getpaseo/highlight": "0.1.60-rc.3",
|
"@getpaseo/highlight": "0.1.60-beta.1",
|
||||||
"@getpaseo/server": "0.1.60-rc.3",
|
"@getpaseo/server": "0.1.60-beta.1",
|
||||||
"@gorhom/bottom-sheet": "^5.2.6",
|
"@gorhom/bottom-sheet": "^5.2.6",
|
||||||
"@gorhom/portal": "^1.0.14",
|
"@gorhom/portal": "^1.0.14",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
@@ -37456,11 +37456,11 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@getpaseo/cli",
|
"name": "@getpaseo/cli",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^1.0.0",
|
"@clack/prompts": "^1.0.0",
|
||||||
"@getpaseo/relay": "0.1.60-rc.3",
|
"@getpaseo/relay": "0.1.60-beta.1",
|
||||||
"@getpaseo/server": "0.1.60-rc.3",
|
"@getpaseo/server": "0.1.60-beta.1",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
@@ -37501,11 +37501,11 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@getpaseo/desktop",
|
"name": "@getpaseo/desktop",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@getpaseo/cli": "0.1.60-rc.3",
|
"@getpaseo/cli": "0.1.60-beta.1",
|
||||||
"@getpaseo/server": "0.1.60-rc.3",
|
"@getpaseo/server": "0.1.60-beta.1",
|
||||||
"electron-log": "^5.4.3",
|
"electron-log": "^5.4.3",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"ws": "^8.14.2"
|
"ws": "^8.14.2"
|
||||||
@@ -37539,7 +37539,7 @@
|
|||||||
},
|
},
|
||||||
"packages/expo-two-way-audio": {
|
"packages/expo-two-way-audio": {
|
||||||
"name": "@getpaseo/expo-two-way-audio",
|
"name": "@getpaseo/expo-two-way-audio",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
@@ -37740,7 +37740,7 @@
|
|||||||
},
|
},
|
||||||
"packages/highlight": {
|
"packages/highlight": {
|
||||||
"name": "@getpaseo/highlight",
|
"name": "@getpaseo/highlight",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.5.0",
|
"@lezer/common": "^1.5.0",
|
||||||
"@lezer/cpp": "^1.1.5",
|
"@lezer/cpp": "^1.1.5",
|
||||||
@@ -37766,7 +37766,7 @@
|
|||||||
},
|
},
|
||||||
"packages/relay": {
|
"packages/relay": {
|
||||||
"name": "@getpaseo/relay",
|
"name": "@getpaseo/relay",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
@@ -37782,14 +37782,14 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@getpaseo/server",
|
"name": "@getpaseo/server",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@agentclientprotocol/sdk": "^0.17.1",
|
"@agentclientprotocol/sdk": "^0.17.1",
|
||||||
"@ai-sdk/openai": "2.0.52",
|
"@ai-sdk/openai": "2.0.52",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"@deepgram/sdk": "^3.4.0",
|
"@deepgram/sdk": "^3.4.0",
|
||||||
"@getpaseo/highlight": "0.1.60-rc.3",
|
"@getpaseo/highlight": "0.1.60-beta.1",
|
||||||
"@getpaseo/relay": "0.1.60-rc.3",
|
"@getpaseo/relay": "0.1.60-beta.1",
|
||||||
"@isaacs/ttlcache": "^2.1.4",
|
"@isaacs/ttlcache": "^2.1.4",
|
||||||
"@mariozechner/pi-coding-agent": "^0.67.68",
|
"@mariozechner/pi-coding-agent": "^0.67.68",
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
@@ -38543,7 +38543,7 @@
|
|||||||
},
|
},
|
||||||
"packages/website": {
|
"packages/website": {
|
||||||
"name": "@getpaseo/website",
|
"name": "@getpaseo/website",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "^1.20.3",
|
"@cloudflare/vite-plugin": "^1.20.3",
|
||||||
"@cloudflare/workers-types": "^4.20260114.0",
|
"@cloudflare/workers-types": "^4.20260114.0",
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paseo",
|
"name": "paseo",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/expo-two-way-audio",
|
"packages/expo-two-way-audio",
|
||||||
@@ -47,19 +47,19 @@
|
|||||||
"version:all:patch": "node scripts/set-release-version.mjs --mode patch",
|
"version:all:patch": "node scripts/set-release-version.mjs --mode patch",
|
||||||
"version:all:minor": "node scripts/set-release-version.mjs --mode minor",
|
"version:all:minor": "node scripts/set-release-version.mjs --mode minor",
|
||||||
"version:all:major": "node scripts/set-release-version.mjs --mode major",
|
"version:all:major": "node scripts/set-release-version.mjs --mode major",
|
||||||
"version:all:rc:patch": "node scripts/set-release-version.mjs --mode rc-patch",
|
"version:all:beta:patch": "node scripts/set-release-version.mjs --mode beta-patch",
|
||||||
"version:all:rc:minor": "node scripts/set-release-version.mjs --mode rc-minor",
|
"version:all:beta:minor": "node scripts/set-release-version.mjs --mode beta-minor",
|
||||||
"version:all:rc:major": "node scripts/set-release-version.mjs --mode rc-major",
|
"version:all:beta:major": "node scripts/set-release-version.mjs --mode beta-major",
|
||||||
"version:all:rc:next": "node scripts/set-release-version.mjs --mode rc-next",
|
"version:all:beta:next": "node scripts/set-release-version.mjs --mode beta-next",
|
||||||
"version:all:promote": "node scripts/set-release-version.mjs --mode promote",
|
"version:all:promote": "node scripts/set-release-version.mjs --mode promote",
|
||||||
"release:check": "npm run release:prepare && npm run typecheck --workspace=@getpaseo/highlight && npm run build --workspace=@getpaseo/highlight && npm run typecheck --workspace=@getpaseo/relay && npm run build --workspace=@getpaseo/relay && npm run typecheck --workspace=@getpaseo/server && npm run build --workspace=@getpaseo/server && npm run typecheck --workspace=@getpaseo/cli && npm run build --workspace=@getpaseo/cli && npm pack --dry-run --workspace=@getpaseo/highlight && npm pack --dry-run --workspace=@getpaseo/relay && npm pack --dry-run --workspace=@getpaseo/server && npm pack --dry-run --workspace=@getpaseo/cli",
|
"release:check": "npm run release:prepare && npm run typecheck --workspace=@getpaseo/highlight && npm run build --workspace=@getpaseo/highlight && npm run typecheck --workspace=@getpaseo/relay && npm run build --workspace=@getpaseo/relay && npm run typecheck --workspace=@getpaseo/server && npm run build --workspace=@getpaseo/server && npm run typecheck --workspace=@getpaseo/cli && npm run build --workspace=@getpaseo/cli && npm pack --dry-run --workspace=@getpaseo/highlight && npm pack --dry-run --workspace=@getpaseo/relay && npm pack --dry-run --workspace=@getpaseo/server && npm pack --dry-run --workspace=@getpaseo/cli",
|
||||||
"release:publish:dry-run": "npm publish --dry-run --workspace=@getpaseo/highlight --access public && npm publish --dry-run --workspace=@getpaseo/relay --access public && npm publish --dry-run --workspace=@getpaseo/server --access public && npm publish --dry-run --workspace=@getpaseo/cli --access public",
|
"release:publish:dry-run": "npm publish --dry-run --workspace=@getpaseo/highlight --access public && npm publish --dry-run --workspace=@getpaseo/relay --access public && npm publish --dry-run --workspace=@getpaseo/server --access public && npm publish --dry-run --workspace=@getpaseo/cli --access public",
|
||||||
"release:publish": "npm publish --workspace=@getpaseo/highlight --access public && npm publish --workspace=@getpaseo/relay --access public && npm publish --workspace=@getpaseo/server --access public && npm publish --workspace=@getpaseo/cli --access public",
|
"release:publish": "npm publish --workspace=@getpaseo/highlight --access public && npm publish --workspace=@getpaseo/relay --access public && npm publish --workspace=@getpaseo/server --access public && npm publish --workspace=@getpaseo/cli --access public",
|
||||||
"release:push": "node scripts/push-current-release-tag.mjs",
|
"release:push": "node scripts/push-current-release-tag.mjs",
|
||||||
"release:rc:patch": "npm run release:check && npm run version:all:rc:patch && npm run release:push",
|
"release:beta:patch": "npm run release:check && npm run version:all:beta:patch && npm run release:push",
|
||||||
"release:rc:minor": "npm run release:check && npm run version:all:rc:minor && npm run release:push",
|
"release:beta:minor": "npm run release:check && npm run version:all:beta:minor && npm run release:push",
|
||||||
"release:rc:major": "npm run release:check && npm run version:all:rc:major && npm run release:push",
|
"release:beta:major": "npm run release:check && npm run version:all:beta:major && npm run release:push",
|
||||||
"release:rc:next": "npm run release:check && npm run version:all:rc:next && npm run release:push",
|
"release:beta:next": "npm run release:check && npm run version:all:beta:next && npm run release:push",
|
||||||
"release:promote": "npm run release:check && npm run version:all:promote && npm run release:publish && npm run release:push",
|
"release:promote": "npm run release:check && npm run version:all:promote && npm run release:publish && npm run release:push",
|
||||||
"release:patch": "npm run release:check && npm run version:all:patch && npm run release:publish && npm run release:push",
|
"release:patch": "npm run release:check && npm run version:all:patch && npm run release:publish && npm run release:push",
|
||||||
"release:minor": "npm run release:check && npm run version:all:minor && npm run release:publish && npm run release:push",
|
"release:minor": "npm run release:check && npm run version:all:minor && npm run release:publish && npm run release:push",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/app",
|
"name": "@getpaseo/app",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "APP_VARIANT=development expo start",
|
"start": "APP_VARIANT=development expo start",
|
||||||
@@ -31,9 +31,9 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@expo/vector-icons": "^15.0.2",
|
"@expo/vector-icons": "^15.0.2",
|
||||||
"@floating-ui/react-native": "^0.10.7",
|
"@floating-ui/react-native": "^0.10.7",
|
||||||
"@getpaseo/expo-two-way-audio": "0.1.60-rc.3",
|
"@getpaseo/expo-two-way-audio": "0.1.60-beta.1",
|
||||||
"@getpaseo/highlight": "0.1.60-rc.3",
|
"@getpaseo/highlight": "0.1.60-beta.1",
|
||||||
"@getpaseo/server": "0.1.60-rc.3",
|
"@getpaseo/server": "0.1.60-beta.1",
|
||||||
"@gorhom/bottom-sheet": "^5.2.6",
|
"@gorhom/bottom-sheet": "^5.2.6",
|
||||||
"@gorhom/portal": "^1.0.14",
|
"@gorhom/portal": "^1.0.14",
|
||||||
"@react-native-async-storage/async-storage": "2.2.0",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ export function SegmentedControl<T extends string>({
|
|||||||
testID,
|
testID,
|
||||||
}: SegmentedControlProps<T>) {
|
}: SegmentedControlProps<T>) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
|
const containerSizeStyle = size === "sm" ? styles.containerSm : styles.containerMd;
|
||||||
const segmentSizeStyle = size === "sm" ? styles.segmentSm : styles.segmentMd;
|
const segmentSizeStyle = size === "sm" ? styles.segmentSm : styles.segmentMd;
|
||||||
const labelSizeStyle = size === "sm" ? styles.labelSm : styles.labelMd;
|
const labelSizeStyle = size === "sm" ? styles.labelSm : styles.labelMd;
|
||||||
const iconSize = size === "sm" ? theme.iconSize.sm : theme.iconSize.md;
|
const iconSize = size === "sm" ? theme.iconSize.sm : theme.iconSize.md;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.container, style]} testID={testID}>
|
<View style={[styles.container, containerSizeStyle, style]} testID={testID}>
|
||||||
{options.map((option) => {
|
{options.map((option) => {
|
||||||
const isSelected = option.value === value;
|
const isSelected = option.value === value;
|
||||||
const iconColor = isSelected ? theme.colors.foreground : theme.colors.foregroundMuted;
|
const iconColor = isSelected ? theme.colors.foreground : theme.colors.foregroundMuted;
|
||||||
@@ -89,39 +90,46 @@ export function SegmentedControl<T extends string>({
|
|||||||
const styles = StyleSheet.create((theme) => ({
|
const styles = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "stretch",
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
gap: 4,
|
backgroundColor: theme.colors.surface2,
|
||||||
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
},
|
||||||
|
containerSm: {
|
||||||
|
padding: 2,
|
||||||
|
},
|
||||||
|
containerMd: {
|
||||||
|
padding: 3,
|
||||||
},
|
},
|
||||||
segment: {
|
segment: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
borderRadius: theme.borderRadius.full,
|
borderRadius: theme.borderRadius.lg,
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: "transparent",
|
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
segmentSm: {
|
segmentSm: {
|
||||||
paddingVertical: theme.spacing[1],
|
paddingVertical: theme.spacing[2],
|
||||||
paddingHorizontal: theme.spacing[2],
|
paddingHorizontal: theme.spacing[4],
|
||||||
},
|
},
|
||||||
segmentMd: {
|
segmentMd: {
|
||||||
paddingVertical: theme.spacing[2],
|
paddingVertical: theme.spacing[3],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[6],
|
||||||
},
|
},
|
||||||
segmentSelected: {
|
segmentSelected: {
|
||||||
backgroundColor: theme.colors.surface3,
|
backgroundColor: theme.colors.surface0,
|
||||||
borderColor: theme.colors.border,
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 2,
|
||||||
|
elevation: 1,
|
||||||
},
|
},
|
||||||
segmentHover: {
|
segmentHover: {
|
||||||
backgroundColor: theme.colors.surface3,
|
backgroundColor: theme.colors.surface1,
|
||||||
borderColor: theme.colors.borderAccent,
|
|
||||||
},
|
},
|
||||||
segmentPressed: {
|
segmentPressed: {
|
||||||
backgroundColor: theme.colors.surface3,
|
backgroundColor: theme.colors.surface1,
|
||||||
borderColor: theme.colors.borderAccent,
|
|
||||||
},
|
},
|
||||||
segmentDisabled: {
|
segmentDisabled: {
|
||||||
opacity: theme.opacity[50],
|
opacity: theme.opacity[50],
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export interface DesktopAppUpdateInstallResult {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DesktopReleaseChannel = "stable" | "beta";
|
||||||
|
|
||||||
export interface LocalDaemonUpdateResult {
|
export interface LocalDaemonUpdateResult {
|
||||||
exitCode: number;
|
exitCode: number;
|
||||||
stdout: string;
|
stdout: string;
|
||||||
@@ -69,8 +71,12 @@ export async function getLocalDaemonVersion(): Promise<LocalDaemonVersionResult>
|
|||||||
return parseLocalDaemonVersionResult(result);
|
return parseLocalDaemonVersionResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDesktopAppUpdate(): Promise<DesktopAppUpdateCheckResult> {
|
export async function checkDesktopAppUpdate({
|
||||||
const result = await invokeDesktopCommand<unknown>("check_app_update");
|
releaseChannel,
|
||||||
|
}: {
|
||||||
|
releaseChannel: DesktopReleaseChannel;
|
||||||
|
}): Promise<DesktopAppUpdateCheckResult> {
|
||||||
|
const result = await invokeDesktopCommand<unknown>("check_app_update", { releaseChannel });
|
||||||
if (!isRecord(result)) {
|
if (!isRecord(result)) {
|
||||||
throw new Error("Unexpected response while checking desktop updates.");
|
throw new Error("Unexpected response while checking desktop updates.");
|
||||||
}
|
}
|
||||||
@@ -85,8 +91,12 @@ export async function checkDesktopAppUpdate(): Promise<DesktopAppUpdateCheckResu
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installDesktopAppUpdate(): Promise<DesktopAppUpdateInstallResult> {
|
export async function installDesktopAppUpdate({
|
||||||
const result = await invokeDesktopCommand<unknown>("install_app_update");
|
releaseChannel,
|
||||||
|
}: {
|
||||||
|
releaseChannel: DesktopReleaseChannel;
|
||||||
|
}): Promise<DesktopAppUpdateInstallResult> {
|
||||||
|
const result = await invokeDesktopCommand<unknown>("install_app_update", { releaseChannel });
|
||||||
if (!isRecord(result)) {
|
if (!isRecord(result)) {
|
||||||
throw new Error("Unexpected response while installing desktop update.");
|
throw new Error("Unexpected response while installing desktop update.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
type DesktopAppUpdateCheckResult,
|
type DesktopAppUpdateCheckResult,
|
||||||
type DesktopAppUpdateInstallResult,
|
type DesktopAppUpdateInstallResult,
|
||||||
} from "@/desktop/updates/desktop-updates";
|
} from "@/desktop/updates/desktop-updates";
|
||||||
|
import { useAppSettings } from "@/hooks/use-settings";
|
||||||
|
|
||||||
export type DesktopAppUpdateStatus =
|
export type DesktopAppUpdateStatus =
|
||||||
| "idle"
|
| "idle"
|
||||||
@@ -83,6 +84,8 @@ function formatStatusText(input: {
|
|||||||
|
|
||||||
export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
||||||
const isDesktopApp = shouldShowDesktopUpdateSection();
|
const isDesktopApp = shouldShowDesktopUpdateSection();
|
||||||
|
const { settings } = useAppSettings();
|
||||||
|
const releaseChannel = settings.releaseChannel;
|
||||||
const requestVersionRef = useRef(0);
|
const requestVersionRef = useRef(0);
|
||||||
const [status, setStatus] = useState<DesktopAppUpdateStatus>("idle");
|
const [status, setStatus] = useState<DesktopAppUpdateStatus>("idle");
|
||||||
const [availableUpdate, setAvailableUpdate] = useState<DesktopAppUpdateCheckResult | null>(null);
|
const [availableUpdate, setAvailableUpdate] = useState<DesktopAppUpdateCheckResult | null>(null);
|
||||||
@@ -105,7 +108,7 @@ export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
|||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await checkDesktopAppUpdate();
|
const result = await checkDesktopAppUpdate({ releaseChannel });
|
||||||
if (requestVersion !== requestVersionRef.current) {
|
if (requestVersion !== requestVersionRef.current) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -140,9 +143,17 @@ export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isDesktopApp],
|
[isDesktopApp, releaseChannel],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDesktopApp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkForUpdates({ silent: true });
|
||||||
|
}, [checkForUpdates, isDesktopApp]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isDesktopApp || status !== "pending") {
|
if (!isDesktopApp || status !== "pending") {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -166,7 +177,7 @@ export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
|||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await installDesktopAppUpdate();
|
const result = await installDesktopAppUpdate({ releaseChannel });
|
||||||
setLastCheckedAt(Date.now());
|
setLastCheckedAt(Date.now());
|
||||||
|
|
||||||
if (result.installed) {
|
if (result.installed) {
|
||||||
@@ -186,7 +197,7 @@ export function useDesktopAppUpdater(): UseDesktopAppUpdaterReturn {
|
|||||||
setErrorMessage(message);
|
setErrorMessage(message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [isDesktopApp]);
|
}, [isDesktopApp, releaseChannel]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDesktopApp,
|
isDesktopApp,
|
||||||
|
|||||||
@@ -40,6 +40,16 @@ describe("use-settings", () => {
|
|||||||
expect(result.theme).toBe("auto");
|
expect(result.theme).toBe("auto");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults release channel to stable when storage is empty", async () => {
|
||||||
|
asyncStorageMock.getItem.mockResolvedValue(null);
|
||||||
|
asyncStorageMock.setItem.mockResolvedValue();
|
||||||
|
|
||||||
|
const mod = await import("./use-settings");
|
||||||
|
const result = await mod.loadSettingsFromStorage();
|
||||||
|
|
||||||
|
expect(result.releaseChannel).toBe("stable");
|
||||||
|
});
|
||||||
|
|
||||||
it("loads persisted built-in daemon management state", async () => {
|
it("loads persisted built-in daemon management state", async () => {
|
||||||
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
||||||
if (key === "@paseo:app-settings") {
|
if (key === "@paseo:app-settings") {
|
||||||
@@ -58,7 +68,24 @@ describe("use-settings", () => {
|
|||||||
theme: "light",
|
theme: "light",
|
||||||
manageBuiltInDaemon: false,
|
manageBuiltInDaemon: false,
|
||||||
sendBehavior: "interrupt",
|
sendBehavior: "interrupt",
|
||||||
|
releaseChannel: "stable",
|
||||||
});
|
});
|
||||||
expect(asyncStorageMock.setItem).not.toHaveBeenCalled();
|
expect(asyncStorageMock.setItem).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads persisted beta release channel", async () => {
|
||||||
|
asyncStorageMock.getItem.mockImplementation(async (key: string) => {
|
||||||
|
if (key === "@paseo:app-settings") {
|
||||||
|
return JSON.stringify({
|
||||||
|
releaseChannel: "beta",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mod = await import("./use-settings");
|
||||||
|
const result = await mod.loadSettingsFromStorage();
|
||||||
|
|
||||||
|
expect(result.releaseChannel).toBe("beta");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,19 +9,23 @@ const APP_SETTINGS_QUERY_KEY = ["app-settings"];
|
|||||||
import { THEME_TO_UNISTYLES, type ThemeName } from "@/styles/theme";
|
import { THEME_TO_UNISTYLES, type ThemeName } from "@/styles/theme";
|
||||||
|
|
||||||
export type SendBehavior = "interrupt" | "queue";
|
export type SendBehavior = "interrupt" | "queue";
|
||||||
|
export type ReleaseChannel = "stable" | "beta";
|
||||||
|
|
||||||
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
const VALID_THEMES = new Set<string>([...Object.keys(THEME_TO_UNISTYLES), "auto"]);
|
||||||
|
const VALID_RELEASE_CHANNELS = new Set<string>(["stable", "beta"]);
|
||||||
|
|
||||||
export interface AppSettings {
|
export interface AppSettings {
|
||||||
theme: ThemeName | "auto";
|
theme: ThemeName | "auto";
|
||||||
manageBuiltInDaemon: boolean;
|
manageBuiltInDaemon: boolean;
|
||||||
sendBehavior: SendBehavior;
|
sendBehavior: SendBehavior;
|
||||||
|
releaseChannel: ReleaseChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_APP_SETTINGS: AppSettings = {
|
export const DEFAULT_APP_SETTINGS: AppSettings = {
|
||||||
theme: "auto",
|
theme: "auto",
|
||||||
manageBuiltInDaemon: true,
|
manageBuiltInDaemon: true,
|
||||||
sendBehavior: "interrupt",
|
sendBehavior: "interrupt",
|
||||||
|
releaseChannel: "stable",
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UseAppSettingsReturn {
|
export interface UseAppSettingsReturn {
|
||||||
@@ -85,6 +89,9 @@ export async function loadSettingsFromStorage(): Promise<AppSettings> {
|
|||||||
if (parsed.theme && !VALID_THEMES.has(parsed.theme)) {
|
if (parsed.theme && !VALID_THEMES.has(parsed.theme)) {
|
||||||
parsed.theme = DEFAULT_APP_SETTINGS.theme;
|
parsed.theme = DEFAULT_APP_SETTINGS.theme;
|
||||||
}
|
}
|
||||||
|
if (parsed.releaseChannel && !VALID_RELEASE_CHANNELS.has(parsed.releaseChannel)) {
|
||||||
|
parsed.releaseChannel = DEFAULT_APP_SETTINGS.releaseChannel;
|
||||||
|
}
|
||||||
return { ...DEFAULT_APP_SETTINGS, ...parsed };
|
return { ...DEFAULT_APP_SETTINGS, ...parsed };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +122,9 @@ function pickAppSettingsFromLegacy(legacy: Record<string, unknown>): Partial<App
|
|||||||
if (typeof legacy.manageBuiltInDaemon === "boolean") {
|
if (typeof legacy.manageBuiltInDaemon === "boolean") {
|
||||||
result.manageBuiltInDaemon = legacy.manageBuiltInDaemon;
|
result.manageBuiltInDaemon = legacy.manageBuiltInDaemon;
|
||||||
}
|
}
|
||||||
|
if (legacy.releaseChannel === "stable" || legacy.releaseChannel === "beta") {
|
||||||
|
result.releaseChannel = legacy.releaseChannel;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ function AboutSection({ appVersionText, isDesktopApp }: AboutSectionProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DesktopAppUpdateRow() {
|
function DesktopAppUpdateRow() {
|
||||||
|
const { settings, updateSettings } = useAppSettings();
|
||||||
const {
|
const {
|
||||||
isDesktopApp,
|
isDesktopApp,
|
||||||
statusText,
|
statusText,
|
||||||
@@ -309,6 +310,13 @@ function DesktopAppUpdateRow() {
|
|||||||
void checkForUpdates();
|
void checkForUpdates();
|
||||||
}, [checkForUpdates, isDesktopApp]);
|
}, [checkForUpdates, isDesktopApp]);
|
||||||
|
|
||||||
|
const handleReleaseChannelChange = useCallback(
|
||||||
|
(releaseChannel: AppSettings["releaseChannel"]) => {
|
||||||
|
void updateSettings({ releaseChannel });
|
||||||
|
},
|
||||||
|
[updateSettings],
|
||||||
|
);
|
||||||
|
|
||||||
const handleInstallUpdate = useCallback(() => {
|
const handleInstallUpdate = useCallback(() => {
|
||||||
if (!isDesktopApp) {
|
if (!isDesktopApp) {
|
||||||
return;
|
return;
|
||||||
@@ -337,40 +345,59 @@ function DesktopAppUpdateRow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
<>
|
||||||
<View style={settingsStyles.rowContent}>
|
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||||
<Text style={settingsStyles.rowTitle}>App updates</Text>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowHint}>{statusText}</Text>
|
<Text style={settingsStyles.rowTitle}>Release channel</Text>
|
||||||
{availableUpdate?.latestVersion ? (
|
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>
|
||||||
Ready to install: {formatVersionWithPrefix(availableUpdate.latestVersion)}
|
Switch to Beta to get updates sooner and help shape them.
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
</View>
|
||||||
{errorMessage ? <Text style={styles.aboutErrorText}>{errorMessage}</Text> : null}
|
<SegmentedControl
|
||||||
</View>
|
|
||||||
<View style={styles.aboutUpdateActions}>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
onPress={handleCheckForUpdates}
|
value={settings.releaseChannel}
|
||||||
disabled={isChecking || isInstalling}
|
onValueChange={handleReleaseChannelChange}
|
||||||
>
|
options={[
|
||||||
{isChecking ? "Checking..." : "Check"}
|
{ value: "stable", label: "Stable" },
|
||||||
</Button>
|
{ value: "beta", label: "Beta" },
|
||||||
<Button
|
]}
|
||||||
variant="default"
|
/>
|
||||||
size="sm"
|
|
||||||
onPress={handleInstallUpdate}
|
|
||||||
disabled={isChecking || isInstalling || !availableUpdate}
|
|
||||||
>
|
|
||||||
{isInstalling
|
|
||||||
? "Installing..."
|
|
||||||
: availableUpdate?.latestVersion
|
|
||||||
? `Update to ${formatVersionWithPrefix(availableUpdate.latestVersion)}`
|
|
||||||
: "Update"}
|
|
||||||
</Button>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||||
|
<View style={settingsStyles.rowContent}>
|
||||||
|
<Text style={settingsStyles.rowTitle}>App updates</Text>
|
||||||
|
<Text style={settingsStyles.rowHint}>{statusText}</Text>
|
||||||
|
{availableUpdate?.latestVersion ? (
|
||||||
|
<Text style={settingsStyles.rowHint}>
|
||||||
|
Ready to install: {formatVersionWithPrefix(availableUpdate.latestVersion)}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
{errorMessage ? <Text style={styles.aboutErrorText}>{errorMessage}</Text> : null}
|
||||||
|
</View>
|
||||||
|
<View style={styles.aboutUpdateActions}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onPress={handleCheckForUpdates}
|
||||||
|
disabled={isChecking || isInstalling}
|
||||||
|
>
|
||||||
|
{isChecking ? "Checking..." : "Check"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onPress={handleInstallUpdate}
|
||||||
|
disabled={isChecking || isInstalling || !availableUpdate}
|
||||||
|
>
|
||||||
|
{isInstalling
|
||||||
|
? "Installing..."
|
||||||
|
: availableUpdate?.latestVersion
|
||||||
|
? `Update to ${formatVersionWithPrefix(availableUpdate.latestVersion)}`
|
||||||
|
: "Update"}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,9 +885,10 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
|||||||
layout="desktop"
|
layout="desktop"
|
||||||
/>
|
/>
|
||||||
<View style={desktopStyles.contentPane}>
|
<View style={desktopStyles.contentPane}>
|
||||||
{detailHeader ? (
|
<ScreenHeader
|
||||||
<ScreenHeader
|
borderless={!detailHeader}
|
||||||
left={
|
left={
|
||||||
|
detailHeader ? (
|
||||||
<>
|
<>
|
||||||
<HeaderIconBadge>
|
<HeaderIconBadge>
|
||||||
<detailHeader.Icon
|
<detailHeader.Icon
|
||||||
@@ -873,10 +901,10 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
|
|||||||
</ScreenTitle>
|
</ScreenTitle>
|
||||||
{detailHeader.titleAccessory}
|
{detailHeader.titleAccessory}
|
||||||
</>
|
</>
|
||||||
}
|
) : null
|
||||||
leftStyle={desktopStyles.detailLeft}
|
}
|
||||||
/>
|
leftStyle={desktopStyles.detailLeft}
|
||||||
) : null}
|
/>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={styles.scrollView}
|
style={styles.scrollView}
|
||||||
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
||||||
|
|||||||
@@ -19,58 +19,66 @@ type RawWindowControlsPadding = {
|
|||||||
|
|
||||||
type WindowControlsPaddingRole = "sidebar" | "header" | "tabRow" | "explorerSidebar";
|
type WindowControlsPaddingRole = "sidebar" | "header" | "tabRow" | "explorerSidebar";
|
||||||
|
|
||||||
|
// Module-level cache so hook remounts (e.g., on navigation) don't briefly
|
||||||
|
// fall back to the default `false` while the async fullscreen check resolves.
|
||||||
|
// Without this, in fullscreen the sidebar flashes with traffic-light padding
|
||||||
|
// on first frame and then snaps to 0 once the async read completes.
|
||||||
|
let cachedIsFullscreen = false;
|
||||||
|
const fullscreenSubscribers = new Set<(value: boolean) => void>();
|
||||||
|
let fullscreenSubscriptionStarted = false;
|
||||||
|
|
||||||
|
function setCachedFullscreen(value: boolean) {
|
||||||
|
if (cachedIsFullscreen === value) return;
|
||||||
|
cachedIsFullscreen = value;
|
||||||
|
for (const sub of fullscreenSubscribers) {
|
||||||
|
sub(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFullscreenSubscription() {
|
||||||
|
if (fullscreenSubscriptionStarted) return;
|
||||||
|
if (isNative || !getIsElectronRuntime()) return;
|
||||||
|
fullscreenSubscriptionStarted = true;
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
const win = getDesktopWindow();
|
||||||
|
if (!win) return;
|
||||||
|
|
||||||
|
if (typeof win.isFullscreen === "function") {
|
||||||
|
try {
|
||||||
|
setCachedFullscreen(await win.isFullscreen());
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[DesktopWindow] Failed to read fullscreen state", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof win.onResized !== "function") return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await win.onResized(async () => {
|
||||||
|
if (typeof win.isFullscreen !== "function") return;
|
||||||
|
try {
|
||||||
|
setCachedFullscreen(await win.isFullscreen());
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[DesktopWindow] Failed to read fullscreen state", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[DesktopWindow] Failed to subscribe to resize", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
function useRawWindowControlsPadding(): RawWindowControlsPadding {
|
function useRawWindowControlsPadding(): RawWindowControlsPadding {
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(cachedIsFullscreen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNative || !getIsElectronRuntime()) return;
|
startFullscreenSubscription();
|
||||||
|
// Sync to any value that resolved between render and effect.
|
||||||
let disposed = false;
|
setIsFullscreen(cachedIsFullscreen);
|
||||||
let cleanup: (() => void) | undefined;
|
fullscreenSubscribers.add(setIsFullscreen);
|
||||||
let didCleanup = false;
|
|
||||||
|
|
||||||
function runCleanup() {
|
|
||||||
if (!cleanup || didCleanup) return;
|
|
||||||
didCleanup = true;
|
|
||||||
try {
|
|
||||||
void Promise.resolve(cleanup()).catch((error) => {
|
|
||||||
console.warn("[DesktopWindow] Failed to remove resize listener", error);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("[DesktopWindow] Failed to remove resize listener", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setup() {
|
|
||||||
const win = getDesktopWindow();
|
|
||||||
if (!win) return;
|
|
||||||
|
|
||||||
const fullscreen = typeof win.isFullscreen === "function" ? await win.isFullscreen() : false;
|
|
||||||
if (disposed) return;
|
|
||||||
setIsFullscreen(fullscreen);
|
|
||||||
|
|
||||||
if (typeof win.onResized !== "function") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unlisten = await win.onResized(async () => {
|
|
||||||
if (disposed) return;
|
|
||||||
const fs = typeof win.isFullscreen === "function" ? await win.isFullscreen() : false;
|
|
||||||
if (disposed) return;
|
|
||||||
setIsFullscreen(fs);
|
|
||||||
});
|
|
||||||
|
|
||||||
cleanup = unlisten;
|
|
||||||
if (disposed) {
|
|
||||||
runCleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup();
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposed = true;
|
fullscreenSubscribers.delete(setIsFullscreen);
|
||||||
runCleanup();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/cli",
|
"name": "@getpaseo/cli",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"description": "Paseo CLI - control your AI coding agents from the command line",
|
"description": "Paseo CLI - control your AI coding agents from the command line",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^1.0.0",
|
"@clack/prompts": "^1.0.0",
|
||||||
"@getpaseo/relay": "0.1.60-rc.3",
|
"@getpaseo/relay": "0.1.60-beta.1",
|
||||||
"@getpaseo/server": "0.1.60-rc.3",
|
"@getpaseo/server": "0.1.60-beta.1",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/desktop",
|
"name": "@getpaseo/desktop",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Paseo desktop app (Electron wrapper)",
|
"description": "Paseo desktop app (Electron wrapper)",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@getpaseo/cli": "0.1.60-rc.3",
|
"@getpaseo/cli": "0.1.60-beta.1",
|
||||||
"@getpaseo/server": "0.1.60-rc.3",
|
"@getpaseo/server": "0.1.60-beta.1",
|
||||||
"electron-log": "^5.4.3",
|
"electron-log": "^5.4.3",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
"ws": "^8.14.2"
|
"ws": "^8.14.2"
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import {
|
|||||||
readManagedFileBase64,
|
readManagedFileBase64,
|
||||||
writeAttachmentBase64,
|
writeAttachmentBase64,
|
||||||
} from "../features/attachments.js";
|
} from "../features/attachments.js";
|
||||||
import { checkForAppUpdate, downloadAndInstallUpdate } from "../features/auto-updater.js";
|
import {
|
||||||
|
checkForAppUpdate,
|
||||||
|
downloadAndInstallUpdate,
|
||||||
|
type AppReleaseChannel,
|
||||||
|
} from "../features/auto-updater.js";
|
||||||
import {
|
import {
|
||||||
installCli,
|
installCli,
|
||||||
getCliInstallStatus,
|
getCliInstallStatus,
|
||||||
@@ -65,6 +69,10 @@ type DesktopPairingOffer = {
|
|||||||
|
|
||||||
type DesktopCommandHandler = (args?: Record<string, unknown>) => Promise<unknown> | unknown;
|
type DesktopCommandHandler = (args?: Record<string, unknown>) => Promise<unknown> | unknown;
|
||||||
|
|
||||||
|
function parseReleaseChannel(args: Record<string, unknown> | undefined): AppReleaseChannel {
|
||||||
|
return args?.releaseChannel === "beta" ? "beta" : "stable";
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Utilities
|
// Utilities
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -483,15 +491,18 @@ export function createDaemonCommandHandlers(): Record<string, DesktopCommandHand
|
|||||||
: "";
|
: "";
|
||||||
if (sessionId) closeLocalTransportSession(sessionId);
|
if (sessionId) closeLocalTransportSession(sessionId);
|
||||||
},
|
},
|
||||||
check_app_update: async () => {
|
check_app_update: async (args) => {
|
||||||
const currentVersion = await resolveCurrentUpdateVersion();
|
const currentVersion = await resolveCurrentUpdateVersion();
|
||||||
return checkForAppUpdate(currentVersion);
|
return checkForAppUpdate({ currentVersion, releaseChannel: parseReleaseChannel(args) });
|
||||||
},
|
},
|
||||||
install_app_update: async () => {
|
install_app_update: async (args) => {
|
||||||
const currentVersion = await resolveCurrentUpdateVersion();
|
const currentVersion = await resolveCurrentUpdateVersion();
|
||||||
return downloadAndInstallUpdate(currentVersion, async () => {
|
return downloadAndInstallUpdate(
|
||||||
await stopDaemon();
|
{ currentVersion, releaseChannel: parseReleaseChannel(args) },
|
||||||
});
|
async () => {
|
||||||
|
await stopDaemon();
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
get_local_daemon_version: () => getLocalDaemonVersion(),
|
get_local_daemon_version: () => getLocalDaemonVersion(),
|
||||||
install_cli: () => installCli(),
|
install_cli: () => installCli(),
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export type AppUpdateInstallResult = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AppReleaseChannel = "stable" | "beta";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// State
|
// State
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -28,18 +30,29 @@ let cachedUpdateInfo: UpdateInfo | null = null;
|
|||||||
let downloadedUpdateVersion: string | null = null;
|
let downloadedUpdateVersion: string | null = null;
|
||||||
let downloading = false;
|
let downloading = false;
|
||||||
let autoUpdaterConfigured = false;
|
let autoUpdaterConfigured = false;
|
||||||
|
let configuredReleaseChannel: AppReleaseChannel | null = null;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Configuration
|
// Configuration
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function configureAutoUpdater(): void {
|
function configureAutoUpdater(releaseChannel: AppReleaseChannel): void {
|
||||||
// Download updates in the background and only prompt once they are ready to install.
|
// Download updates in the background and only prompt once they are ready to install.
|
||||||
autoUpdater.autoDownload = true;
|
autoUpdater.autoDownload = true;
|
||||||
autoUpdater.autoInstallOnAppQuit = true;
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
|
||||||
// Suppress built-in dialogs; the renderer handles UI.
|
// Suppress built-in dialogs; the renderer handles UI.
|
||||||
autoUpdater.autoRunAppAfterInstall = true;
|
autoUpdater.autoRunAppAfterInstall = true;
|
||||||
|
autoUpdater.allowPrerelease = releaseChannel === "beta";
|
||||||
|
autoUpdater.channel = releaseChannel === "beta" ? "beta" : "latest";
|
||||||
|
autoUpdater.allowDowngrade = false;
|
||||||
|
|
||||||
|
if (configuredReleaseChannel !== releaseChannel) {
|
||||||
|
cachedUpdateInfo = null;
|
||||||
|
downloadedUpdateVersion = null;
|
||||||
|
downloading = false;
|
||||||
|
configuredReleaseChannel = releaseChannel;
|
||||||
|
}
|
||||||
|
|
||||||
if (autoUpdaterConfigured) {
|
if (autoUpdaterConfigured) {
|
||||||
return;
|
return;
|
||||||
@@ -109,7 +122,13 @@ function scheduleQuitAndInstall(onBeforeQuit?: () => Promise<void>): void {
|
|||||||
// Public API
|
// Public API
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export async function checkForAppUpdate(currentVersion: string): Promise<AppUpdateCheckResult> {
|
export async function checkForAppUpdate({
|
||||||
|
currentVersion,
|
||||||
|
releaseChannel,
|
||||||
|
}: {
|
||||||
|
currentVersion: string;
|
||||||
|
releaseChannel: AppReleaseChannel;
|
||||||
|
}): Promise<AppUpdateCheckResult> {
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
return buildCheckResult({
|
return buildCheckResult({
|
||||||
currentVersion,
|
currentVersion,
|
||||||
@@ -118,7 +137,7 @@ export async function checkForAppUpdate(currentVersion: string): Promise<AppUpda
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
configureAutoUpdater();
|
configureAutoUpdater(releaseChannel);
|
||||||
|
|
||||||
const cachedVersion = cachedUpdateInfo?.version ?? null;
|
const cachedVersion = cachedUpdateInfo?.version ?? null;
|
||||||
if (cachedVersion && cachedVersion !== currentVersion) {
|
if (cachedVersion && cachedVersion !== currentVersion) {
|
||||||
@@ -176,7 +195,13 @@ export async function checkForAppUpdate(currentVersion: string): Promise<AppUpda
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadAndInstallUpdate(
|
export async function downloadAndInstallUpdate(
|
||||||
currentVersion: string,
|
{
|
||||||
|
currentVersion,
|
||||||
|
releaseChannel,
|
||||||
|
}: {
|
||||||
|
currentVersion: string;
|
||||||
|
releaseChannel: AppReleaseChannel;
|
||||||
|
},
|
||||||
onBeforeQuit?: () => Promise<void>,
|
onBeforeQuit?: () => Promise<void>,
|
||||||
): Promise<AppUpdateInstallResult> {
|
): Promise<AppUpdateInstallResult> {
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
@@ -195,7 +220,7 @@ export async function downloadAndInstallUpdate(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
configureAutoUpdater();
|
configureAutoUpdater(releaseChannel);
|
||||||
|
|
||||||
const readyVersion = cachedUpdateInfo.version;
|
const readyVersion = cachedUpdateInfo.version;
|
||||||
if (isReadyToInstallVersion(readyVersion)) {
|
if (isReadyToInstallVersion(readyVersion)) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/expo-two-way-audio",
|
"name": "@getpaseo/expo-two-way-audio",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"description": "Native module for two way audio streaming",
|
"description": "Native module for two way audio streaming",
|
||||||
"main": "build/index.js",
|
"main": "build/index.js",
|
||||||
"types": "build/index.d.ts",
|
"types": "build/index.d.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/highlight",
|
"name": "@getpaseo/highlight",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/relay",
|
"name": "@getpaseo/relay",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"description": "Paseo relay for bridging daemon and client connections",
|
"description": "Paseo relay for bridging daemon and client connections",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/server",
|
"name": "@getpaseo/server",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"description": "Paseo backend server",
|
"description": "Paseo backend server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
"@ai-sdk/openai": "2.0.52",
|
"@ai-sdk/openai": "2.0.52",
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||||
"@deepgram/sdk": "^3.4.0",
|
"@deepgram/sdk": "^3.4.0",
|
||||||
"@getpaseo/highlight": "0.1.60-rc.3",
|
"@getpaseo/highlight": "0.1.60-beta.1",
|
||||||
"@getpaseo/relay": "0.1.60-rc.3",
|
"@getpaseo/relay": "0.1.60-beta.1",
|
||||||
"@isaacs/ttlcache": "^2.1.4",
|
"@isaacs/ttlcache": "^2.1.4",
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"@mariozechner/pi-coding-agent": "^0.67.68",
|
"@mariozechner/pi-coding-agent": "^0.67.68",
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50";
|
|||||||
|
|
||||||
function isAppVersionAtLeast(appVersion: string | null, minVersion: string): boolean {
|
function isAppVersionAtLeast(appVersion: string | null, minVersion: string): boolean {
|
||||||
if (!appVersion) return false;
|
if (!appVersion) return false;
|
||||||
// Strip RC/prerelease suffix: "0.1.45-rc.4" → "0.1.45"
|
// Strip prerelease suffix: "0.1.45-beta.4" -> "0.1.45"
|
||||||
const base = appVersion.replace(/-.*$/, "");
|
const base = appVersion.replace(/-.*$/, "");
|
||||||
const parts = base.split(".").map(Number);
|
const parts = base.split(".").map(Number);
|
||||||
const minParts = minVersion.split(".").map(Number);
|
const minParts = minVersion.split(".").map(Number);
|
||||||
|
|||||||
@@ -1089,7 +1089,7 @@ describe("slugify", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("keeps very long names within the hostname label length limit", () => {
|
it("keeps very long names within the hostname label length limit", () => {
|
||||||
const result = slugify("Release Candidate ".repeat(12));
|
const result = slugify("Beta Build ".repeat(12));
|
||||||
|
|
||||||
expect(result.length).toBeLessThanOrEqual(63);
|
expect(result.length).toBeLessThanOrEqual(63);
|
||||||
expectValidHostnameLabel(result);
|
expectValidHostnameLabel(result);
|
||||||
@@ -1107,7 +1107,7 @@ describe("slugify", () => {
|
|||||||
"feature/cool stuff",
|
"feature/cool stuff",
|
||||||
" Café Launch ",
|
" Café Launch ",
|
||||||
"__bar__",
|
"__bar__",
|
||||||
"Release Candidate ".repeat(12),
|
"Beta Build ".repeat(12),
|
||||||
"release***candidate",
|
"release***candidate",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getpaseo/website",
|
"name": "@getpaseo/website",
|
||||||
"version": "0.1.60-rc.3",
|
"version": "0.1.60-beta.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ const entries = [
|
|||||||
["RELEASE_BASE_VERSION", info.baseVersion],
|
["RELEASE_BASE_VERSION", info.baseVersion],
|
||||||
["RELEASE_PRERELEASE", info.prerelease ?? ""],
|
["RELEASE_PRERELEASE", info.prerelease ?? ""],
|
||||||
["IS_PRERELEASE", info.isPrerelease ? "true" : "false"],
|
["IS_PRERELEASE", info.isPrerelease ? "true" : "false"],
|
||||||
["IS_RELEASE_CANDIDATE", info.isReleaseCandidate ? "true" : "false"],
|
["IS_BETA", info.isBeta ? "true" : "false"],
|
||||||
["RELEASE_TYPE", info.releaseType],
|
["RELEASE_TYPE", info.releaseType],
|
||||||
|
["RELEASE_CHANNEL", info.releaseChannel],
|
||||||
["DESKTOP_VERSION", info.version],
|
["DESKTOP_VERSION", info.version],
|
||||||
["IS_SMOKE_TAG", info.isSmokeTag ? "true" : "false"],
|
["IS_SMOKE_TAG", info.isSmokeTag ? "true" : "false"],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function parseReleaseVersion(version) {
|
|||||||
const match = trimmed.match(versionPattern);
|
const match = trimmed.match(versionPattern);
|
||||||
if (!match?.groups) {
|
if (!match?.groups) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported release version "${version}". Expected semver like 0.1.41 or 0.1.41-rc.1.`,
|
`Unsupported release version "${version}". Expected semver like 0.1.41 or 0.1.41-beta.1.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,14 +22,20 @@ export function parseReleaseVersion(version) {
|
|||||||
const minor = Number.parseInt(match.groups.minor, 10);
|
const minor = Number.parseInt(match.groups.minor, 10);
|
||||||
const patch = Number.parseInt(match.groups.patch, 10);
|
const patch = Number.parseInt(match.groups.patch, 10);
|
||||||
const prerelease = match.groups.prerelease ?? null;
|
const prerelease = match.groups.prerelease ?? null;
|
||||||
const rcMatch = prerelease?.match(/^rc\.(?<rc>\d+)$/) ?? null;
|
const betaMatch = prerelease?.match(/^beta\.(?<beta>\d+)$/) ?? null;
|
||||||
const rcNumber = rcMatch?.groups?.rc ? Number.parseInt(rcMatch.groups.rc, 10) : null;
|
const betaNumber = betaMatch?.groups?.beta ? Number.parseInt(betaMatch.groups.beta, 10) : null;
|
||||||
|
|
||||||
|
if (prerelease !== null && betaNumber === null) {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported release version "${version}". Expected beta prerelease versions like 0.1.41-beta.1.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assertInteger(major, "major version");
|
assertInteger(major, "major version");
|
||||||
assertInteger(minor, "minor version");
|
assertInteger(minor, "minor version");
|
||||||
assertInteger(patch, "patch version");
|
assertInteger(patch, "patch version");
|
||||||
if (rcNumber !== null) {
|
if (betaNumber !== null) {
|
||||||
assertInteger(rcNumber, "release candidate number");
|
assertInteger(betaNumber, "beta number");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -40,8 +46,8 @@ export function parseReleaseVersion(version) {
|
|||||||
prerelease,
|
prerelease,
|
||||||
baseVersion: `${major}.${minor}.${patch}`,
|
baseVersion: `${major}.${minor}.${patch}`,
|
||||||
isPrerelease: prerelease !== null,
|
isPrerelease: prerelease !== null,
|
||||||
isReleaseCandidate: rcNumber !== null,
|
isBeta: betaNumber !== null,
|
||||||
rcNumber,
|
betaNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +63,7 @@ export function normalizeReleaseTag(rawTag) {
|
|||||||
const match = trimmed.match(sourceTagPattern);
|
const match = trimmed.match(sourceTagPattern);
|
||||||
if (!match?.groups?.version) {
|
if (!match?.groups?.version) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unsupported release tag "${rawTag}". Expected vX.Y.Z, vX.Y.Z-rc.N, desktop-v..., or android-v...`,
|
`Unsupported release tag "${rawTag}". Expected vX.Y.Z, vX.Y.Z-beta.N, desktop-v..., or android-v...`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return `v${match.groups.version}`;
|
return `v${match.groups.version}`;
|
||||||
@@ -73,9 +79,10 @@ export function getReleaseInfoFromSourceTag(sourceTag) {
|
|||||||
baseVersion: parsed.baseVersion,
|
baseVersion: parsed.baseVersion,
|
||||||
prerelease: parsed.prerelease,
|
prerelease: parsed.prerelease,
|
||||||
isPrerelease: parsed.isPrerelease,
|
isPrerelease: parsed.isPrerelease,
|
||||||
isReleaseCandidate: parsed.isReleaseCandidate,
|
isBeta: parsed.isBeta,
|
||||||
rcNumber: parsed.rcNumber,
|
betaNumber: parsed.betaNumber,
|
||||||
releaseType: parsed.isPrerelease ? "prerelease" : "release",
|
releaseType: parsed.isPrerelease ? "prerelease" : "release",
|
||||||
|
releaseChannel: parsed.isBeta ? "beta" : "latest",
|
||||||
isSmokeTag: sourceTag.includes("gha-smoke"),
|
isSmokeTag: sourceTag.includes("gha-smoke"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -110,54 +117,54 @@ export function computeNextReleaseVersion(currentVersion, mode) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "rc-patch" || mode === "rc-minor" || mode === "rc-major") {
|
if (mode === "beta-patch" || mode === "beta-minor" || mode === "beta-major") {
|
||||||
if (parsed.isPrerelease) {
|
if (parsed.isPrerelease) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot start a new RC line from prerelease version ${currentVersion}. Use rc-next or promote.`,
|
`Cannot start a new beta line from prerelease version ${currentVersion}. Use beta-next or promote.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (mode === "rc-patch") {
|
if (mode === "beta-patch") {
|
||||||
return formatReleaseVersion({
|
return formatReleaseVersion({
|
||||||
major: parsed.major,
|
major: parsed.major,
|
||||||
minor: parsed.minor,
|
minor: parsed.minor,
|
||||||
patch: parsed.patch + 1,
|
patch: parsed.patch + 1,
|
||||||
prerelease: "rc.1",
|
prerelease: "beta.1",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (mode === "rc-minor") {
|
if (mode === "beta-minor") {
|
||||||
return formatReleaseVersion({
|
return formatReleaseVersion({
|
||||||
major: parsed.major,
|
major: parsed.major,
|
||||||
minor: parsed.minor + 1,
|
minor: parsed.minor + 1,
|
||||||
patch: 0,
|
patch: 0,
|
||||||
prerelease: "rc.1",
|
prerelease: "beta.1",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return formatReleaseVersion({
|
return formatReleaseVersion({
|
||||||
major: parsed.major + 1,
|
major: parsed.major + 1,
|
||||||
minor: 0,
|
minor: 0,
|
||||||
patch: 0,
|
patch: 0,
|
||||||
prerelease: "rc.1",
|
prerelease: "beta.1",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "rc-next") {
|
if (mode === "beta-next") {
|
||||||
if (!parsed.isReleaseCandidate || parsed.rcNumber === null) {
|
if (!parsed.isBeta || parsed.betaNumber === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot advance RC number from ${currentVersion}. Expected a version like 0.1.41-rc.1.`,
|
`Cannot advance beta number from ${currentVersion}. Expected a version like 0.1.41-beta.1.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return formatReleaseVersion({
|
return formatReleaseVersion({
|
||||||
major: parsed.major,
|
major: parsed.major,
|
||||||
minor: parsed.minor,
|
minor: parsed.minor,
|
||||||
patch: parsed.patch,
|
patch: parsed.patch,
|
||||||
prerelease: `rc.${parsed.rcNumber + 1}`,
|
prerelease: `beta.${parsed.betaNumber + 1}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "promote") {
|
if (mode === "promote") {
|
||||||
if (!parsed.isReleaseCandidate) {
|
if (!parsed.isBeta) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot promote ${currentVersion}. Expected a release candidate version like 0.1.41-rc.1.`,
|
`Cannot promote ${currentVersion}. Expected a beta version like 0.1.41-beta.1.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return parsed.baseVersion;
|
return parsed.baseVersion;
|
||||||
|
|||||||
53
scripts/release-version-utils.test.mjs
Normal file
53
scripts/release-version-utils.test.mjs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import {
|
||||||
|
computeNextReleaseVersion,
|
||||||
|
getReleaseInfoFromSourceTag,
|
||||||
|
parseReleaseVersion,
|
||||||
|
} from "./release-version-utils.mjs";
|
||||||
|
|
||||||
|
test("computes the next beta patch from a stable version", () => {
|
||||||
|
assert.equal(computeNextReleaseVersion("0.1.59", "beta-patch"), "0.1.60-beta.1");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("advances beta versions", () => {
|
||||||
|
assert.equal(computeNextReleaseVersion("0.1.60-beta.1", "beta-next"), "0.1.60-beta.2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("promotes beta versions to stable", () => {
|
||||||
|
assert.equal(computeNextReleaseVersion("0.1.60-beta.2", "promote"), "0.1.60");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parses beta release metadata", () => {
|
||||||
|
assert.deepEqual(parseReleaseVersion("0.1.60-beta.1"), {
|
||||||
|
version: "0.1.60-beta.1",
|
||||||
|
major: 0,
|
||||||
|
minor: 1,
|
||||||
|
patch: 60,
|
||||||
|
prerelease: "beta.1",
|
||||||
|
baseVersion: "0.1.60",
|
||||||
|
isPrerelease: true,
|
||||||
|
isBeta: true,
|
||||||
|
betaNumber: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("emits beta release info from tags", () => {
|
||||||
|
assert.deepEqual(getReleaseInfoFromSourceTag("v0.1.60-beta.1"), {
|
||||||
|
sourceTag: "v0.1.60-beta.1",
|
||||||
|
releaseTag: "v0.1.60-beta.1",
|
||||||
|
version: "0.1.60-beta.1",
|
||||||
|
baseVersion: "0.1.60",
|
||||||
|
prerelease: "beta.1",
|
||||||
|
isPrerelease: true,
|
||||||
|
isBeta: true,
|
||||||
|
betaNumber: 1,
|
||||||
|
releaseType: "prerelease",
|
||||||
|
releaseChannel: "beta",
|
||||||
|
isSmokeTag: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rejects non-beta prerelease versions", () => {
|
||||||
|
assert.throws(() => parseReleaseVersion("0.1.60-canary.1"), /Expected beta prerelease versions/);
|
||||||
|
});
|
||||||
@@ -11,7 +11,7 @@ const rootPackagePath = path.join(rootDir, "package.json");
|
|||||||
function usageAndExit(code = 1) {
|
function usageAndExit(code = 1) {
|
||||||
process.stderr.write(`Usage: node scripts/set-release-version.mjs --mode <mode> [--print]\n`);
|
process.stderr.write(`Usage: node scripts/set-release-version.mjs --mode <mode> [--print]\n`);
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
"Modes: patch, minor, major, rc-patch, rc-minor, rc-major, rc-next, promote\n",
|
"Modes: patch, minor, major, beta-patch, beta-minor, beta-major, beta-next, promote\n",
|
||||||
);
|
);
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,24 +139,6 @@ function updateReleaseNotes({ releaseId, repo, notesPath }, execFileSync = nodeE
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPrereleaseNotes({ releaseTag, version }) {
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
|
||||||
const lines = [
|
|
||||||
`## ${version} - ${today}`,
|
|
||||||
"",
|
|
||||||
"Release candidate build for testing from `main`.",
|
|
||||||
"",
|
|
||||||
`- Tag: \`${releaseTag}\``,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (process.env.GITHUB_SHA) {
|
|
||||||
lines.push(`- Commit: \`${process.env.GITHUB_SHA}\``);
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.push("- This prerelease does not replace the website's latest stable download.");
|
|
||||||
return `${lines.join("\n")}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function syncReleaseNotes(argv = process.argv.slice(2), deps = {}) {
|
export function syncReleaseNotes(argv = process.argv.slice(2), deps = {}) {
|
||||||
const execFileSync = deps.execFileSync ?? nodeExecFileSync;
|
const execFileSync = deps.execFileSync ?? nodeExecFileSync;
|
||||||
const args = parseArgs(argv);
|
const args = parseArgs(argv);
|
||||||
@@ -170,13 +152,6 @@ export function syncReleaseNotes(argv = process.argv.slice(2), deps = {}) {
|
|||||||
|
|
||||||
let notes = targetEntry?.notes ?? null;
|
let notes = targetEntry?.notes ?? null;
|
||||||
|
|
||||||
if (!notes && releaseInfo.isPrerelease) {
|
|
||||||
notes = buildPrereleaseNotes({
|
|
||||||
releaseTag: releaseInfo.releaseTag,
|
|
||||||
version: releaseInfo.version,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!notes) {
|
if (!notes) {
|
||||||
console.log(`No matching changelog section found for ${targetTag}. Skipping.`);
|
console.log(`No matching changelog section found for ${targetTag}. Skipping.`);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function withTempChangelog(fn) {
|
|||||||
const previousCwd = process.cwd();
|
const previousCwd = process.cwd();
|
||||||
const tempDir = mkdtempSync(path.join(tmpdir(), "paseo-release-notes-test-"));
|
const tempDir = mkdtempSync(path.join(tmpdir(), "paseo-release-notes-test-"));
|
||||||
process.chdir(tempDir);
|
process.chdir(tempDir);
|
||||||
writeFileSync("CHANGELOG.md", "## 0.1.60 - 2026-04-20\n\n- Stable notes.\n");
|
writeFileSync("CHANGELOG.md", "## 0.1.60-beta.1 - 2026-04-20\n\n- Beta notes.\n");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fn();
|
fn();
|
||||||
@@ -26,7 +26,7 @@ test("updates an existing release body through the release id API", () => {
|
|||||||
const execFileSync = (command, args, options) => {
|
const execFileSync = (command, args, options) => {
|
||||||
calls.push({ args, command, options });
|
calls.push({ args, command, options });
|
||||||
|
|
||||||
if (args[0] === "api" && args[1] === "repos/getpaseo/paseo/releases/tags/v0.1.60-rc.3") {
|
if (args[0] === "api" && args[1] === "repos/getpaseo/paseo/releases/tags/v0.1.60-beta.1") {
|
||||||
return JSON.stringify({ id: 311163621 });
|
return JSON.stringify({ id: 311163621 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,14 +34,14 @@ test("updates an existing release body through the release id API", () => {
|
|||||||
const notesArg = args.find((arg) => arg.startsWith("body=@"));
|
const notesArg = args.find((arg) => arg.startsWith("body=@"));
|
||||||
assert.ok(notesArg, "PATCH should send the notes body from a file");
|
assert.ok(notesArg, "PATCH should send the notes body from a file");
|
||||||
const notesPath = notesArg.slice("body=@".length);
|
const notesPath = notesArg.slice("body=@".length);
|
||||||
assert.match(notesPath, /v0\.1\.60-rc\.3-notes\.md$/);
|
assert.match(notesPath, /v0\.1\.60-beta\.1-notes\.md$/);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unexpected gh call: ${command} ${args.join(" ")}`);
|
throw new Error(`Unexpected gh call: ${command} ${args.join(" ")}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
syncReleaseNotes(["--repo", "getpaseo/paseo", "--tag", "v0.1.60-rc.3"], {
|
syncReleaseNotes(["--repo", "getpaseo/paseo", "--tag", "v0.1.60-beta.1"], {
|
||||||
execFileSync,
|
execFileSync,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user