Compare commits

..

29 Commits

Author SHA1 Message Date
Gregory Schier
41fe01adb9 Update generated route tree formatting 2026-05-06 07:51:35 -07:00
Gregory Schier
a200410697 Fix gRPC Any response reflection (#451) 2026-05-06 07:42:35 -07:00
pandeb
4c15a49f8f fix: align HTTP method tags to the left (#450) 2026-05-06 07:13:22 -07:00
Gregory Schier
c901ad4cbd Some cleanup 2026-04-30 09:00:12 -07:00
Gregory Schier
d73d38f418 Surface error when failing importing a binary file 2026-04-29 07:33:21 -07:00
Gregory Schier
b0740770df Increase HTTP/2 response header limit
Set Yaak's reqwest request clients to accept HTTP/2 response header lists up to 1 MiB and wrap configured clients so the sender path cannot accidentally bypass the shared builder.

Feedback: https://yaak.app/feedback/posts/when-response-headers-exceed-a-certain-size-hyper-throws-error
2026-04-28 07:49:47 -07:00
dependabot[bot]
75d94da578 Bump quinn-proto from 0.11.12 to 0.11.14 (#421)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:43:06 -07:00
dependabot[bot]
79c49d8398 Bump vite-plus from 0.1.11 to 0.1.19 (#447)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:42:48 -07:00
dependabot[bot]
7c51510616 Bump rustls-webpki from 0.103.10 to 0.103.13 (#446)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:40:01 -07:00
dependabot[bot]
0b36ee56d2 Bump hono from 4.12.4 to 4.12.14 (#442)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:38:31 -07:00
dependabot[bot]
2c345fc2ca Bump picomatch from 4.0.3 to 4.0.4 (#436)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:38:13 -07:00
dependabot[bot]
909580c4a4 Bump rustls-webpki from 0.103.7 to 0.103.10 (#434)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:38:06 -07:00
dependabot[bot]
e805b225f7 Bump yaml from 2.8.2 to 2.8.3 (#435)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:38:00 -07:00
dependabot[bot]
0def693b63 Bump follow-redirects from 1.15.11 to 1.16.0 (#441)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:37:52 -07:00
dependabot[bot]
7109db911a Bump @xmldom/xmldom from 0.9.8 to 0.9.10 (#444)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:37:45 -07:00
dependabot[bot]
980f26f2f0 Bump uuid from 11.1.0 to 14.0.0 (#445)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:37:40 -07:00
dependabot[bot]
6b56ec569f Bump @hono/node-server from 1.19.10 to 1.19.13 (#439)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:37:00 -07:00
dependabot[bot]
36fa7a52fe Bump tar from 0.4.44 to 0.4.45 (#433)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-23 06:36:49 -07:00
Gregory Schier
c95099588f Fix duplicate request snapshotting URL as name (#429) 2026-04-23 06:36:33 -07:00
Nguyễn Huy Hoàng
929f6202a4 fix: bug where selection layer leaves a ghost residual line below wrapped lines after deselecting (#432)
Co-authored-by: hoangnh290 <hoangnh290@viettel.com.vn>
2026-04-23 06:30:18 -07:00
Julien Bourdeau
915af7e3de chore: Delete old .nvmrc (#443) 2026-04-23 06:28:49 -07:00
Gregory Schier
eb9b5b6bb6 Don't override user-defined Content-Type for GraphQL and form-urlencoded requests
The frontend already sets the appropriate Content-Type header when
selecting a body type, so the backend no longer needs to force it.
This allows users to override Content-Type for servers with
non-standard requirements.

Fixes https://yaak.app/feedback/posts/graphql-mode-ignores-manual-content-type-header-override

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 09:43:04 -07:00
Gregory Schier
b4a1c418bb Run oxfmt across repo, add format script and docs
Add .oxfmtignore to skip generated bindings and wasm-pack output.
Add npm format script, update DEVELOPMENT.md for Vite+ toolchain,
and format all non-generated files with oxfmt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 10:15:49 -07:00
Gregory Schier
45262edfbd Migrate to Vite+ unified toolchain (#428) 2026-03-13 09:27:56 -07:00
Gregory Schier
aed7bd12ea Add react compiler 2026-03-13 06:49:14 -07:00
Gregory Schier
b5928af1d7 Bump react 2026-03-13 06:47:42 -07:00
Gregory Schier
6cc47bea38 Fixes for wasm 2026-03-13 06:46:27 -07:00
Gregory Schier
b83d9e6765 Vite 8 upgrade 2026-03-13 06:33:20 -07:00
Gregory Schier
c8ba35e268 Gracefully handle plugin init failures (#424)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:55:46 -07:00
693 changed files with 16388 additions and 14887 deletions

View File

@@ -1,9 +1,11 @@
# Claude Context: Detaching Tauri from Yaak # Claude Context: Detaching Tauri from Yaak
## Goal ## Goal
Make Yaak runnable as a standalone CLI without Tauri as a dependency. The core Rust crates in `crates/` should be usable independently, while Tauri-specific code lives in `crates-tauri/`. Make Yaak runnable as a standalone CLI without Tauri as a dependency. The core Rust crates in `crates/` should be usable independently, while Tauri-specific code lives in `crates-tauri/`.
## Project Structure ## Project Structure
``` ```
crates/ # Core crates - should NOT depend on Tauri crates/ # Core crates - should NOT depend on Tauri
crates-tauri/ # Tauri-specific crates (yaak-app, yaak-tauri-utils, etc.) crates-tauri/ # Tauri-specific crates (yaak-app, yaak-tauri-utils, etc.)
@@ -13,11 +15,13 @@ crates-cli/ # CLI crate (yaak-cli)
## Completed Work ## Completed Work
### 1. Folder Restructure ### 1. Folder Restructure
- Moved Tauri-dependent app code to `crates-tauri/yaak-app/` - Moved Tauri-dependent app code to `crates-tauri/yaak-app/`
- Created `crates-tauri/yaak-tauri-utils/` for shared Tauri utilities (window traits, api_client, error handling) - Created `crates-tauri/yaak-tauri-utils/` for shared Tauri utilities (window traits, api_client, error handling)
- Created `crates-cli/yaak-cli/` for the standalone CLI - Created `crates-cli/yaak-cli/` for the standalone CLI
### 2. Decoupled Crates (no longer depend on Tauri) ### 2. Decoupled Crates (no longer depend on Tauri)
- **yaak-models**: Uses `init_standalone()` pattern for CLI database access - **yaak-models**: Uses `init_standalone()` pattern for CLI database access
- **yaak-http**: Removed Tauri plugin, HttpConnectionManager initialized in yaak-app setup - **yaak-http**: Removed Tauri plugin, HttpConnectionManager initialized in yaak-app setup
- **yaak-common**: Only contains Tauri-free utilities (serde, platform) - **yaak-common**: Only contains Tauri-free utilities (serde, platform)
@@ -25,6 +29,7 @@ crates-cli/ # CLI crate (yaak-cli)
- **yaak-grpc**: Replaced AppHandle with GrpcConfig struct, uses tokio::process::Command instead of Tauri sidecar - **yaak-grpc**: Replaced AppHandle with GrpcConfig struct, uses tokio::process::Command instead of Tauri sidecar
### 3. CLI Implementation ### 3. CLI Implementation
- Basic CLI at `crates-cli/yaak-cli/src/main.rs` - Basic CLI at `crates-cli/yaak-cli/src/main.rs`
- Commands: workspaces, requests, send (by ID), get (ad-hoc URL), create - Commands: workspaces, requests, send (by ID), get (ad-hoc URL), create
- Uses same database as Tauri app via `yaak_models::init_standalone()` - Uses same database as Tauri app via `yaak_models::init_standalone()`
@@ -32,12 +37,14 @@ crates-cli/ # CLI crate (yaak-cli)
## Remaining Work ## Remaining Work
### Crates Still Depending on Tauri (in `crates/`) ### Crates Still Depending on Tauri (in `crates/`)
1. **yaak-git** (3 files) - Moderate complexity 1. **yaak-git** (3 files) - Moderate complexity
2. **yaak-plugins** (13 files) - **Hardest** - deeply integrated with Tauri for plugin-to-window communication 2. **yaak-plugins** (13 files) - **Hardest** - deeply integrated with Tauri for plugin-to-window communication
3. **yaak-sync** (4 files) - Moderate complexity 3. **yaak-sync** (4 files) - Moderate complexity
4. **yaak-ws** (5 files) - Moderate complexity 4. **yaak-ws** (5 files) - Moderate complexity
### Pattern for Decoupling ### Pattern for Decoupling
1. Remove Tauri plugin `init()` function from the crate 1. Remove Tauri plugin `init()` function from the crate
2. Move commands to `yaak-app/src/commands.rs` or keep inline in `lib.rs` 2. Move commands to `yaak-app/src/commands.rs` or keep inline in `lib.rs`
3. Move extension traits (e.g., `SomethingManagerExt`) to yaak-app or yaak-tauri-utils 3. Move extension traits (e.g., `SomethingManagerExt`) to yaak-app or yaak-tauri-utils
@@ -47,6 +54,7 @@ crates-cli/ # CLI crate (yaak-cli)
7. Replace `tauri::async_runtime::block_on` with `tokio::runtime::Handle::current().block_on()` 7. Replace `tauri::async_runtime::block_on` with `tokio::runtime::Handle::current().block_on()`
## Key Files ## Key Files
- `crates-tauri/yaak-app/src/lib.rs` - Main Tauri app, setup block initializes managers - `crates-tauri/yaak-app/src/lib.rs` - Main Tauri app, setup block initializes managers
- `crates-tauri/yaak-app/src/commands.rs` - Migrated Tauri commands - `crates-tauri/yaak-app/src/commands.rs` - Migrated Tauri commands
- `crates-tauri/yaak-app/src/models_ext.rs` - Database plugin and extension traits - `crates-tauri/yaak-app/src/models_ext.rs` - Database plugin and extension traits
@@ -54,9 +62,11 @@ crates-cli/ # CLI crate (yaak-cli)
- `crates/yaak-models/src/lib.rs` - Contains `init_standalone()` for CLI usage - `crates/yaak-models/src/lib.rs` - Contains `init_standalone()` for CLI usage
## Git Branch ## Git Branch
Working on `detach-tauri` branch. Working on `detach-tauri` branch.
## Recent Commits ## Recent Commits
``` ```
c40cff40 Remove Tauri dependencies from yaak-crypto and yaak-grpc c40cff40 Remove Tauri dependencies from yaak-crypto and yaak-grpc
df495f1d Move Tauri utilities from yaak-common to yaak-tauri-utils df495f1d Move Tauri utilities from yaak-common to yaak-tauri-utils
@@ -67,6 +77,7 @@ e718a5f1 Refactor models_ext to use init_standalone from yaak-models
``` ```
## Testing ## Testing
- Run `cargo check -p <crate>` to verify a crate builds without Tauri - Run `cargo check -p <crate>` to verify a crate builds without Tauri
- Run `npm run app-dev` to test the Tauri app still works - Run `npm run app-dev` to test the Tauri app still works
- Run `cargo run -p yaak-cli -- --help` to test the CLI - Run `cargo run -p yaak-cli -- --help` to test the CLI

View File

@@ -8,7 +8,7 @@ Generate formatted release notes for Yaak releases by analyzing git history and
## What to do ## What to do
1. Identifies the version tag and previous version 1. Identifies the version tag and previous version
2. Retrieves all commits between versions 2. Retrieves all commits between versions
- If the version is a beta version, it retrieves commits between the beta version and previous beta version - If the version is a beta version, it retrieves commits between the beta version and previous beta version
- If the version is a stable version, it retrieves commits between the stable version and the previous stable version - If the version is a stable version, it retrieves commits between the stable version and the previous stable version
3. Fetches PR descriptions for linked issues to find: 3. Fetches PR descriptions for linked issues to find:

View File

@@ -1,10 +1,9 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ""
labels: '' labels: ""
assignees: '' assignees: ""
--- ---
**Describe the bug** **Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
@@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari] - OS: [e.g. iOS]
- Version [e.g. 22] - Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):** **Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1] - Device: [e.g. iPhone6]
- Browser [e.g. stock browser, safari] - OS: [e.g. iOS8.1]
- Version [e.g. 22] - Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

View File

@@ -11,6 +11,7 @@
- [ ] I added or updated tests when reasonable. - [ ] I added or updated tests when reasonable.
Approved feedback item (required if not a bug fix or small-scope improvement): Approved feedback item (required if not a bug fix or small-scope improvement):
<!-- https://yaak.app/feedback/... --> <!-- https://yaak.app/feedback/... -->
## Related ## Related

View File

@@ -14,17 +14,20 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: voidzero-dev/setup-vp@v1
with:
node-version: "24"
cache: true
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
with: with:
shared-key: ci shared-key: ci
cache-on-failure: true cache-on-failure: true
- run: npm ci - run: vp install
- run: npm run bootstrap - run: npm run bootstrap
- run: npm run lint - run: npm run lint
- name: Run JS Tests - name: Run JS Tests
run: npm test run: vp test
- name: Run Rust Tests - name: Run Rust Tests
run: cargo test --all run: cargo test --all

View File

@@ -47,4 +47,3 @@ jobs:
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options # or https://code.claude.com/docs/en/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)' # claude_args: '--allowed-tools Bash(gh pr:*)'

View File

@@ -50,8 +50,11 @@ jobs:
- name: Checkout yaakapp/app - name: Checkout yaakapp/app
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Vite+
uses: actions/setup-node@v4 uses: voidzero-dev/setup-vp@v1
with:
node-version: "24"
cache: true
- name: install Rust stable - name: install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
@@ -87,13 +90,13 @@ jobs:
echo $dir >> $env:GITHUB_PATH echo $dir >> $env:GITHUB_PATH
& $exe --version & $exe --version
- run: npm ci - run: vp install
- run: npm run bootstrap - run: npm run bootstrap
env: env:
YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }} YAAK_TARGET_ARCH: ${{ matrix.yaak_arch }}
- run: npm run lint - run: npm run lint
- name: Run JS Tests - name: Run JS Tests
run: npm test run: vp test
- name: Run Rust Tests - name: Run Rust Tests
run: cargo test --all --exclude yaak-cli run: cargo test --all --exclude yaak-cli

View File

@@ -16,23 +16,23 @@ jobs:
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@v1
with: with:
token: ${{ secrets.SPONSORS_PAT }} token: ${{ secrets.SPONSORS_PAT }}
file: 'README.md' file: "README.md"
maximum: 1999 maximum: 1999
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="50px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;' template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="50px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
active-only: false active-only: false
include-private: true include-private: true
marker: 'sponsors-base' marker: "sponsors-base"
- name: Generate Sponsors - name: Generate Sponsors
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@v1
with: with:
token: ${{ secrets.SPONSORS_PAT }} token: ${{ secrets.SPONSORS_PAT }}
file: 'README.md' file: "README.md"
minimum: 2000 minimum: 2000
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="80px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;' template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="80px" alt="User avatar: {{{ login }}}" /></a>&nbsp;&nbsp;'
active-only: false active-only: false
include-private: true include-private: true
marker: 'sponsors-premium' marker: "sponsors-premium"
# ⚠️ Note: You can use any deployment step here to automatically push the README # ⚠️ Note: You can use any deployment step here to automatically push the README
# changes back to your branch. # changes back to your branch.
@@ -41,4 +41,4 @@ jobs:
with: with:
branch: main branch: main
force: false force: false
folder: '.' folder: "."

1
.node-version Normal file
View File

@@ -0,0 +1 @@
24.14.0

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
# vite-plugin-wasm has not yet declared Vite 8 in its peerDependencies
legacy-peer-deps=true

1
.nvmrc
View File

@@ -1 +0,0 @@
20

4
.oxfmtrc.json Normal file
View File

@@ -0,0 +1,4 @@
{
"printWidth": 100,
"ignorePatterns": ["**/bindings/**", "crates/yaak-templates/pkg/**", "src-web/routeTree.gen.ts"]
}

1
.vite-hooks/pre-commit Normal file
View File

@@ -0,0 +1 @@
vp lint

View File

@@ -1,3 +1,7 @@
{ {
"recommendations": ["biomejs.biome", "rust-lang.rust-analyzer", "bradlc.vscode-tailwindcss"] "recommendations": [
"rust-lang.rust-analyzer",
"bradlc.vscode-tailwindcss",
"VoidZero.vite-plus-extension-pack"
]
} }

View File

@@ -1,6 +1,8 @@
{ {
"editor.defaultFormatter": "biomejs.biome", "editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"biome.enabled": true, "editor.formatOnSaveMode": "file",
"biome.lint.format.enable": true "editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
} }

16
Cargo.lock generated
View File

@@ -1147,7 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [ dependencies = [
"lazy_static 1.5.0", "lazy_static 1.5.0",
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -5753,9 +5753,9 @@ dependencies = [
[[package]] [[package]]
name = "quinn-proto" name = "quinn-proto"
version = "0.11.12" version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [ dependencies = [
"bytes", "bytes",
"getrandom 0.3.3", "getrandom 0.3.3",
@@ -6803,9 +6803,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.7" version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -7692,9 +7692,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.44" version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
dependencies = [ dependencies = [
"filetime", "filetime",
"libc", "libc",
@@ -9495,7 +9495,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]

View File

@@ -1,30 +1,30 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = [ members = [
"crates/yaak", "crates/yaak",
# Shared crates (no Tauri dependency) # Shared crates (no Tauri dependency)
"crates/yaak-core", "crates/yaak-core",
"crates/yaak-common", "crates/yaak-common",
"crates/yaak-crypto", "crates/yaak-crypto",
"crates/yaak-git", "crates/yaak-git",
"crates/yaak-grpc", "crates/yaak-grpc",
"crates/yaak-http", "crates/yaak-http",
"crates/yaak-models", "crates/yaak-models",
"crates/yaak-plugins", "crates/yaak-plugins",
"crates/yaak-sse", "crates/yaak-sse",
"crates/yaak-sync", "crates/yaak-sync",
"crates/yaak-templates", "crates/yaak-templates",
"crates/yaak-tls", "crates/yaak-tls",
"crates/yaak-ws", "crates/yaak-ws",
"crates/yaak-api", "crates/yaak-api",
# CLI crates # CLI crates
"crates-cli/yaak-cli", "crates-cli/yaak-cli",
# Tauri-specific crates # Tauri-specific crates
"crates-tauri/yaak-app", "crates-tauri/yaak-app",
"crates-tauri/yaak-fonts", "crates-tauri/yaak-fonts",
"crates-tauri/yaak-license", "crates-tauri/yaak-license",
"crates-tauri/yaak-mac-window", "crates-tauri/yaak-mac-window",
"crates-tauri/yaak-tauri-utils", "crates-tauri/yaak-tauri-utils",
] ]
[workspace.dependencies] [workspace.dependencies]

View File

@@ -1,24 +1,26 @@
# Developer Setup # Developer Setup
Yaak is a combined Node.js and Rust monorepo. It is a [Tauri](https://tauri.app) project, so Yaak is a combined Node.js and Rust monorepo. It is a [Tauri](https://tauri.app) project, so
uses Rust and HTML/CSS/JS for the main application but there is also a plugin system powered uses Rust and HTML/CSS/JS for the main application but there is also a plugin system powered
by a Node.js sidecar that communicates to the app over gRPC. by a Node.js sidecar that communicates to the app over gRPC.
Because of the moving parts, there are a few setup steps required before development can Because of the moving parts, there are a few setup steps required before development can
begin. begin.
## Prerequisites ## Prerequisites
Make sure you have the following tools installed: Make sure you have the following tools installed:
- [Node.js](https://nodejs.org/en/download/package-manager) - [Node.js](https://nodejs.org/en/download/package-manager) (v24+)
- [Rust](https://www.rust-lang.org/tools/install) - [Rust](https://www.rust-lang.org/tools/install)
- [Vite+](https://vite.dev/guide/vite-plus) (`vp` CLI)
Check the installations with the following commands: Check the installations with the following commands:
```shell ```shell
node -v node -v
npm -v npm -v
vp --version
rustc --version rustc --version
``` ```
@@ -45,12 +47,12 @@ npm start
## SQLite Migrations ## SQLite Migrations
New migrations can be created from the `src-tauri/` directory: New migrations can be created from the `src-tauri/` directory:
```shell ```shell
npm run migration npm run migration
``` ```
Rerun the app to apply the migrations. Rerun the app to apply the migrations.
_Note: For safety, development builds use a separate database location from production builds._ _Note: For safety, development builds use a separate database location from production builds._
@@ -61,9 +63,9 @@ _Note: For safety, development builds use a separate database location from prod
lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts lezer-generator components/core/Editor/<LANG>/<LANG>.grammar > components/core/Editor/<LANG>/<LANG>.ts
``` ```
## Linting & Formatting ## Linting and Formatting
This repo uses Biome for linting and formatting (replacing ESLint + Prettier). This repo uses [Vite+](https://vite.dev/guide/vite-plus) for linting (oxlint) and formatting (oxfmt).
- Lint the entire repo: - Lint the entire repo:
@@ -71,12 +73,6 @@ This repo uses Biome for linting and formatting (replacing ESLint + Prettier).
npm run lint npm run lint
``` ```
- Auto-fix lint issues where possible:
```sh
npm run lint:fix
```
- Format code: - Format code:
```sh ```sh
@@ -84,5 +80,7 @@ npm run format
``` ```
Notes: Notes:
- Many workspace packages also expose the same scripts (`lint`, `lint:fix`, and `format`).
- TypeScript type-checking still runs separately via `tsc --noEmit` in relevant packages. - A pre-commit hook runs `vp lint` automatically on commit.
- Some workspace packages also run `tsc --noEmit` for type-checking.
- VS Code users should install the recommended extensions for format-on-save support.

View File

@@ -16,8 +16,6 @@
</p> </p>
<br> <br>
<p align="center"> <p align="center">
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/bytebase"><img src="https:&#x2F;&#x2F;github.com&#x2F;bytebase.png" width="80px" alt="User avatar: bytebase" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium --> <!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https:&#x2F;&#x2F;github.com&#x2F;MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a>&nbsp;&nbsp;<a href="https://github.com/dharsanb"><img src="https:&#x2F;&#x2F;github.com&#x2F;dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a>&nbsp;&nbsp;<a href="https://github.com/railwayapp"><img src="https:&#x2F;&#x2F;github.com&#x2F;railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a>&nbsp;&nbsp;<a href="https://github.com/caseyamcl"><img src="https:&#x2F;&#x2F;github.com&#x2F;caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a>&nbsp;&nbsp;<a href="https://github.com/bytebase"><img src="https:&#x2F;&#x2F;github.com&#x2F;bytebase.png" width="80px" alt="User avatar: bytebase" /></a>&nbsp;&nbsp;<a href="https://github.com/"><img src="https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;JamesIves&#x2F;github-sponsors-readme-action&#x2F;dev&#x2F;.github&#x2F;assets&#x2F;placeholder.png" width="80px" alt="User avatar: " /></a>&nbsp;&nbsp;<!-- sponsors-premium -->
</p> </p>
@@ -27,12 +25,10 @@
![Yaak API Client](https://yaak.app/static/screenshot.png) ![Yaak API Client](https://yaak.app/static/screenshot.png)
## Features ## Features
Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it. Yaak is an offline-first API client designed to stay out of your way while giving you everything you need when you need it.
Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in. Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight, and private. No telemetry, no VC funding, and no cloud lock-in.
### 🌐 Work with any API ### 🌐 Work with any API
@@ -41,21 +37,23 @@ Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight
- Filter and inspect responses with JSONPath or XPath. - Filter and inspect responses with JSONPath or XPath.
### 🔐 Stay secure ### 🔐 Stay secure
- Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication. - Use OAuth 2.0, JWT, Basic Auth, or custom plugins for authentication.
- Secure sensitive values with encrypted secrets. - Secure sensitive values with encrypted secrets.
- Store secrets in your OS keychain. - Store secrets in your OS keychain.
### ☁️ Organize & collaborate ### ☁️ Organize & collaborate
- Group requests into workspaces and nested folders. - Group requests into workspaces and nested folders.
- Use environment variables to switch between dev, staging, and prod. - Use environment variables to switch between dev, staging, and prod.
- Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox. - Mirror workspaces to your filesystem for versioning in Git or syncing with Dropbox.
### 🧩 Extend & customize ### 🧩 Extend & customize
- Insert dynamic values like UUIDs or timestamps with template tags. - Insert dynamic values like UUIDs or timestamps with template tags.
- Pick from built-in themes or build your own. - Pick from built-in themes or build your own.
- Create plugins to extend authentication, template tags, or the UI. - Create plugins to extend authentication, template tags, or the UI.
## Contribution Policy ## Contribution Policy
> [!IMPORTANT] > [!IMPORTANT]

View File

@@ -1,55 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useKeyWithClickEvents": "off"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"bracketSpacing": true
},
"css": {
"parser": {
"tailwindDirectives": true
},
"linter": {
"enabled": false
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always"
}
},
"files": {
"includes": [
"**",
"!**/node_modules",
"!**/dist",
"!**/build",
"!target",
"!scripts",
"!crates",
"!crates-tauri",
"!src-web/tailwind.config.cjs",
"!src-web/postcss.config.cjs",
"!src-web/vite.config.ts",
"!src-web/routeTree.gen.ts",
"!packages/plugin-runtime-types/lib",
"!**/bindings",
"!flatpak",
"!npm"
]
}
}

View File

@@ -29,7 +29,14 @@ schemars = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
sha2 = { workspace = true } sha2 = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "io-util", "net", "signal", "time"] } tokio = { workspace = true, features = [
"rt-multi-thread",
"macros",
"io-util",
"net",
"signal",
"time",
] }
walkdir = "2" walkdir = "2"
webbrowser = "1" webbrowser = "1"
zip = "4" zip = "4"

View File

@@ -1,6 +1,6 @@
# Yaak CLI # Yaak CLI
The `yaak` CLI for publishing plugins and creating/updating/sending requests. The `yaak` CLI for publishing plugins and creating/updating/sending requests.
## Installation ## Installation
@@ -24,8 +24,8 @@ Use the `yaak` CLI with agents like Claude or Codex to do useful things for you.
Here are some example prompts: Here are some example prompts:
```text ```text
Scan my API routes and create a workspace (using yaak cli) with Scan my API routes and create a workspace (using yaak cli) with
all the requests needed for me to do manual testing? all the requests needed for me to do manual testing?
``` ```
```text ```text

View File

@@ -35,7 +35,16 @@ r2d2 = "0.8.10"
r2d2_sqlite = "0.25.0" r2d2_sqlite = "0.25.0"
mime_guess = "2.0.5" mime_guess = "2.0.5"
rand = "0.9.0" rand = "0.9.0"
reqwest = { workspace = true, features = ["multipart", "gzip", "brotli", "deflate", "json", "rustls-tls-manual-roots-no-provider", "socks", "http2"] } reqwest = { workspace = true, features = [
"multipart",
"gzip",
"brotli",
"deflate",
"json",
"rustls-tls-manual-roots-no-provider",
"socks",
"http2",
] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] } serde_json = { workspace = true, features = ["raw_value"] }
tauri = { workspace = true, features = ["devtools", "protocol-asset"] } tauri = { workspace = true, features = ["devtools", "protocol-asset"] }

View File

@@ -1,9 +1,7 @@
{ {
"identifier": "default", "identifier": "default",
"description": "Default capabilities for all build variants", "description": "Default capabilities for all build variants",
"windows": [ "windows": ["*"],
"*"
],
"permissions": [ "permissions": [
"core:app:allow-identifier", "core:app:allow-identifier",
"core:event:allow-emit", "core:event:allow-emit",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/tauri", "name": "@yaakapp-internal/tauri",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "bindings/index.ts" "main": "bindings/index.ts"
} }

View File

@@ -1,9 +1,10 @@
use crate::PluginContextExt; use crate::PluginContextExt;
use crate::error::Result; use crate::error::{Error, Result};
use crate::models_ext::QueryManagerExt; use crate::models_ext::QueryManagerExt;
use log::info; use log::info;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::io::ErrorKind;
use tauri::{Manager, Runtime, WebviewWindow}; use tauri::{Manager, Runtime, WebviewWindow};
use yaak_core::WorkspaceContext; use yaak_core::WorkspaceContext;
use yaak_models::models::{ use yaak_models::models::{
@@ -18,8 +19,7 @@ pub(crate) async fn import_data<R: Runtime>(
file_path: &str, file_path: &str,
) -> Result<BatchUpsertResult> { ) -> Result<BatchUpsertResult> {
let plugin_manager = window.state::<PluginManager>(); let plugin_manager = window.state::<PluginManager>();
let file = let file = read_import_file(file_path)?;
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str(); let file_contents = file.as_str();
let import_result = plugin_manager.import_data(&window.plugin_context(), file_contents).await?; let import_result = plugin_manager.import_data(&window.plugin_context(), file_contents).await?;
@@ -127,3 +127,41 @@ pub(crate) async fn import_data<R: Runtime>(
Ok(upserted) Ok(upserted)
} }
fn read_import_file(file_path: &str) -> Result<String> {
read_to_string(file_path).map_err(|err| {
if err.kind() == ErrorKind::InvalidData {
Error::GenericError(format!(
"Import file must be UTF-8 text; binary files are not supported: {file_path}"
))
} else {
Error::GenericError(format!("Unable to read import file {file_path}: {err}"))
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{remove_file, write};
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn read_import_file_returns_error_for_binary_file() {
let path = std::env::temp_dir().join(format!(
"yaak-import-binary-{}.pftrace",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before unix epoch")
.as_nanos()
));
write(&path, [0xff, 0xfe, 0xfd]).expect("write binary fixture");
let err = read_import_file(path.to_str().expect("temp path is utf-8"))
.expect_err("binary import should return an error");
assert!(err.to_string().contains("binary files are not supported"));
remove_file(path).expect("remove binary fixture");
}
}

View File

@@ -34,8 +34,7 @@ use tokio::time;
use yaak_common::command::new_checked_command; use yaak_common::command::new_checked_command;
use yaak_crypto::manager::EncryptionManager; use yaak_crypto::manager::EncryptionManager;
use yaak_grpc::manager::{GrpcConfig, GrpcHandle}; use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
use yaak_templates::strip_json_comments::strip_json_comments; use yaak_grpc::{Code, ServiceDefinition};
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
use yaak_mac_window::AppHandleMacWindowExt; use yaak_mac_window::AppHandleMacWindowExt;
use yaak_models::models::{ use yaak_models::models::{
AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent, AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent,
@@ -60,6 +59,7 @@ use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent; use yaak_sse::sse::ServerSentEvent;
use yaak_tauri_utils::window::WorkspaceWindowTrait; use yaak_tauri_utils::window::WorkspaceWindowTrait;
use yaak_templates::format_json::format_json; use yaak_templates::format_json::format_json;
use yaak_templates::strip_json_comments::strip_json_comments;
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args}; use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
use yaak_tls::find_client_certificate; use yaak_tls::find_client_certificate;
@@ -522,7 +522,7 @@ async fn cmd_grpc_go<R: Runtime>(
&method, &method,
in_msg_stream, in_msg_stream,
&metadata, &metadata,
client_cert, client_cert.clone(),
on_message.clone(), on_message.clone(),
) )
.await, .await,
@@ -538,7 +538,7 @@ async fn cmd_grpc_go<R: Runtime>(
&method, &method,
in_msg_stream, in_msg_stream,
&metadata, &metadata,
client_cert, client_cert.clone(),
on_message.clone(), on_message.clone(),
) )
.await, .await,
@@ -551,7 +551,9 @@ async fn cmd_grpc_go<R: Runtime>(
(false, false) => ( (false, false) => (
None, None,
Some( Some(
connection.unary(&service, &method, &msg, &metadata, client_cert).await, connection
.unary(&service, &method, &msg, &metadata, client_cert.clone())
.await,
), ),
), ),
}; };
@@ -589,11 +591,34 @@ async fn cmd_grpc_go<R: Runtime>(
&UpdateSource::from_window_label(window.label()), &UpdateSource::from_window_label(window.label()),
) )
.unwrap(); .unwrap();
let response_message = msg.into_inner();
let content = match connection
.serialize_message(&response_message, &metadata, client_cert.clone())
.await
{
Ok(content) => content,
Err(err) => {
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: "Failed to read response".to_string(),
error: Some(err.to_string()),
status: Some(Code::Internal as i32),
event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
},
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
return;
}
};
app_handle app_handle
.db() .db()
.upsert_grpc_event( .upsert_grpc_event(
&GrpcEvent { &GrpcEvent {
content: serialize_message(&msg.into_inner()).unwrap(), content,
event_type: GrpcEventType::ServerMessage, event_type: GrpcEventType::ServerMessage,
..base_event.clone() ..base_event.clone()
}, },
@@ -728,7 +753,28 @@ async fn cmd_grpc_go<R: Runtime>(
loop { loop {
match stream.message().await { match stream.message().await {
Ok(Some(msg)) => { Ok(Some(msg)) => {
let message = serialize_message(&msg).unwrap(); let message = match connection
.serialize_message(&msg, &metadata, client_cert.clone())
.await
{
Ok(message) => message,
Err(err) => {
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: "Failed to read response".to_string(),
error: Some(err.to_string()),
status: Some(Code::Internal as i32),
event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
},
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
break;
}
};
app_handle app_handle
.db() .db()
.upsert_grpc_event( .upsert_grpc_event(
@@ -1383,13 +1429,12 @@ async fn cmd_reload_plugins<R: Runtime>(
app_handle: AppHandle<R>, app_handle: AppHandle<R>,
window: WebviewWindow<R>, window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>, plugin_manager: State<'_, PluginManager>,
) -> YaakResult<()> { ) -> YaakResult<Vec<(String, String)>> {
let plugins = app_handle.db().list_plugins()?; let plugins = app_handle.db().list_plugins()?;
let plugin_context = let plugin_context =
PluginContext::new(Some(window.label().to_string()), window.workspace_id()); PluginContext::new(Some(window.label().to_string()), window.workspace_id());
let _errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await; let errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
// Note: errors are returned but we don't show toasts here since this is a manual reload Ok(errors)
Ok(())
} }
#[tauri::command] #[tauri::command]
@@ -1731,6 +1776,7 @@ pub fn run() {
git_ext::cmd_git_rm_remote, git_ext::cmd_git_rm_remote,
// //
// Plugin commands // Plugin commands
plugins_ext::cmd_plugin_init_errors,
plugins_ext::cmd_plugins_install_from_directory, plugins_ext::cmd_plugins_install_from_directory,
plugins_ext::cmd_plugins_search, plugins_ext::cmd_plugins_search,
plugins_ext::cmd_plugins_install, plugins_ext::cmd_plugins_install,

View File

@@ -198,6 +198,13 @@ pub async fn cmd_plugins_uninstall<R: Runtime>(
Ok(delete_and_uninstall(plugin_manager, &query_manager, &plugin_context, plugin_id).await?) Ok(delete_and_uninstall(plugin_manager, &query_manager, &plugin_context, plugin_id).await?)
} }
#[command]
pub async fn cmd_plugin_init_errors(
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<(String, String)>> {
Ok(plugin_manager.take_init_errors().await)
}
#[command] #[command]
pub async fn cmd_plugins_updates<R: Runtime>( pub async fn cmd_plugins_updates<R: Runtime>(
app_handle: AppHandle<R>, app_handle: AppHandle<R>,
@@ -306,7 +313,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
dev_mode, dev_mode,
) )
.await .await
.expect("Failed to initialize plugins"); .expect("Failed to start plugin runtime");
app_handle_clone.manage(manager); app_handle_clone.manage(manager);
}); });

View File

@@ -14,10 +14,7 @@
"assetProtocol": { "assetProtocol": {
"enable": true, "enable": true,
"scope": { "scope": {
"allow": [ "allow": ["$APPDATA/responses/*", "$RESOURCE/static/*"]
"$APPDATA/responses/*",
"$RESOURCE/static/*"
]
} }
} }
} }
@@ -25,9 +22,7 @@
"plugins": { "plugins": {
"deep-link": { "deep-link": {
"desktop": { "desktop": {
"schemes": [ "schemes": ["yaak"]
"yaak"
]
} }
} }
}, },

View File

@@ -16,9 +16,7 @@
}, },
"plugins": { "plugins": {
"updater": { "updater": {
"endpoints": [ "endpoints": ["https://update.yaak.app/check/{{target}}/{{arch}}/{{current_version}}"],
"https://update.yaak.app/check/{{target}}/{{arch}}/{{current_version}}"
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEVGRkFGMjQxRUNEOTQ3MzAKUldRd1I5bnNRZkw2NzRtMnRlWTN3R24xYUR3aGRsUjJzWGwvdHdEcGljb3ZJMUNlMjFsaHlqVU4K" "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEVGRkFGMjQxRUNEOTQ3MzAKUldRd1I5bnNRZkw2NzRtMnRlWTN3R24xYUR3aGRsUjJzWGwvdHdEcGljb3ZJMUNlMjFsaHlqVU4K"
} }
}, },

View File

@@ -1,14 +1,14 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from "@tanstack/react-query";
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { Fonts } from './bindings/gen_fonts'; import { Fonts } from "./bindings/gen_fonts";
export async function listFonts() { export async function listFonts() {
return invoke<Fonts>('plugin:yaak-fonts|list', {}); return invoke<Fonts>("plugin:yaak-fonts|list", {});
} }
export function useFonts() { export function useFonts() {
return useQuery({ return useQuery({
queryKey: ['list_fonts'], queryKey: ["list_fonts"],
queryFn: () => listFonts(), queryFn: () => listFonts(),
}); });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/fonts", "name": "@yaakapp-internal/fonts",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -1,35 +1,35 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { listen } from '@tauri-apps/api/event'; import { listen } from "@tauri-apps/api/event";
import { appInfo } from '@yaakapp/app/lib/appInfo'; import { appInfo } from "@yaakapp/app/lib/appInfo";
import { useEffect } from 'react'; import { useEffect } from "react";
import { LicenseCheckStatus } from './bindings/license'; import { LicenseCheckStatus } from "./bindings/license";
export * from './bindings/license'; export * from "./bindings/license";
const CHECK_QUERY_KEY = ['license.check']; const CHECK_QUERY_KEY = ["license.check"];
export function useLicense() { export function useLicense() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const activate = useMutation<void, string, { licenseKey: string }>({ const activate = useMutation<void, string, { licenseKey: string }>({
mutationKey: ['license.activate'], mutationKey: ["license.activate"],
mutationFn: (payload) => invoke('plugin:yaak-license|activate', payload), mutationFn: (payload) => invoke("plugin:yaak-license|activate", payload),
onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }), onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }),
}); });
const deactivate = useMutation<void, string, void>({ const deactivate = useMutation<void, string, void>({
mutationKey: ['license.deactivate'], mutationKey: ["license.deactivate"],
mutationFn: () => invoke('plugin:yaak-license|deactivate'), mutationFn: () => invoke("plugin:yaak-license|deactivate"),
onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }), onSuccess: () => queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }),
}); });
// Check the license again after a license is activated // Check the license again after a license is activated
useEffect(() => { useEffect(() => {
const unlisten = listen('license-activated', async () => { const unlisten = listen("license-activated", async () => {
await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY }); await queryClient.invalidateQueries({ queryKey: CHECK_QUERY_KEY });
}); });
return () => { return () => {
unlisten.then((fn) => fn()); void unlisten.then((fn) => fn());
}; };
}, []); }, []);
@@ -41,7 +41,7 @@ export function useLicense() {
if (!appInfo.featureLicense) { if (!appInfo.featureLicense) {
return null; return null;
} }
return invoke<LicenseCheckStatus>('plugin:yaak-license|check'); return invoke<LicenseCheckStatus>("plugin:yaak-license|check");
}, },
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/license", "name": "@yaakapp-internal/license",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -1,9 +1,9 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
export function setWindowTitle(title: string) { export function setWindowTitle(title: string) {
invoke('plugin:yaak-mac-window|set_title', { title }).catch(console.error); invoke("plugin:yaak-mac-window|set_title", { title }).catch(console.error);
} }
export function setWindowTheme(bgColor: string) { export function setWindowTheme(bgColor: string) {
invoke('plugin:yaak-mac-window|set_theme', { bgColor }).catch(console.error); invoke("plugin:yaak-mac-window|set_theme", { bgColor }).catch(console.error);
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/mac-window", "name": "@yaakapp-internal/mac-window",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -1,6 +1,3 @@
[default] [default]
description = "Default permissions for the plugin" description = "Default permissions for the plugin"
permissions = [ permissions = ["allow-set-title", "allow-set-theme"]
"allow-set-title",
"allow-set-theme",
]

View File

@@ -25,6 +25,7 @@ pub(crate) struct PluginState {
} }
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
#[cfg_attr(not(target_os = "macos"), allow(unused_mut))]
let mut builder = plugin::Builder::new("yaak-mac-window") let mut builder = plugin::Builder::new("yaak-mac-window")
.setup(move |app, _| { .setup(move |app, _| {
app.manage(PluginState { native_titlebar: AtomicBool::new(false) }); app.manage(PluginState { native_titlebar: AtomicBool::new(false) });

View File

@@ -1,17 +1,17 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
export function enableEncryption(workspaceId: string) { export function enableEncryption(workspaceId: string) {
return invoke<void>('cmd_enable_encryption', { workspaceId }); return invoke<void>("cmd_enable_encryption", { workspaceId });
} }
export function revealWorkspaceKey(workspaceId: string) { export function revealWorkspaceKey(workspaceId: string) {
return invoke<string>('cmd_reveal_workspace_key', { workspaceId }); return invoke<string>("cmd_reveal_workspace_key", { workspaceId });
} }
export function setWorkspaceKey(args: { workspaceId: string; key: string }) { export function setWorkspaceKey(args: { workspaceId: string; key: string }) {
return invoke<void>('cmd_set_workspace_key', args); return invoke<void>("cmd_set_workspace_key", args);
} }
export function disableEncryption(workspaceId: string) { export function disableEncryption(workspaceId: string) {
return invoke<void>('cmd_disable_encryption', { workspaceId }); return invoke<void>("cmd_disable_encryption", { workspaceId });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/crypto", "name": "@yaakapp-internal/crypto",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -1,60 +1,66 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from "@tanstack/react-query";
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { createFastMutation } from '@yaakapp/app/hooks/useFastMutation'; import { createFastMutation } from "@yaakapp/app/hooks/useFastMutation";
import { queryClient } from '@yaakapp/app/lib/queryClient'; import { queryClient } from "@yaakapp/app/lib/queryClient";
import { useMemo } from 'react'; import { useMemo } from "react";
import { BranchDeleteResult, CloneResult, GitCommit, GitRemote, GitStatusSummary, PullResult, PushResult } from './bindings/gen_git'; import {
import { showToast } from '@yaakapp/app/lib/toast'; BranchDeleteResult,
CloneResult,
GitCommit,
GitRemote,
GitStatusSummary,
PullResult,
PushResult,
} from "./bindings/gen_git";
import { showToast } from "@yaakapp/app/lib/toast";
export * from './bindings/gen_git'; export * from "./bindings/gen_git";
export * from './bindings/gen_models'; export * from "./bindings/gen_models";
export interface GitCredentials { export interface GitCredentials {
username: string; username: string;
password: string; password: string;
} }
export type DivergedStrategy = 'force_reset' | 'merge' | 'cancel'; export type DivergedStrategy = "force_reset" | "merge" | "cancel";
export type UncommittedChangesStrategy = 'reset' | 'cancel'; export type UncommittedChangesStrategy = "reset" | "cancel";
export interface GitCallbacks { export interface GitCallbacks {
addRemote: () => Promise<GitRemote | null>; addRemote: () => Promise<GitRemote | null>;
promptCredentials: ( promptCredentials: (
result: Extract<PushResult, { type: 'needs_credentials' }>, result: Extract<PushResult, { type: "needs_credentials" }>,
) => Promise<GitCredentials | null>; ) => Promise<GitCredentials | null>;
promptDiverged: ( promptDiverged: (result: Extract<PullResult, { type: "diverged" }>) => Promise<DivergedStrategy>;
result: Extract<PullResult, { type: 'diverged' }>,
) => Promise<DivergedStrategy>;
promptUncommittedChanges: () => Promise<UncommittedChangesStrategy>; promptUncommittedChanges: () => Promise<UncommittedChangesStrategy>;
forceSync: () => Promise<void>; forceSync: () => Promise<void>;
} }
const onSuccess = () => queryClient.invalidateQueries({ queryKey: ['git'] }); const onSuccess = () => queryClient.invalidateQueries({ queryKey: ["git"] });
export function useGit(dir: string, callbacks: GitCallbacks, refreshKey?: string) { export function useGit(dir: string, callbacks: GitCallbacks, refreshKey?: string) {
const mutations = useMemo(() => gitMutations(dir, callbacks), [dir, callbacks]); const mutations = useMemo(() => gitMutations(dir, callbacks), [dir, callbacks]);
const fetchAll = useQuery<void, string>({ const fetchAll = useQuery<void, string>({
queryKey: ['git', 'fetch_all', dir, refreshKey], queryKey: ["git", "fetch_all", dir, refreshKey],
queryFn: () => invoke('cmd_git_fetch_all', { dir }), queryFn: () => invoke("cmd_git_fetch_all", { dir }),
refetchInterval: 10 * 60_000, refetchInterval: 10 * 60_000,
}); });
return [ return [
{ {
remotes: useQuery<GitRemote[], string>({ remotes: useQuery<GitRemote[], string>({
queryKey: ['git', 'remotes', dir, refreshKey], queryKey: ["git", "remotes", dir, refreshKey],
queryFn: () => getRemotes(dir), queryFn: () => getRemotes(dir),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}), }),
log: useQuery<GitCommit[], string>({ log: useQuery<GitCommit[], string>({
queryKey: ['git', 'log', dir, refreshKey], queryKey: ["git", "log", dir, refreshKey],
queryFn: () => invoke('cmd_git_log', { dir }), queryFn: () => invoke("cmd_git_log", { dir }),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}), }),
status: useQuery<GitStatusSummary, string>({ status: useQuery<GitStatusSummary, string>({
refetchOnMount: true, refetchOnMount: true,
queryKey: ['git', 'status', dir, refreshKey, fetchAll.dataUpdatedAt], queryKey: ["git", "status", dir, refreshKey, fetchAll.dataUpdatedAt],
queryFn: () => invoke('cmd_git_status', { dir }), queryFn: () => invoke("cmd_git_status", { dir }),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
}), }),
}, },
@@ -67,151 +73,167 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
const remotes = await getRemotes(dir); const remotes = await getRemotes(dir);
if (remotes.length === 0) { if (remotes.length === 0) {
const remote = await callbacks.addRemote(); const remote = await callbacks.addRemote();
if (remote == null) throw new Error('No remote found'); if (remote == null) throw new Error("No remote found");
} }
const result = await invoke<PushResult>('cmd_git_push', { dir }); const result = await invoke<PushResult>("cmd_git_push", { dir });
if (result.type !== 'needs_credentials') return result; if (result.type !== "needs_credentials") return result;
// Needs credentials, prompt for them // Needs credentials, prompt for them
const creds = await callbacks.promptCredentials(result); const creds = await callbacks.promptCredentials(result);
if (creds == null) throw new Error('Canceled'); if (creds == null) throw new Error("Canceled");
await invoke('cmd_git_add_credential', { await invoke("cmd_git_add_credential", {
remoteUrl: result.url, remoteUrl: result.url,
username: creds.username, username: creds.username,
password: creds.password, password: creds.password,
}); });
// Push again // Push again
return invoke<PushResult>('cmd_git_push', { dir }); return invoke<PushResult>("cmd_git_push", { dir });
}; };
const handleError = (err: unknown) => { const handleError = (err: unknown) => {
showToast({ showToast({
id: `${err}`, id: err instanceof Error ? err.message : String(err),
message: `${err}`, message: err instanceof Error ? err.message : String(err),
color: 'danger', color: "danger",
timeout: 5000, timeout: 5000,
}); });
} };
return { return {
init: createFastMutation<void, string, void>({ init: createFastMutation<void, string, void>({
mutationKey: ['git', 'init'], mutationKey: ["git", "init"],
mutationFn: () => invoke('cmd_git_initialize', { dir }), mutationFn: () => invoke("cmd_git_initialize", { dir }),
onSuccess, onSuccess,
}), }),
add: createFastMutation<void, string, { relaPaths: string[] }>({ add: createFastMutation<void, string, { relaPaths: string[] }>({
mutationKey: ['git', 'add', dir], mutationKey: ["git", "add", dir],
mutationFn: (args) => invoke('cmd_git_add', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_add", { dir, ...args }),
onSuccess, onSuccess,
}), }),
addRemote: createFastMutation<GitRemote, string, GitRemote>({ addRemote: createFastMutation<GitRemote, string, GitRemote>({
mutationKey: ['git', 'add-remote'], mutationKey: ["git", "add-remote"],
mutationFn: (args) => invoke('cmd_git_add_remote', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_add_remote", { dir, ...args }),
onSuccess, onSuccess,
}), }),
rmRemote: createFastMutation<void, string, { name: string }>({ rmRemote: createFastMutation<void, string, { name: string }>({
mutationKey: ['git', 'rm-remote', dir], mutationKey: ["git", "rm-remote", dir],
mutationFn: (args) => invoke('cmd_git_rm_remote', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_rm_remote", { dir, ...args }),
onSuccess, onSuccess,
}), }),
createBranch: createFastMutation<void, string, { branch: string; base?: string }>({ createBranch: createFastMutation<void, string, { branch: string; base?: string }>({
mutationKey: ['git', 'branch', dir], mutationKey: ["git", "branch", dir],
mutationFn: (args) => invoke('cmd_git_branch', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
mergeBranch: createFastMutation<void, string, { branch: string }>({ mergeBranch: createFastMutation<void, string, { branch: string }>({
mutationKey: ['git', 'merge', dir], mutationKey: ["git", "merge", dir],
mutationFn: (args) => invoke('cmd_git_merge_branch', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_merge_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
deleteBranch: createFastMutation<BranchDeleteResult, string, { branch: string, force?: boolean }>({ deleteBranch: createFastMutation<
mutationKey: ['git', 'delete-branch', dir], BranchDeleteResult,
mutationFn: (args) => invoke('cmd_git_delete_branch', { dir, ...args }), string,
{ branch: string; force?: boolean }
>({
mutationKey: ["git", "delete-branch", dir],
mutationFn: (args) => invoke("cmd_git_delete_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
deleteRemoteBranch: createFastMutation<void, string, { branch: string }>({ deleteRemoteBranch: createFastMutation<void, string, { branch: string }>({
mutationKey: ['git', 'delete-remote-branch', dir], mutationKey: ["git", "delete-remote-branch", dir],
mutationFn: (args) => invoke('cmd_git_delete_remote_branch', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_delete_remote_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
renameBranch: createFastMutation<void, string, { oldName: string, newName: string }>({ renameBranch: createFastMutation<void, string, { oldName: string; newName: string }>({
mutationKey: ['git', 'rename-branch', dir], mutationKey: ["git", "rename-branch", dir],
mutationFn: (args) => invoke('cmd_git_rename_branch', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_rename_branch", { dir, ...args }),
onSuccess, onSuccess,
}), }),
checkout: createFastMutation<string, string, { branch: string; force: boolean }>({ checkout: createFastMutation<string, string, { branch: string; force: boolean }>({
mutationKey: ['git', 'checkout', dir], mutationKey: ["git", "checkout", dir],
mutationFn: (args) => invoke('cmd_git_checkout', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_checkout", { dir, ...args }),
onSuccess, onSuccess,
}), }),
commit: createFastMutation<void, string, { message: string }>({ commit: createFastMutation<void, string, { message: string }>({
mutationKey: ['git', 'commit', dir], mutationKey: ["git", "commit", dir],
mutationFn: (args) => invoke('cmd_git_commit', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_commit", { dir, ...args }),
onSuccess, onSuccess,
}), }),
commitAndPush: createFastMutation<PushResult, string, { message: string }>({ commitAndPush: createFastMutation<PushResult, string, { message: string }>({
mutationKey: ['git', 'commit_push', dir], mutationKey: ["git", "commit_push", dir],
mutationFn: async (args) => { mutationFn: async (args) => {
await invoke('cmd_git_commit', { dir, ...args }); await invoke("cmd_git_commit", { dir, ...args });
return push(); return push();
}, },
onSuccess, onSuccess,
}), }),
push: createFastMutation<PushResult, string, void>({ push: createFastMutation<PushResult, string, void>({
mutationKey: ['git', 'push', dir], mutationKey: ["git", "push", dir],
mutationFn: push, mutationFn: push,
onSuccess, onSuccess,
}), }),
pull: createFastMutation<PullResult, string, void>({ pull: createFastMutation<PullResult, string, void>({
mutationKey: ['git', 'pull', dir], mutationKey: ["git", "pull", dir],
async mutationFn() { async mutationFn() {
const result = await invoke<PullResult>('cmd_git_pull', { dir }); const result = await invoke<PullResult>("cmd_git_pull", { dir });
if (result.type === 'needs_credentials') { if (result.type === "needs_credentials") {
const creds = await callbacks.promptCredentials(result); const creds = await callbacks.promptCredentials(result);
if (creds == null) throw new Error('Canceled'); if (creds == null) throw new Error("Canceled");
await invoke('cmd_git_add_credential', { await invoke("cmd_git_add_credential", {
remoteUrl: result.url, remoteUrl: result.url,
username: creds.username, username: creds.username,
password: creds.password, password: creds.password,
}); });
// Pull again after credentials // Pull again after credentials
return invoke<PullResult>('cmd_git_pull', { dir }); return invoke<PullResult>("cmd_git_pull", { dir });
} }
if (result.type === 'uncommitted_changes') { if (result.type === "uncommitted_changes") {
callbacks.promptUncommittedChanges().then(async (strategy) => { void callbacks
if (strategy === 'cancel') return; .promptUncommittedChanges()
.then(async (strategy) => {
if (strategy === "cancel") return;
await invoke('cmd_git_reset_changes', { dir }); await invoke("cmd_git_reset_changes", { dir });
return invoke<PullResult>('cmd_git_pull', { dir }); return invoke<PullResult>("cmd_git_pull", { dir });
}).then(async () => { onSuccess(); await callbacks.forceSync(); }, handleError); })
.then(async () => {
await onSuccess();
await callbacks.forceSync();
}, handleError);
} }
if (result.type === 'diverged') { if (result.type === "diverged") {
callbacks.promptDiverged(result).then((strategy) => { void callbacks
if (strategy === 'cancel') return; .promptDiverged(result)
.then((strategy) => {
if (strategy === "cancel") return;
if (strategy === 'force_reset') { if (strategy === "force_reset") {
return invoke<PullResult>('cmd_git_pull_force_reset', { return invoke<PullResult>("cmd_git_pull_force_reset", {
dir,
remote: result.remote,
branch: result.branch,
});
}
return invoke<PullResult>("cmd_git_pull_merge", {
dir, dir,
remote: result.remote, remote: result.remote,
branch: result.branch, branch: result.branch,
}); });
} })
.then(async () => {
return invoke<PullResult>('cmd_git_pull_merge', { await onSuccess();
dir, await callbacks.forceSync();
remote: result.remote, }, handleError);
branch: result.branch,
});
}).then(async () => { onSuccess(); await callbacks.forceSync(); }, handleError);
} }
return result; return result;
@@ -219,20 +241,20 @@ export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
onSuccess, onSuccess,
}), }),
unstage: createFastMutation<void, string, { relaPaths: string[] }>({ unstage: createFastMutation<void, string, { relaPaths: string[] }>({
mutationKey: ['git', 'unstage', dir], mutationKey: ["git", "unstage", dir],
mutationFn: (args) => invoke('cmd_git_unstage', { dir, ...args }), mutationFn: (args) => invoke("cmd_git_unstage", { dir, ...args }),
onSuccess, onSuccess,
}), }),
resetChanges: createFastMutation<void, string, void>({ resetChanges: createFastMutation<void, string, void>({
mutationKey: ['git', 'reset-changes', dir], mutationKey: ["git", "reset-changes", dir],
mutationFn: () => invoke('cmd_git_reset_changes', { dir }), mutationFn: () => invoke("cmd_git_reset_changes", { dir }),
onSuccess, onSuccess,
}), }),
} as const; } as const;
}; };
async function getRemotes(dir: string) { async function getRemotes(dir: string) {
return invoke<GitRemote[]>('cmd_git_remotes', { dir }); return invoke<GitRemote[]>("cmd_git_remotes", { dir });
} }
/** /**
@@ -241,21 +263,24 @@ async function getRemotes(dir: string) {
export async function gitClone( export async function gitClone(
url: string, url: string,
dir: string, dir: string,
promptCredentials: (args: { url: string; error: string | null }) => Promise<GitCredentials | null>, promptCredentials: (args: {
url: string;
error: string | null;
}) => Promise<GitCredentials | null>,
): Promise<CloneResult> { ): Promise<CloneResult> {
const result = await invoke<CloneResult>('cmd_git_clone', { url, dir }); const result = await invoke<CloneResult>("cmd_git_clone", { url, dir });
if (result.type !== 'needs_credentials') return result; if (result.type !== "needs_credentials") return result;
// Prompt for credentials // Prompt for credentials
const creds = await promptCredentials({ url: result.url, error: result.error }); const creds = await promptCredentials({ url: result.url, error: result.error });
if (creds == null) return {type: 'cancelled'}; if (creds == null) return { type: "cancelled" };
// Store credentials and retry // Store credentials and retry
await invoke('cmd_git_add_credential', { await invoke("cmd_git_add_credential", {
remoteUrl: result.url, remoteUrl: result.url,
username: creds.username, username: creds.username,
password: creds.password, password: creds.password,
}); });
return invoke<CloneResult>('cmd_git_clone', { url, dir }); return invoke<CloneResult>("cmd_git_clone", { url, dir });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/git", "name": "@yaakapp-internal/git",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -37,7 +37,7 @@ pub struct MethodDefinition {
static SERIALIZE_OPTIONS: &'static SerializeOptions = static SERIALIZE_OPTIONS: &'static SerializeOptions =
&SerializeOptions::new().skip_default_fields(false).stringify_64_bit_integers(false); &SerializeOptions::new().skip_default_fields(false).stringify_64_bit_integers(false);
pub fn serialize_message(msg: &DynamicMessage) -> Result<String, String> { pub(crate) fn serialize_dynamic_message_json(msg: &DynamicMessage) -> Result<String, String> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut se = serde_json::Serializer::pretty(&mut buf); let mut se = serde_json::Serializer::pretty(&mut buf);
msg.serialize_with_options(&mut se, SERIALIZE_OPTIONS).map_err(|e| e.to_string())?; msg.serialize_with_options(&mut se, SERIALIZE_OPTIONS).map_err(|e| e.to_string())?;

View File

@@ -2,7 +2,8 @@ use crate::codec::DynamicCodec;
use crate::error::Error::GenericError; use crate::error::Error::GenericError;
use crate::error::Result; use crate::error::Result;
use crate::reflection::{ use crate::reflection::{
fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path, reflect_types_for_message, fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path,
reflect_types_for_dynamic_message, reflect_types_for_message,
}; };
use crate::transport::get_transport; use crate::transport::get_transport;
use crate::{MethodDefinition, ServiceDefinition, json_schema}; use crate::{MethodDefinition, ServiceDefinition, json_schema};
@@ -11,8 +12,11 @@ use hyper_util::client::legacy::Client;
use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::connect::HttpConnector;
use log::{info, warn}; use log::{info, warn};
pub use prost_reflect::DynamicMessage; pub use prost_reflect::DynamicMessage;
use prost_reflect::ReflectMessage;
use prost_reflect::prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor}; use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor};
use serde_json::Deserializer; use serde_json::Deserializer;
use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
@@ -115,6 +119,38 @@ impl GrpcConnection {
Ok(client.unary(req, path, codec).await?) Ok(client.unary(req, path, codec).await?)
} }
pub async fn serialize_message(
&self,
message: &DynamicMessage,
metadata: &BTreeMap<String, String>,
client_cert: Option<ClientCertificateConfig>,
) -> Result<String> {
let message = if self.use_reflection {
reflect_types_for_dynamic_message(
self.pool.clone(),
&self.uri,
message,
metadata,
client_cert,
)
.await?;
let message_name = message.descriptor().full_name().to_string();
let message_desc = {
let pool = self.pool.read().await;
pool.get_message_by_name(&message_name)
.ok_or(GenericError(format!("Failed to find message {message_name}")))?
};
let mut message_with_updated_pool = DynamicMessage::new(message_desc);
message_with_updated_pool.merge(message.encode_to_vec().as_slice())?;
Cow::Owned(message_with_updated_pool)
} else {
Cow::Borrowed(message)
};
crate::serialize_dynamic_message_json(message.as_ref()).map_err(GenericError)
}
pub async fn streaming<F>( pub async fn streaming<F>(
&self, &self,
service: &str, service: &str,

View File

@@ -7,7 +7,7 @@ use anyhow::anyhow;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use log::{debug, info, warn}; use log::{debug, info, warn};
use prost::Message; use prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor}; use prost_reflect::{DescriptorPool, DynamicMessage, MethodDescriptor, ReflectMessage, Value};
use prost_types::{FileDescriptorProto, FileDescriptorSet}; use prost_types::{FileDescriptorProto, FileDescriptorSet};
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::env::temp_dir; use std::env::temp_dir;
@@ -233,6 +233,83 @@ pub(crate) async fn reflect_types_for_message(
Ok(()) Ok(())
} }
pub(crate) async fn reflect_types_for_dynamic_message(
pool: Arc<RwLock<DescriptorPool>>,
uri: &Uri,
message: &DynamicMessage,
metadata: &BTreeMap<String, String>,
client_cert: Option<ClientCertificateConfig>,
) -> Result<()> {
let mut extra_types = HashSet::new();
collect_any_types_from_dynamic_message(message, &mut extra_types);
if extra_types.is_empty() {
return Ok(());
}
let mut client = AutoReflectionClient::new(uri, false, client_cert)?;
for extra_type in extra_types {
{
let guard = pool.read().await;
if guard.get_message_by_name(&extra_type).is_some() {
continue;
}
}
info!("Adding response file descriptor for {:?} from reflection", extra_type);
let req = MessageRequest::FileContainingSymbol(extra_type.clone().into());
let resp = match client.send_reflection_request(req, metadata).await {
Ok(r) => r,
Err(e) => {
return Err(GenericError(format!(
"Error sending reflection request for response @type \"{extra_type}\": {e:?}",
)));
}
};
let files = match resp {
MessageResponse::FileDescriptorResponse(resp) => resp.file_descriptor_proto,
_ => panic!("Expected a FileDescriptorResponse variant"),
};
{
let mut guard = pool.write().await;
add_file_descriptors_to_pool(files, &mut *guard, &mut client, metadata).await;
}
}
Ok(())
}
fn collect_any_types_from_dynamic_message(message: &DynamicMessage, out: &mut HashSet<String>) {
if message.descriptor().full_name() == "google.protobuf.Any" {
if let Some(Value::String(type_url)) = message.get_field_by_name("type_url").as_deref() {
if let Some(full_name) = type_url.rsplit_once('/').map(|(_, name)| name) {
out.insert(full_name.to_string());
}
}
}
for (_, value) in message.fields() {
collect_any_types_from_value(value, out);
}
}
fn collect_any_types_from_value(value: &Value, out: &mut HashSet<String>) {
match value {
Value::Message(message) => collect_any_types_from_dynamic_message(message, out),
Value::List(values) => {
for value in values {
collect_any_types_from_value(value, out);
}
}
Value::Map(values) => {
for value in values.values() {
collect_any_types_from_value(value, out);
}
}
_ => {}
}
}
#[async_recursion] #[async_recursion]
pub(crate) async fn add_file_descriptors_to_pool( pub(crate) async fn add_file_descriptors_to_pool(
fds: Vec<Vec<u8>>, fds: Vec<Vec<u8>>,

View File

@@ -19,7 +19,12 @@ hyper-util = { version = "0.1.17", default-features = false, features = ["client
log = { workspace = true } log = { workspace = true }
mime_guess = "2.0.5" mime_guess = "2.0.5"
regex = "1.11.1" regex = "1.11.1"
reqwest = { workspace = true, features = ["rustls-tls-manual-roots-no-provider", "socks", "http2", "stream"] } reqwest = { workspace = true, features = [
"rustls-tls-manual-roots-no-provider",
"socks",
"http2",
"stream",
] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }

View File

@@ -1,11 +1,36 @@
use crate::dns::LocalhostResolver; use crate::dns::LocalhostResolver;
use crate::error::Result; use crate::error::Result;
use log::{debug, info, warn}; use log::{debug, info, warn};
use reqwest::{Client, Proxy, redirect}; use reqwest::{Client, ClientBuilder, Proxy, redirect};
use std::sync::Arc; use std::sync::Arc;
use yaak_models::models::DnsOverride; use yaak_models::models::DnsOverride;
use yaak_tls::{ClientCertificateConfig, get_tls_config}; use yaak_tls::{ClientCertificateConfig, get_tls_config};
pub const HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE: u32 = 1024 * 1024;
fn client_builder() -> ClientBuilder {
Client::builder().http2_max_header_list_size(HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE)
}
#[derive(Clone)]
pub struct ConfiguredClient {
inner: Client,
}
impl ConfiguredClient {
pub(crate) fn build_default() -> Result<Self> {
Ok(Self { inner: client_builder().build()? })
}
pub(crate) fn from_inner(inner: Client) -> Self {
Self { inner }
}
pub(crate) fn inner(&self) -> &Client {
&self.inner
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct HttpConnectionProxySettingAuth { pub struct HttpConnectionProxySettingAuth {
pub user: String, pub user: String,
@@ -37,8 +62,8 @@ impl HttpConnectionOptions {
/// Build a reqwest Client and return it along with the DNS resolver. /// Build a reqwest Client and return it along with the DNS resolver.
/// The resolver is returned separately so it can be configured per-request /// The resolver is returned separately so it can be configured per-request
/// to emit DNS timing events to the appropriate channel. /// to emit DNS timing events to the appropriate channel.
pub(crate) fn build_client(&self) -> Result<(Client, Arc<LocalhostResolver>)> { pub(crate) fn build_client(&self) -> Result<(ConfiguredClient, Arc<LocalhostResolver>)> {
let mut client = Client::builder() let mut client = client_builder()
.connection_verbose(true) .connection_verbose(true)
.redirect(redirect::Policy::none()) .redirect(redirect::Policy::none())
// Decompression is handled by HttpTransaction, not reqwest // Decompression is handled by HttpTransaction, not reqwest
@@ -79,7 +104,7 @@ impl HttpConnectionOptions {
self.client_certificate.is_some() self.client_certificate.is_some()
); );
Ok((client.build()?, resolver)) Ok((ConfiguredClient::from_inner(client.build()?), resolver))
} }
} }

View File

@@ -1,7 +1,6 @@
use crate::client::HttpConnectionOptions; use crate::client::{ConfiguredClient, HttpConnectionOptions};
use crate::dns::LocalhostResolver; use crate::dns::LocalhostResolver;
use crate::error::Result; use crate::error::Result;
use reqwest::Client;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -10,7 +9,7 @@ use tokio::sync::RwLock;
/// A cached HTTP client along with its DNS resolver. /// A cached HTTP client along with its DNS resolver.
/// The resolver is needed to set the event sender per-request. /// The resolver is needed to set the event sender per-request.
pub struct CachedClient { pub struct CachedClient {
pub client: Client, pub client: ConfiguredClient,
pub resolver: Arc<LocalhostResolver>, pub resolver: Arc<LocalhostResolver>,
} }

View File

@@ -5,7 +5,7 @@ use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures_util::StreamExt; use futures_util::StreamExt;
use http_body::{Body as HttpBody, Frame, SizeHint}; use http_body::{Body as HttpBody, Frame, SizeHint};
use reqwest::{Client, Method, Version}; use reqwest::{Method, Version};
use std::fmt::Display; use std::fmt::Display;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@@ -411,18 +411,18 @@ pub trait HttpSender: Send + Sync {
/// Reqwest-based implementation of HttpSender /// Reqwest-based implementation of HttpSender
pub struct ReqwestSender { pub struct ReqwestSender {
client: Client, client: crate::client::ConfiguredClient,
} }
impl ReqwestSender { impl ReqwestSender {
/// Create a new ReqwestSender with a default client /// Create a new ReqwestSender with a default client
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let client = Client::builder().build().map_err(Error::Client)?; let client = crate::client::ConfiguredClient::build_default()?;
Ok(Self { client }) Ok(Self { client })
} }
/// Create a new ReqwestSender with a custom client /// Create a new ReqwestSender with a configured client
pub fn with_client(client: Client) -> Self { pub fn with_client(client: crate::client::ConfiguredClient) -> Self {
Self { client } Self { client }
} }
} }
@@ -444,7 +444,7 @@ impl HttpSender for ReqwestSender {
.map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?; .map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?;
// Build the request // Build the request
let mut req_builder = self.client.request(method, &request.url); let mut req_builder = self.client.inner().request(method, &request.url);
// Add headers // Add headers
for header in request.headers { for header in request.headers {
@@ -513,7 +513,7 @@ impl HttpSender for ReqwestSender {
send_event(HttpResponseEvent::Info("Sending request to server".to_string())); send_event(HttpResponseEvent::Info("Sending request to server".to_string()));
// Map some errors to our own, so they look nicer // Map some errors to our own, so they look nicer
let response = self.client.execute(sendable_req).await.map_err(|e| { let response = self.client.inner().execute(sendable_req).await.map_err(|e| {
if reqwest::Error::is_timeout(&e) { if reqwest::Error::is_timeout(&e) {
Error::RequestTimeout( Error::RequestTimeout(
request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(), request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(),

View File

@@ -226,10 +226,8 @@ async fn build_body(
let (body, content_type) = match body_type.as_str() { let (body, content_type) = match body_type.as_str() {
"binary" => (build_binary_body(&body).await?, None), "binary" => (build_binary_body(&body).await?, None),
"graphql" => (build_graphql_body(&method, &body), Some("application/json".to_string())), "graphql" => (build_graphql_body(&method, &body), None),
"application/x-www-form-urlencoded" => { "application/x-www-form-urlencoded" => (build_form_body(&body), None),
(build_form_body(&body), Some("application/x-www-form-urlencoded".to_string()))
}
"multipart/form-data" => build_multipart_body(&body, &headers).await?, "multipart/form-data" => build_multipart_body(&body, &headers).await?,
_ if body.contains_key("text") => (build_text_body(&body, body_type), None), _ if body.contains_key("text") => (build_text_body(&body, body_type), None),
t => { t => {

View File

@@ -1,35 +1,39 @@
import { atom } from 'jotai'; import { atom } from "jotai";
import { selectAtom } from 'jotai/utils'; import { selectAtom } from "jotai/utils";
import type { AnyModel } from '../bindings/gen_models'; import type { AnyModel } from "../bindings/gen_models";
import { ExtractModel } from './types'; import { ExtractModel } from "./types";
import { newStoreData } from './util'; import { newStoreData } from "./util";
export const modelStoreDataAtom = atom(newStoreData()); export const modelStoreDataAtom = atom(newStoreData());
export const cookieJarsAtom = createOrderedModelAtom('cookie_jar', 'name', 'asc'); export const cookieJarsAtom = createOrderedModelAtom("cookie_jar", "name", "asc");
export const environmentsAtom = createOrderedModelAtom('environment', 'sortPriority', 'asc'); export const environmentsAtom = createOrderedModelAtom("environment", "sortPriority", "asc");
export const foldersAtom = createModelAtom('folder'); export const foldersAtom = createModelAtom("folder");
export const grpcConnectionsAtom = createOrderedModelAtom('grpc_connection', 'createdAt', 'desc'); export const grpcConnectionsAtom = createOrderedModelAtom("grpc_connection", "createdAt", "desc");
export const grpcEventsAtom = createOrderedModelAtom('grpc_event', 'createdAt', 'asc'); export const grpcEventsAtom = createOrderedModelAtom("grpc_event", "createdAt", "asc");
export const grpcRequestsAtom = createModelAtom('grpc_request'); export const grpcRequestsAtom = createModelAtom("grpc_request");
export const httpRequestsAtom = createModelAtom('http_request'); export const httpRequestsAtom = createModelAtom("http_request");
export const httpResponsesAtom = createOrderedModelAtom('http_response', 'createdAt', 'desc'); export const httpResponsesAtom = createOrderedModelAtom("http_response", "createdAt", "desc");
export const httpResponseEventsAtom = createOrderedModelAtom('http_response_event', 'createdAt', 'asc'); export const httpResponseEventsAtom = createOrderedModelAtom(
export const keyValuesAtom = createModelAtom('key_value'); "http_response_event",
export const pluginsAtom = createModelAtom('plugin'); "createdAt",
export const settingsAtom = createSingularModelAtom('settings'); "asc",
export const websocketRequestsAtom = createModelAtom('websocket_request');
export const websocketEventsAtom = createOrderedModelAtom('websocket_event', 'createdAt', 'asc');
export const websocketConnectionsAtom = createOrderedModelAtom(
'websocket_connection',
'createdAt',
'desc',
); );
export const workspaceMetasAtom = createModelAtom('workspace_meta'); export const keyValuesAtom = createModelAtom("key_value");
export const workspacesAtom = createOrderedModelAtom('workspace', 'name', 'asc'); export const pluginsAtom = createModelAtom("plugin");
export const settingsAtom = createSingularModelAtom("settings");
export const websocketRequestsAtom = createModelAtom("websocket_request");
export const websocketEventsAtom = createOrderedModelAtom("websocket_event", "createdAt", "asc");
export const websocketConnectionsAtom = createOrderedModelAtom(
"websocket_connection",
"createdAt",
"desc",
);
export const workspaceMetasAtom = createModelAtom("workspace_meta");
export const workspacesAtom = createOrderedModelAtom("workspace", "name", "asc");
export function createModelAtom<M extends AnyModel['model']>(modelType: M) { export function createModelAtom<M extends AnyModel["model"]>(modelType: M) {
return selectAtom( return selectAtom(
modelStoreDataAtom, modelStoreDataAtom,
(data) => Object.values(data[modelType] ?? {}), (data) => Object.values(data[modelType] ?? {}),
@@ -37,19 +41,19 @@ export function createModelAtom<M extends AnyModel['model']>(modelType: M) {
); );
} }
export function createSingularModelAtom<M extends AnyModel['model']>(modelType: M) { export function createSingularModelAtom<M extends AnyModel["model"]>(modelType: M) {
return selectAtom(modelStoreDataAtom, (data) => { return selectAtom(modelStoreDataAtom, (data) => {
const modelData = Object.values(data[modelType] ?? {}); const modelData = Object.values(data[modelType] ?? {});
const item = modelData[0]; const item = modelData[0];
if (item == null) throw new Error('Failed creating singular model with no data: ' + modelType); if (item == null) throw new Error("Failed creating singular model with no data: " + modelType);
return item; return item;
}); });
} }
export function createOrderedModelAtom<M extends AnyModel['model']>( export function createOrderedModelAtom<M extends AnyModel["model"]>(
modelType: M, modelType: M,
field: keyof ExtractModel<AnyModel, M>, field: keyof ExtractModel<AnyModel, M>,
order: 'asc' | 'desc', order: "asc" | "desc",
) { ) {
return selectAtom( return selectAtom(
modelStoreDataAtom, modelStoreDataAtom,
@@ -58,7 +62,7 @@ export function createOrderedModelAtom<M extends AnyModel['model']>(
return Object.values(modelData).sort( return Object.values(modelData).sort(
(a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => { (a: ExtractModel<AnyModel, M>, b: ExtractModel<AnyModel, M>) => {
const n = a[field] > b[field] ? 1 : -1; const n = a[field] > b[field] ? 1 : -1;
return order === 'desc' ? n * -1 : n; return order === "desc" ? n * -1 : n;
}, },
); );
}, },

View File

@@ -1,11 +1,11 @@
import { AnyModel } from '../bindings/gen_models'; import { AnyModel } from "../bindings/gen_models";
export * from '../bindings/gen_models'; export * from "../bindings/gen_models";
export * from '../bindings/gen_util'; export * from "../bindings/gen_util";
export * from './store'; export * from "./store";
export * from './atoms'; export * from "./atoms";
export function modelTypeLabel(m: AnyModel): string { export function modelTypeLabel(m: AnyModel): string {
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
return m.model.split('_').map(capitalize).join(' '); return m.model.split("_").map(capitalize).join(" ");
} }

View File

@@ -1,10 +1,10 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { resolvedModelName } from '@yaakapp/app/lib/resolvedModelName'; import { resolvedModelName } from "@yaakapp/app/lib/resolvedModelName";
import { AnyModel, ModelPayload } from '../bindings/gen_models'; import { AnyModel, ModelPayload } from "../bindings/gen_models";
import { modelStoreDataAtom } from './atoms'; import { modelStoreDataAtom } from "./atoms";
import { ExtractModel, JotaiStore, ModelStoreData } from './types'; import { ExtractModel, JotaiStore, ModelStoreData } from "./types";
import { newStoreData } from './util'; import { newStoreData } from "./util";
let _store: JotaiStore | null = null; let _store: JotaiStore | null = null;
@@ -12,11 +12,11 @@ export function initModelStore(store: JotaiStore) {
_store = store; _store = store;
getCurrentWebviewWindow() getCurrentWebviewWindow()
.listen<ModelPayload>('model_write', ({ payload }) => { .listen<ModelPayload>("model_write", ({ payload }) => {
if (shouldIgnoreModel(payload)) return; if (shouldIgnoreModel(payload)) return;
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => { mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
if (payload.change.type === 'upsert') { if (payload.change.type === "upsert") {
return { return {
...prev, ...prev,
[payload.model.model]: { [payload.model.model]: {
@@ -36,7 +36,7 @@ export function initModelStore(store: JotaiStore) {
function mustStore(): JotaiStore { function mustStore(): JotaiStore {
if (_store == null) { if (_store == null) {
throw new Error('Model store was not initialized'); throw new Error("Model store was not initialized");
} }
return _store; return _store;
@@ -45,8 +45,8 @@ function mustStore(): JotaiStore {
let _activeWorkspaceId: string | null = null; let _activeWorkspaceId: string | null = null;
export async function changeModelStoreWorkspace(workspaceId: string | null) { export async function changeModelStoreWorkspace(workspaceId: string | null) {
console.log('Syncing models with new workspace', workspaceId); console.log("Syncing models with new workspace", workspaceId);
const workspaceModelsStr = await invoke<string>('models_workspace_models', { const workspaceModelsStr = await invoke<string>("models_workspace_models", {
workspaceId, // NOTE: if no workspace id provided, it will just fetch global models workspaceId, // NOTE: if no workspace id provided, it will just fetch global models
}); });
const workspaceModels = JSON.parse(workspaceModelsStr) as AnyModel[]; const workspaceModels = JSON.parse(workspaceModelsStr) as AnyModel[];
@@ -57,12 +57,12 @@ export async function changeModelStoreWorkspace(workspaceId: string | null) {
mustStore().set(modelStoreDataAtom, data); mustStore().set(modelStoreDataAtom, data);
console.log('Synced model store with workspace', workspaceId, data); console.log("Synced model store with workspace", workspaceId, data);
_activeWorkspaceId = workspaceId; _activeWorkspaceId = workspaceId;
} }
export function listModels<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export function listModels<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
modelType: M | ReadonlyArray<M>, modelType: M | ReadonlyArray<M>,
): T[] { ): T[] {
let data = mustStore().get(modelStoreDataAtom); let data = mustStore().get(modelStoreDataAtom);
@@ -70,7 +70,7 @@ export function listModels<M extends AnyModel['model'], T extends ExtractModel<A
return types.flatMap((t) => Object.values(data[t]) as T[]); return types.flatMap((t) => Object.values(data[t]) as T[]);
} }
export function getModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export function getModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
modelType: M | ReadonlyArray<M>, modelType: M | ReadonlyArray<M>,
id: string, id: string,
): T | null { ): T | null {
@@ -83,18 +83,17 @@ export function getModel<M extends AnyModel['model'], T extends ExtractModel<Any
return null; return null;
} }
export function getAnyModel( export function getAnyModel(id: string): AnyModel | null {
id: string,
): AnyModel | null {
let data = mustStore().get(modelStoreDataAtom); let data = mustStore().get(modelStoreDataAtom);
for (const t of Object.keys(data)) { for (const t of Object.keys(data)) {
// oxlint-disable-next-line no-explicit-any
let v = (data as any)[t]?.[id]; let v = (data as any)[t]?.[id];
if (v?.model === t) return v; if (v?.model === t) return v;
} }
return null; return null;
} }
export function patchModelById<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export function patchModelById<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
model: M, model: M,
id: string, id: string,
patch: Partial<T> | ((prev: T) => T), patch: Partial<T> | ((prev: T) => T),
@@ -104,54 +103,55 @@ export function patchModelById<M extends AnyModel['model'], T extends ExtractMod
throw new Error(`Failed to get model to patch id=${id} model=${model}`); throw new Error(`Failed to get model to patch id=${id} model=${model}`);
} }
const newModel = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch }; const newModel = typeof patch === "function" ? patch(prev) : { ...prev, ...patch };
return updateModel(newModel); return updateModel(newModel);
} }
export async function patchModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export async function patchModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
base: Pick<T, 'id' | 'model'>, base: Pick<T, "id" | "model">,
patch: Partial<T>, patch: Partial<T>,
): Promise<string> { ): Promise<string> {
return patchModelById<M, T>(base.model, base.id, patch); return patchModelById<M, T>(base.model, base.id, patch);
} }
export async function updateModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export async function updateModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
model: T, model: T,
): Promise<string> { ): Promise<string> {
return invoke<string>('models_upsert', { model }); return invoke<string>("models_upsert", { model });
} }
export async function deleteModelById< export async function deleteModelById<
M extends AnyModel['model'], M extends AnyModel["model"],
T extends ExtractModel<AnyModel, M>, T extends ExtractModel<AnyModel, M>,
>(modelType: M | M[], id: string) { >(modelType: M | M[], id: string) {
let model = getModel<M, T>(modelType, id); let model = getModel<M, T>(modelType, id);
await deleteModel(model); await deleteModel(model);
} }
export async function deleteModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export async function deleteModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
model: T | null, model: T | null,
) { ) {
if (model == null) { if (model == null) {
throw new Error('Failed to delete null model'); throw new Error("Failed to delete null model");
} }
await invoke<string>('models_delete', { model }); await invoke<string>("models_delete", { model });
} }
export function duplicateModel<M extends AnyModel['model'], T extends ExtractModel<AnyModel, M>>( export function duplicateModel<M extends AnyModel["model"], T extends ExtractModel<AnyModel, M>>(
model: T | null, model: T | null,
) { ) {
if (model == null) { if (model == null) {
throw new Error('Failed to duplicate null model'); throw new Error("Failed to duplicate null model");
} }
// If the model has a name, try to duplicate it with a name that doesn't conflict // If the model has an explicit (non-empty) name, try to duplicate it with a name that doesn't conflict.
let name = 'name' in model ? resolvedModelName(model) : undefined; // When the name is empty, keep it empty so the display falls back to the URL.
if (name != null) { let name = "name" in model ? model.name : undefined;
if (name) {
const existingModels = listModels(model.model); const existingModels = listModels(model.model);
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
const hasConflict = existingModels.some((m) => { const hasConflict = existingModels.some((m) => {
if ('folderId' in m && 'folderId' in model && model.folderId !== m.folderId) { if ("folderId" in m && "folderId" in model && model.folderId !== m.folderId) {
return false; return false;
} else if (resolvedModelName(m) !== name) { } else if (resolvedModelName(m) !== name) {
return false; return false;
@@ -165,7 +165,7 @@ export function duplicateModel<M extends AnyModel['model'], T extends ExtractMod
// Name conflict. Try another one // Name conflict. Try another one
const m: RegExpMatchArray | null = name.match(/ Copy( (?<n>\d+))?$/); const m: RegExpMatchArray | null = name.match(/ Copy( (?<n>\d+))?$/);
if (m != null && m.groups?.n == null) { if (m != null && m.groups?.n == null) {
name = name.substring(0, m.index) + ' Copy 2'; name = name.substring(0, m.index) + " Copy 2";
} else if (m != null && m.groups?.n != null) { } else if (m != null && m.groups?.n != null) {
name = name.substring(0, m.index) + ` Copy ${parseInt(m.groups.n) + 1}`; name = name.substring(0, m.index) + ` Copy ${parseInt(m.groups.n) + 1}`;
} else { } else {
@@ -174,23 +174,23 @@ export function duplicateModel<M extends AnyModel['model'], T extends ExtractMod
} }
} }
return invoke<string>('models_duplicate', { model: { ...model, name } }); return invoke<string>("models_duplicate", { model: { ...model, name } });
} }
export async function createGlobalModel<T extends Exclude<AnyModel, { workspaceId: string }>>( export async function createGlobalModel<T extends Exclude<AnyModel, { workspaceId: string }>>(
patch: Partial<T> & Pick<T, 'model'>, patch: Partial<T> & Pick<T, "model">,
): Promise<string> { ): Promise<string> {
return invoke<string>('models_upsert', { model: patch }); return invoke<string>("models_upsert", { model: patch });
} }
export async function createWorkspaceModel<T extends Extract<AnyModel, { workspaceId: string }>>( export async function createWorkspaceModel<T extends Extract<AnyModel, { workspaceId: string }>>(
patch: Partial<T> & Pick<T, 'model' | 'workspaceId'>, patch: Partial<T> & Pick<T, "model" | "workspaceId">,
): Promise<string> { ): Promise<string> {
return invoke<string>('models_upsert', { model: patch }); return invoke<string>("models_upsert", { model: patch });
} }
export function replaceModelsInStore< export function replaceModelsInStore<
M extends AnyModel['model'], M extends AnyModel["model"],
T extends Extract<AnyModel, { model: M }>, T extends Extract<AnyModel, { model: M }>,
>(model: M, models: T[]) { >(model: M, models: T[]) {
const newModels: Record<string, T> = {}; const newModels: Record<string, T> = {};
@@ -207,7 +207,7 @@ export function replaceModelsInStore<
} }
export function mergeModelsInStore< export function mergeModelsInStore<
M extends AnyModel['model'], M extends AnyModel["model"],
T extends Extract<AnyModel, { model: M }>, T extends Extract<AnyModel, { model: M }>,
>(model: M, models: T[], filter?: (model: T) => boolean) { >(model: M, models: T[], filter?: (model: T) => boolean) {
mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => { mustStore().set(modelStoreDataAtom, (prev: ModelStoreData) => {
@@ -236,7 +236,7 @@ export function mergeModelsInStore<
function shouldIgnoreModel({ model, updateSource }: ModelPayload) { function shouldIgnoreModel({ model, updateSource }: ModelPayload) {
// Never ignore updates from non-user sources // Never ignore updates from non-user sources
if (updateSource.type !== 'window') { if (updateSource.type !== "window") {
return false; return false;
} }
@@ -246,11 +246,11 @@ function shouldIgnoreModel({ model, updateSource }: ModelPayload) {
} }
// Only sync models that belong to this workspace, if a workspace ID is present // Only sync models that belong to this workspace, if a workspace ID is present
if ('workspaceId' in model && model.workspaceId !== _activeWorkspaceId) { if ("workspaceId" in model && model.workspaceId !== _activeWorkspaceId) {
return true; return true;
} }
if (model.model === 'key_value' && model.namespace === 'no_sync') { if (model.model === "key_value" && model.namespace === "no_sync") {
return true; return true;
} }

View File

@@ -1,8 +1,8 @@
import { createStore } from 'jotai'; import { createStore } from "jotai";
import { AnyModel } from '../bindings/gen_models'; import { AnyModel } from "../bindings/gen_models";
export type ExtractModel<T, M> = T extends { model: M } ? T : never; export type ExtractModel<T, M> = T extends { model: M } ? T : never;
export type ModelStoreData<T extends AnyModel = AnyModel> = { export type ModelStoreData<T extends AnyModel = AnyModel> = {
[M in T['model']]: Record<string, Extract<T, { model: M }>>; [M in T["model"]]: Record<string, Extract<T, { model: M }>>;
}; };
export type JotaiStore = ReturnType<typeof createStore>; export type JotaiStore = ReturnType<typeof createStore>;

View File

@@ -1,4 +1,4 @@
import { ModelStoreData } from './types'; import { ModelStoreData } from "./types";
export function newStoreData(): ModelStoreData { export function newStoreData(): ModelStoreData {
return { return {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/models", "name": "@yaakapp-internal/models",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "guest-js/index.ts" "main": "guest-js/index.ts"
} }

View File

@@ -1,30 +1,30 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse } from './bindings/gen_api'; import { PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse } from "./bindings/gen_api";
export * from './bindings/gen_models'; export * from "./bindings/gen_models";
export * from './bindings/gen_events'; export * from "./bindings/gen_events";
export * from './bindings/gen_search'; export * from "./bindings/gen_search";
export async function searchPlugins(query: string) { export async function searchPlugins(query: string) {
return invoke<PluginSearchResponse>('cmd_plugins_search', { query }); return invoke<PluginSearchResponse>("cmd_plugins_search", { query });
} }
export async function installPlugin(name: string, version: string | null) { export async function installPlugin(name: string, version: string | null) {
return invoke<void>('cmd_plugins_install', { name, version }); return invoke<void>("cmd_plugins_install", { name, version });
} }
export async function uninstallPlugin(pluginId: string) { export async function uninstallPlugin(pluginId: string) {
return invoke<void>('cmd_plugins_uninstall', { pluginId }); return invoke<void>("cmd_plugins_uninstall", { pluginId });
} }
export async function checkPluginUpdates() { export async function checkPluginUpdates() {
return invoke<PluginUpdatesResponse>('cmd_plugins_updates', {}); return invoke<PluginUpdatesResponse>("cmd_plugins_updates", {});
} }
export async function updateAllPlugins() { export async function updateAllPlugins() {
return invoke<PluginNameVersion[]>('cmd_plugins_update_all', {}); return invoke<PluginNameVersion[]>("cmd_plugins_update_all", {});
} }
export async function installPluginFromDirectory(directory: string) { export async function installPluginFromDirectory(directory: string) {
return invoke<void>('cmd_plugins_install_from_directory', { directory }); return invoke<void>("cmd_plugins_install_from_directory", { directory });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/plugins", "name": "@yaakapp-internal/plugins",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -50,6 +50,8 @@ pub struct PluginManager {
vendored_plugin_dir: PathBuf, vendored_plugin_dir: PathBuf,
pub(crate) installed_plugin_dir: PathBuf, pub(crate) installed_plugin_dir: PathBuf,
dev_mode: bool, dev_mode: bool,
/// Errors from plugin initialization, retrievable once via `take_init_errors`.
init_errors: Arc<Mutex<Vec<(String, String)>>>,
} }
/// Callback for plugin initialization events (e.g., toast notifications) /// Callback for plugin initialization events (e.g., toast notifications)
@@ -93,6 +95,7 @@ impl PluginManager {
vendored_plugin_dir, vendored_plugin_dir,
installed_plugin_dir, installed_plugin_dir,
dev_mode, dev_mode,
init_errors: Default::default(),
}; };
// Forward events to subscribers // Forward events to subscribers
@@ -183,17 +186,21 @@ impl PluginManager {
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await; let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
if !init_errors.is_empty() { if !init_errors.is_empty() {
let joined = init_errors for (dir, err) in &init_errors {
.into_iter() warn!("Plugin failed to initialize: {dir}: {err}");
.map(|(dir, err)| format!("{dir}: {err}")) }
.collect::<Vec<_>>() *plugin_manager.init_errors.lock().await = init_errors;
.join("; ");
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
} }
Ok(plugin_manager) Ok(plugin_manager)
} }
/// Take any initialization errors, clearing them from the manager.
/// Returns a list of `(plugin_directory, error_message)` pairs.
pub async fn take_init_errors(&self) -> Vec<(String, String)> {
std::mem::take(&mut *self.init_errors.lock().await)
}
/// Get the vendored plugin directory path (resolves dev mode path if applicable) /// Get the vendored plugin directory path (resolves dev mode path if applicable)
pub fn get_plugins_dir(&self) -> PathBuf { pub fn get_plugins_dir(&self) -> PathBuf {
if self.dev_mode { if self.dev_mode {

View File

@@ -1 +1 @@
export * from './bindings/sse'; export * from "./bindings/sse";

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/sse", "name": "@yaakapp-internal/sse",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -1,24 +1,24 @@
import { Channel, invoke } from '@tauri-apps/api/core'; import { Channel, invoke } from "@tauri-apps/api/core";
import { emit } from '@tauri-apps/api/event'; import { emit } from "@tauri-apps/api/event";
import type { WatchResult } from '@yaakapp-internal/tauri'; import type { WatchResult } from "@yaakapp-internal/tauri";
import { SyncOp } from './bindings/gen_sync'; import { SyncOp } from "./bindings/gen_sync";
import { WatchEvent } from './bindings/gen_watch'; import { WatchEvent } from "./bindings/gen_watch";
export * from './bindings/gen_models'; export * from "./bindings/gen_models";
export async function calculateSync(workspaceId: string, syncDir: string) { export async function calculateSync(workspaceId: string, syncDir: string) {
return invoke<SyncOp[]>('cmd_sync_calculate', { return invoke<SyncOp[]>("cmd_sync_calculate", {
workspaceId, workspaceId,
syncDir, syncDir,
}); });
} }
export async function calculateSyncFsOnly(dir: string) { export async function calculateSyncFsOnly(dir: string) {
return invoke<SyncOp[]>('cmd_sync_calculate_fs', { dir }); return invoke<SyncOp[]>("cmd_sync_calculate_fs", { dir });
} }
export async function applySync(workspaceId: string, syncDir: string, syncOps: SyncOp[]) { export async function applySync(workspaceId: string, syncDir: string, syncOps: SyncOp[]) {
return invoke<void>('cmd_sync_apply', { return invoke<void>("cmd_sync_apply", {
workspaceId, workspaceId,
syncDir, syncDir,
syncOps: syncOps, syncOps: syncOps,
@@ -30,40 +30,40 @@ export function watchWorkspaceFiles(
syncDir: string, syncDir: string,
callback: (e: WatchEvent) => void, callback: (e: WatchEvent) => void,
) { ) {
console.log('Watching workspace files', workspaceId, syncDir); console.log("Watching workspace files", workspaceId, syncDir);
const channel = new Channel<WatchEvent>(); const channel = new Channel<WatchEvent>();
channel.onmessage = callback; channel.onmessage = callback;
const unlistenPromise = invoke<WatchResult>('cmd_sync_watch', { const unlistenPromise = invoke<WatchResult>("cmd_sync_watch", {
workspaceId, workspaceId,
syncDir, syncDir,
channel, channel,
}); });
unlistenPromise.then(({ unlistenEvent }) => { void unlistenPromise.then(({ unlistenEvent }) => {
addWatchKey(unlistenEvent); addWatchKey(unlistenEvent);
}); });
return () => return () =>
unlistenPromise unlistenPromise
.then(async ({ unlistenEvent }) => { .then(async ({ unlistenEvent }) => {
console.log('Unwatching workspace files', workspaceId, syncDir); console.log("Unwatching workspace files", workspaceId, syncDir);
unlistenToWatcher(unlistenEvent); unlistenToWatcher(unlistenEvent);
}) })
.catch(console.error); .catch(console.error);
} }
function unlistenToWatcher(unlistenEvent: string) { function unlistenToWatcher(unlistenEvent: string) {
emit(unlistenEvent).then(() => { void emit(unlistenEvent).then(() => {
removeWatchKey(unlistenEvent); removeWatchKey(unlistenEvent);
}); });
} }
function getWatchKeys() { function getWatchKeys() {
return sessionStorage.getItem('workspace-file-watchers')?.split(',').filter(Boolean) ?? []; return sessionStorage.getItem("workspace-file-watchers")?.split(",").filter(Boolean) ?? [];
} }
function setWatchKeys(keys: string[]) { function setWatchKeys(keys: string[]) {
sessionStorage.setItem('workspace-file-watchers', keys.join(',')); sessionStorage.setItem("workspace-file-watchers", keys.join(","));
} }
function addWatchKey(key: string) { function addWatchKey(key: string) {
@@ -79,6 +79,6 @@ function removeWatchKey(key: string) {
// On page load, unlisten to all zombie watchers // On page load, unlisten to all zombie watchers
const keys = getWatchKeys(); const keys = getWatchKeys();
if (keys.length > 0) { if (keys.length > 0) {
console.log('Unsubscribing to zombie file watchers', keys); console.log("Unsubscribing to zombie file watchers", keys);
keys.forEach(unlistenToWatcher); keys.forEach(unlistenToWatcher);
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/sync", "name": "@yaakapp-internal/sync",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -1,8 +1,27 @@
const { execSync } = require('node:child_process'); const { execSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");
if (process.env.SKIP_WASM_BUILD === '1') { if (process.env.SKIP_WASM_BUILD === "1") {
console.log('Skipping wasm-pack build (SKIP_WASM_BUILD=1)'); console.log("Skipping wasm-pack build (SKIP_WASM_BUILD=1)");
return; return;
} }
execSync('wasm-pack build --target bundler', { stdio: 'inherit' }); execSync("wasm-pack build --target bundler", { stdio: "inherit" });
// Rewrite the generated entry to use Vite's ?init import style instead of
// the ES Module Integration style that wasm-pack generates, which Vite/rolldown
// does not support in production builds.
const entry = path.join(__dirname, "pkg", "yaak_templates.js");
fs.writeFileSync(
entry,
[
'import init from "./yaak_templates_bg.wasm?init";',
'export * from "./yaak_templates_bg.js";',
'import * as bg from "./yaak_templates_bg.js";',
'const instance = await init({ "./yaak_templates_bg.js": bg });',
"bg.__wbg_set_wasm(instance.exports);",
"instance.exports.__wbindgen_start();",
"",
].join("\n"),
);

View File

@@ -1,6 +1,6 @@
export * from './bindings/parser'; export * from "./bindings/parser";
import { Tokens } from './bindings/parser'; import { Tokens } from "./bindings/parser";
import { escape_template, parse_template, unescape_template } from './pkg'; import { escape_template, parse_template, unescape_template } from "./pkg";
export function parseTemplate(template: string) { export function parseTemplate(template: string) {
return parse_template(template) as Tokens; return parse_template(template) as Tokens;

View File

@@ -1,7 +1,7 @@
{ {
"name": "@yaakapp-internal/templates", "name": "@yaakapp-internal/templates",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"bootstrap": "npm run build", "bootstrap": "npm run build",

View File

@@ -1,5 +1,6 @@
import * as wasm from "./yaak_templates_bg.wasm"; import init from "./yaak_templates_bg.wasm?init";
export * from "./yaak_templates_bg.js"; export * from "./yaak_templates_bg.js";
import { __wbg_set_wasm } from "./yaak_templates_bg.js"; import * as bg from "./yaak_templates_bg.js";
__wbg_set_wasm(wasm); const instance = await init({ "./yaak_templates_bg.js": bg });
wasm.__wbindgen_start(); bg.__wbg_set_wasm(instance.exports);
instance.exports.__wbindgen_start();

Binary file not shown.

View File

@@ -14,7 +14,10 @@ url = "2"
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros", "time", "test-util", "rt"] } tokio = { workspace = true, features = ["macros", "time", "test-util", "rt"] }
tokio-tungstenite = { version = "0.26.2", default-features = false, features = ["rustls-tls-native-roots", "connect"] } tokio-tungstenite = { version = "0.26.2", default-features = false, features = [
"rustls-tls-native-roots",
"connect",
] }
yaak-http = { workspace = true } yaak-http = { workspace = true }
yaak-tls = { workspace = true } yaak-tls = { workspace = true }
yaak-models = { workspace = true } yaak-models = { workspace = true }

View File

@@ -1,8 +1,8 @@
import { invoke } from '@tauri-apps/api/core'; import { invoke } from "@tauri-apps/api/core";
import { WebsocketConnection } from '@yaakapp-internal/models'; import { WebsocketConnection } from "@yaakapp-internal/models";
export function deleteWebsocketConnections(requestId: string) { export function deleteWebsocketConnections(requestId: string) {
return invoke('cmd_ws_delete_connections', { return invoke("cmd_ws_delete_connections", {
requestId, requestId,
}); });
} }
@@ -16,7 +16,7 @@ export function connectWebsocket({
environmentId: string | null; environmentId: string | null;
cookieJarId: string | null; cookieJarId: string | null;
}) { }) {
return invoke('cmd_ws_connect', { return invoke("cmd_ws_connect", {
requestId, requestId,
environmentId, environmentId,
cookieJarId, cookieJarId,
@@ -24,7 +24,7 @@ export function connectWebsocket({
} }
export function closeWebsocket({ connectionId }: { connectionId: string }) { export function closeWebsocket({ connectionId }: { connectionId: string }) {
return invoke('cmd_ws_close', { return invoke("cmd_ws_close", {
connectionId, connectionId,
}); });
} }
@@ -36,7 +36,7 @@ export function sendWebsocket({
connectionId: string; connectionId: string;
environmentId: string | null; environmentId: string | null;
}) { }) {
return invoke('cmd_ws_send', { return invoke("cmd_ws_send", {
connectionId, connectionId,
environmentId, environmentId,
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/ws", "name": "@yaakapp-internal/ws",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -5,6 +5,10 @@
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"os": ["darwin"], "os": [
"cpu": ["arm64"] "darwin"
],
"cpu": [
"arm64"
]
} }

View File

@@ -5,6 +5,10 @@
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"os": ["darwin"], "os": [
"cpu": ["x64"] "darwin"
],
"cpu": [
"x64"
]
} }

View File

@@ -5,6 +5,10 @@
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"os": ["linux"], "os": [
"cpu": ["arm64"] "linux"
],
"cpu": [
"arm64"
]
} }

View File

@@ -5,6 +5,10 @@
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"os": ["linux"], "os": [
"cpu": ["x64"] "linux"
],
"cpu": [
"x64"
]
} }

View File

@@ -5,6 +5,10 @@
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"os": ["win32"], "os": [
"cpu": ["arm64"] "win32"
],
"cpu": [
"arm64"
]
} }

View File

@@ -5,6 +5,10 @@
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"os": ["win32"], "os": [
"cpu": ["x64"] "win32"
],
"cpu": [
"x64"
]
} }

View File

@@ -4,7 +4,7 @@ const BINARY_DISTRIBUTION_PACKAGES = {
linux_arm64: "@yaakapp/cli-linux-arm64", linux_arm64: "@yaakapp/cli-linux-arm64",
linux_x64: "@yaakapp/cli-linux-x64", linux_x64: "@yaakapp/cli-linux-x64",
win32_x64: "@yaakapp/cli-win32-x64", win32_x64: "@yaakapp/cli-win32-x64",
win32_arm64: "@yaakapp/cli-win32-arm64" win32_arm64: "@yaakapp/cli-win32-arm64",
}; };
const BINARY_DISTRIBUTION_VERSION = require("./package.json").version; const BINARY_DISTRIBUTION_VERSION = require("./package.json").version;
@@ -16,5 +16,5 @@ module.exports = {
BINARY_DISTRIBUTION_PACKAGES, BINARY_DISTRIBUTION_PACKAGES,
BINARY_DISTRIBUTION_VERSION, BINARY_DISTRIBUTION_VERSION,
BINARY_NAME, BINARY_NAME,
PLATFORM_SPECIFIC_PACKAGE_NAME PLATFORM_SPECIFIC_PACKAGE_NAME,
}; };

View File

@@ -5,7 +5,7 @@ const https = require("node:https");
const { const {
BINARY_DISTRIBUTION_VERSION, BINARY_DISTRIBUTION_VERSION,
BINARY_NAME, BINARY_NAME,
PLATFORM_SPECIFIC_PACKAGE_NAME PLATFORM_SPECIFIC_PACKAGE_NAME,
} = require("./common"); } = require("./common");
const fallbackBinaryPath = path.join(__dirname, BINARY_NAME); const fallbackBinaryPath = path.join(__dirname, BINARY_NAME);
@@ -27,8 +27,8 @@ function makeRequest(url) {
} else { } else {
reject( reject(
new Error( new Error(
`npm responded with status code ${response.statusCode} when downloading package ${url}` `npm responded with status code ${response.statusCode} when downloading package ${url}`,
) ),
); );
} }
}) })

View File

@@ -1,25 +1,25 @@
{ {
"name": "@yaakapp/cli", "name": "@yaakapp/cli",
"version": "0.0.1", "version": "0.0.1",
"main": "./index.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
}, },
"scripts": {
"postinstall": "node ./install.js",
"prepublishOnly": "node ./prepublish.js"
},
"bin": { "bin": {
"yaak": "bin/cli.js", "yaak": "bin/cli.js",
"yaakcli": "bin/cli.js" "yaakcli": "bin/cli.js"
}, },
"main": "./index.js",
"scripts": {
"postinstall": "node ./install.js",
"prepublishOnly": "node ./prepublish.js"
},
"optionalDependencies": { "optionalDependencies": {
"@yaakapp/cli-darwin-x64": "0.0.1",
"@yaakapp/cli-darwin-arm64": "0.0.1", "@yaakapp/cli-darwin-arm64": "0.0.1",
"@yaakapp/cli-darwin-x64": "0.0.1",
"@yaakapp/cli-linux-arm64": "0.0.1", "@yaakapp/cli-linux-arm64": "0.0.1",
"@yaakapp/cli-linux-x64": "0.0.1", "@yaakapp/cli-linux-x64": "0.0.1",
"@yaakapp/cli-win32-x64": "0.0.1", "@yaakapp/cli-win32-arm64": "0.0.1",
"@yaakapp/cli-win32-arm64": "0.0.1" "@yaakapp/cli-win32-x64": "0.0.1"
} }
} }

View File

@@ -14,34 +14,34 @@ const packages = [
"cli-linux-arm64", "cli-linux-arm64",
"cli-linux-x64", "cli-linux-x64",
"cli-win32-arm64", "cli-win32-arm64",
"cli-win32-x64" "cli-win32-x64",
]; ];
const binaries = [ const binaries = [
{ {
src: join(__dirname, "dist", "cli-darwin-arm64", "yaak"), src: join(__dirname, "dist", "cli-darwin-arm64", "yaak"),
dest: join(__dirname, "cli-darwin-arm64", "bin", "yaak") dest: join(__dirname, "cli-darwin-arm64", "bin", "yaak"),
}, },
{ {
src: join(__dirname, "dist", "cli-darwin-x64", "yaak"), src: join(__dirname, "dist", "cli-darwin-x64", "yaak"),
dest: join(__dirname, "cli-darwin-x64", "bin", "yaak") dest: join(__dirname, "cli-darwin-x64", "bin", "yaak"),
}, },
{ {
src: join(__dirname, "dist", "cli-linux-arm64", "yaak"), src: join(__dirname, "dist", "cli-linux-arm64", "yaak"),
dest: join(__dirname, "cli-linux-arm64", "bin", "yaak") dest: join(__dirname, "cli-linux-arm64", "bin", "yaak"),
}, },
{ {
src: join(__dirname, "dist", "cli-linux-x64", "yaak"), src: join(__dirname, "dist", "cli-linux-x64", "yaak"),
dest: join(__dirname, "cli-linux-x64", "bin", "yaak") dest: join(__dirname, "cli-linux-x64", "bin", "yaak"),
}, },
{ {
src: join(__dirname, "dist", "cli-win32-arm64", "yaak.exe"), src: join(__dirname, "dist", "cli-win32-arm64", "yaak.exe"),
dest: join(__dirname, "cli-win32-arm64", "bin", "yaak.exe") dest: join(__dirname, "cli-win32-arm64", "bin", "yaak.exe"),
}, },
{ {
src: join(__dirname, "dist", "cli-win32-x64", "yaak.exe"), src: join(__dirname, "dist", "cli-win32-x64", "yaak.exe"),
dest: join(__dirname, "cli-win32-x64", "bin", "yaak.exe") dest: join(__dirname, "cli-win32-x64", "bin", "yaak.exe"),
} },
]; ];
for (const { src, dest } of binaries) { for (const { src, dest } of binaries) {
@@ -67,7 +67,7 @@ for (const pkg of packages) {
"@yaakapp/cli-linux-arm64": version, "@yaakapp/cli-linux-arm64": version,
"@yaakapp/cli-linux-x64": version, "@yaakapp/cli-linux-x64": version,
"@yaakapp/cli-win32-x64": version, "@yaakapp/cli-win32-x64": version,
"@yaakapp/cli-win32-arm64": version "@yaakapp/cli-win32-arm64": version,
}; };
} }

3500
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "yaak-app", "name": "yaak-app",
"private": true,
"version": "0.0.0", "version": "0.0.0",
"private": true,
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/mountain-loop/yaak.git" "url": "git+https://github.com/mountain-loop/yaak.git"
@@ -63,7 +63,7 @@
"src-web" "src-web"
], ],
"scripts": { "scripts": {
"prepare": "husky", "prepare": "vp config",
"init": "npm install && npm run bootstrap", "init": "npm install && npm run bootstrap",
"start": "npm run app-dev", "start": "npm run app-dev",
"app-build": "tauri build", "app-build": "tauri build",
@@ -82,34 +82,36 @@
"vendor:vendor-plugins": "node scripts/vendor-plugins.cjs", "vendor:vendor-plugins": "node scripts/vendor-plugins.cjs",
"vendor:vendor-protoc": "node scripts/vendor-protoc.cjs", "vendor:vendor-protoc": "node scripts/vendor-protoc.cjs",
"vendor:vendor-node": "node scripts/vendor-node.cjs", "vendor:vendor-node": "node scripts/vendor-node.cjs",
"format": "vp fmt",
"lint": "run-p lint:*", "lint": "run-p lint:*",
"lint:biome": "biome lint", "lint:vp": "vp lint",
"lint:extra": "npm run --workspaces --if-present lint", "lint:workspaces": "npm run --workspaces --if-present lint",
"format": "biome format --write .",
"replace-version": "node scripts/replace-version.cjs", "replace-version": "node scripts/replace-version.cjs",
"tauri": "tauri", "tauri": "tauri",
"tauri-before-build": "npm run bootstrap", "tauri-before-build": "npm run bootstrap",
"tauri-before-dev": "node scripts/run-workspaces-dev.mjs" "tauri-before-dev": "node scripts/run-workspaces-dev.mjs"
}, },
"overrides": {
"js-yaml": "^4.1.1"
},
"devDependencies": {
"@biomejs/biome": "^2.3.13",
"@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.5.1",
"dotenv-cli": "^11.0.0",
"husky": "^9.1.7",
"nodejs-file-downloader": "^4.13.0",
"npm-run-all": "^4.1.5",
"typescript": "^5.8.3",
"vitest": "^3.2.4"
},
"dependencies": { "dependencies": {
"@codemirror/lang-go": "^6.0.1", "@codemirror/lang-go": "^6.0.1",
"@codemirror/lang-java": "^6.0.2", "@codemirror/lang-java": "^6.0.2",
"@codemirror/lang-php": "^6.0.2", "@codemirror/lang-php": "^6.0.2",
"@codemirror/lang-python": "^6.2.1", "@codemirror/lang-python": "^6.2.1",
"@codemirror/legacy-modes": "^6.5.2" "@codemirror/legacy-modes": "^6.5.2"
} },
"devDependencies": {
"@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.5.1",
"dotenv-cli": "^11.0.0",
"nodejs-file-downloader": "^4.13.0",
"npm-run-all": "^4.1.5",
"typescript": "^5.8.3",
"vite-plus": "latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
},
"overrides": {
"js-yaml": "^4.1.1",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vitest": "npm:@voidzero-dev/vite-plus-test@latest"
},
"packageManager": "npm@11.11.1"
} }

View File

@@ -1,7 +1,7 @@
// biome-ignore lint/suspicious/noExplicitAny: none // oxlint-disable-next-line no-explicit-any
export function debounce(fn: (...args: any[]) => void, delay = 500) { export function debounce(fn: (...args: any[]) => void, delay = 500) {
let timer: ReturnType<typeof setTimeout>; let timer: ReturnType<typeof setTimeout>;
// biome-ignore lint/suspicious/noExplicitAny: none // oxlint-disable-next-line no-explicit-any
const result = (...args: any[]) => { const result = (...args: any[]) => {
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay); timer = setTimeout(() => fn(...args), delay);

View File

@@ -4,16 +4,16 @@ export function formatSize(bytes: number): string {
if (bytes > 1000 * 1000 * 1000) { if (bytes > 1000 * 1000 * 1000) {
num = bytes / 1000 / 1000 / 1000; num = bytes / 1000 / 1000 / 1000;
unit = 'GB'; unit = "GB";
} else if (bytes > 1000 * 1000) { } else if (bytes > 1000 * 1000) {
num = bytes / 1000 / 1000; num = bytes / 1000 / 1000;
unit = 'MB'; unit = "MB";
} else if (bytes > 1000) { } else if (bytes > 1000) {
num = bytes / 1000; num = bytes / 1000;
unit = 'KB'; unit = "KB";
} else { } else {
num = bytes; num = bytes;
unit = 'B'; unit = "B";
} }
return `${Math.round(num * 10) / 10} ${unit}`; return `${Math.round(num * 10) / 10} ${unit}`;

View File

@@ -1,3 +1,3 @@
export * from './debounce'; export * from "./debounce";
export * from './formatSize'; export * from "./formatSize";
export * from './templateFunction'; export * from "./templateFunction";

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp-internal/lib", "name": "@yaakapp-internal/lib",
"private": true,
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.ts" "main": "index.ts"
} }

View File

@@ -2,20 +2,20 @@ import type {
CallTemplateFunctionArgs, CallTemplateFunctionArgs,
JsonPrimitive, JsonPrimitive,
TemplateFunctionArg, TemplateFunctionArg,
} from '@yaakapp-internal/plugins'; } from "@yaakapp-internal/plugins";
export function validateTemplateFunctionArgs( export function validateTemplateFunctionArgs(
fnName: string, fnName: string,
args: TemplateFunctionArg[], args: TemplateFunctionArg[],
values: CallTemplateFunctionArgs['values'], values: CallTemplateFunctionArgs["values"],
): string | null { ): string | null {
for (const arg of args) { for (const arg of args) {
if ('inputs' in arg && arg.inputs) { if ("inputs" in arg && arg.inputs) {
// Recurse down // Recurse down
const err = validateTemplateFunctionArgs(fnName, arg.inputs, values); const err = validateTemplateFunctionArgs(fnName, arg.inputs, values);
if (err) return err; if (err) return err;
} }
if (!('name' in arg)) continue; if (!("name" in arg)) continue;
if (arg.optional) continue; if (arg.optional) continue;
if (arg.defaultValue != null) continue; if (arg.defaultValue != null) continue;
if (arg.hidden) continue; if (arg.hidden) continue;
@@ -34,14 +34,14 @@ export function applyFormInputDefaults(
) { ) {
let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values }; let newValues: { [p: string]: JsonPrimitive | undefined } = { ...values };
for (const input of inputs) { for (const input of inputs) {
if ('defaultValue' in input && values[input.name] === undefined) { if ("defaultValue" in input && values[input.name] === undefined) {
newValues[input.name] = input.defaultValue; newValues[input.name] = input.defaultValue;
} }
if (input.type === 'checkbox' && values[input.name] === undefined) { if (input.type === "checkbox" && values[input.name] === undefined) {
newValues[input.name] = false; newValues[input.name] = false;
} }
// Recurse down to all child inputs // Recurse down to all child inputs
if ('inputs' in input) { if ("inputs" in input) {
newValues = applyFormInputDefaults(input.inputs ?? [], newValues); newValues = applyFormInputDefaults(input.inputs ?? [], newValues);
} }
} }

View File

@@ -3,23 +3,23 @@
"version": "0.8.0", "version": "0.8.0",
"keywords": [ "keywords": [
"api-client", "api-client",
"insomnia-alternative",
"bruno-alternative", "bruno-alternative",
"insomnia-alternative",
"postman-alternative" "postman-alternative"
], ],
"homepage": "https://yaak.app",
"bugs": {
"url": "https://feedback.yaak.app"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/mountain-loop/yaak" "url": "https://github.com/mountain-loop/yaak"
}, },
"bugs": {
"url": "https://feedback.yaak.app"
},
"homepage": "https://yaak.app",
"main": "lib/index.js",
"typings": "./lib/index.d.ts",
"files": [ "files": [
"lib/**/*" "lib/**/*"
], ],
"main": "lib/index.js",
"typings": "./lib/index.d.ts",
"scripts": { "scripts": {
"bootstrap": "npm run build", "bootstrap": "npm run build",
"build": "run-s build:copy-types build:tsc", "build": "run-s build:copy-types build:tsc",

View File

@@ -1,9 +1,9 @@
export type * from './plugins'; export type * from "./plugins";
export type * from './themes'; export type * from "./themes";
export * from './bindings/gen_models'; export * from "./bindings/gen_models";
export * from './bindings/gen_events'; export * from "./bindings/gen_events";
// Some extras for utility // Some extras for utility
export type { PartialImportResources } from './plugins/ImporterPlugin'; export type { PartialImportResources } from "./plugins/ImporterPlugin";

View File

@@ -5,9 +5,9 @@ import type {
FormInput, FormInput,
GetHttpAuthenticationSummaryResponse, GetHttpAuthenticationSummaryResponse,
HttpAuthenticationAction, HttpAuthenticationAction,
} from '../bindings/gen_events'; } from "../bindings/gen_events";
import type { MaybePromise } from '../helpers'; import type { MaybePromise } from "../helpers";
import type { Context } from './Context'; import type { Context } from "./Context";
type AddDynamicMethod<T> = { type AddDynamicMethod<T> = {
dynamic?: ( dynamic?: (
@@ -16,16 +16,16 @@ type AddDynamicMethod<T> = {
) => MaybePromise<Partial<T> | null | undefined>; ) => MaybePromise<Partial<T> | null | undefined>;
}; };
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern // oxlint-disable-next-line no-explicit-any -- distributive conditional type pattern
type AddDynamic<T> = T extends any type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] } ? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & { ? Omit<T, "inputs"> & {
inputs: Array<AddDynamic<FormInput>>; inputs: Array<AddDynamic<FormInput>>;
dynamic?: ( dynamic?: (
ctx: Context, ctx: Context,
args: CallHttpAuthenticationActionArgs, args: CallHttpAuthenticationActionArgs,
) => MaybePromise< ) => MaybePromise<
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined Partial<Omit<T, "inputs"> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
>; >;
} }
: T & AddDynamicMethod<T> : T & AddDynamicMethod<T>

View File

@@ -26,10 +26,10 @@ import type {
ShowToastRequest, ShowToastRequest,
TemplateRenderRequest, TemplateRenderRequest,
WorkspaceInfo, WorkspaceInfo,
} from '../bindings/gen_events.ts'; } from "../bindings/gen_events.ts";
import type { Folder, HttpRequest } from '../bindings/gen_models.ts'; import type { Folder, HttpRequest } from "../bindings/gen_models.ts";
import type { JsonValue } from '../bindings/serde_json/JsonValue'; import type { JsonValue } from "../bindings/serde_json/JsonValue";
import type { MaybePromise } from '../helpers'; import type { MaybePromise } from "../helpers";
export type CallPromptFormDynamicArgs = { export type CallPromptFormDynamicArgs = {
values: { [key in string]?: JsonPrimitive }; values: { [key in string]?: JsonPrimitive };
@@ -42,16 +42,16 @@ type AddDynamicMethod<T> = {
) => MaybePromise<Partial<T> | null | undefined>; ) => MaybePromise<Partial<T> | null | undefined>;
}; };
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern // oxlint-disable-next-line no-explicit-any -- distributive conditional type pattern
type AddDynamic<T> = T extends any type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] } ? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & { ? Omit<T, "inputs"> & {
inputs: Array<AddDynamic<FormInput>>; inputs: Array<AddDynamic<FormInput>>;
dynamic?: ( dynamic?: (
ctx: Context, ctx: Context,
args: CallPromptFormDynamicArgs, args: CallPromptFormDynamicArgs,
) => MaybePromise< ) => MaybePromise<
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined Partial<Omit<T, "inputs"> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
>; >;
} }
: T & AddDynamicMethod<T> : T & AddDynamicMethod<T>
@@ -59,11 +59,11 @@ type AddDynamic<T> = T extends any
export type DynamicPromptFormArg = AddDynamic<FormInput>; export type DynamicPromptFormArg = AddDynamic<FormInput>;
type DynamicPromptFormRequest = Omit<PromptFormRequest, 'inputs'> & { type DynamicPromptFormRequest = Omit<PromptFormRequest, "inputs"> & {
inputs: DynamicPromptFormArg[]; inputs: DynamicPromptFormArg[];
}; };
export type WorkspaceHandle = Pick<WorkspaceInfo, 'id' | 'name'>; export type WorkspaceHandle = Pick<WorkspaceInfo, "id" | "name">;
export interface Context { export interface Context {
clipboard: { clipboard: {
@@ -73,8 +73,8 @@ export interface Context {
show(args: ShowToastRequest): Promise<void>; show(args: ShowToastRequest): Promise<void>;
}; };
prompt: { prompt: {
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>; text(args: PromptTextRequest): Promise<PromptTextResponse["value"]>;
form(args: DynamicPromptFormRequest): Promise<PromptFormResponse['values']>; form(args: DynamicPromptFormRequest): Promise<PromptFormResponse["values"]>;
}; };
store: { store: {
set<T>(key: string, value: T): Promise<void>; set<T>(key: string, value: T): Promise<void>;
@@ -94,41 +94,41 @@ export interface Context {
openExternalUrl(url: string): Promise<void>; openExternalUrl(url: string): Promise<void>;
}; };
cookies: { cookies: {
listNames(): Promise<ListCookieNamesResponse['names']>; listNames(): Promise<ListCookieNamesResponse["names"]>;
getValue(args: GetCookieValueRequest): Promise<GetCookieValueResponse['value']>; getValue(args: GetCookieValueRequest): Promise<GetCookieValueResponse["value"]>;
}; };
grpcRequest: { grpcRequest: {
render(args: RenderGrpcRequestRequest): Promise<RenderGrpcRequestResponse['grpcRequest']>; render(args: RenderGrpcRequestRequest): Promise<RenderGrpcRequestResponse["grpcRequest"]>;
}; };
httpRequest: { httpRequest: {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>; send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse["httpResponse"]>;
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>; getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse["httpRequest"]>;
render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>; render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse["httpRequest"]>;
list(args?: ListHttpRequestsRequest): Promise<ListHttpRequestsResponse['httpRequests']>; list(args?: ListHttpRequestsRequest): Promise<ListHttpRequestsResponse["httpRequests"]>;
create( create(
args: Omit<Partial<HttpRequest>, 'id' | 'model' | 'createdAt' | 'updatedAt'> & args: Omit<Partial<HttpRequest>, "id" | "model" | "createdAt" | "updatedAt"> &
Pick<HttpRequest, 'workspaceId' | 'url'>, Pick<HttpRequest, "workspaceId" | "url">,
): Promise<HttpRequest>; ): Promise<HttpRequest>;
update( update(
args: Omit<Partial<HttpRequest>, 'model' | 'createdAt' | 'updatedAt'> & args: Omit<Partial<HttpRequest>, "model" | "createdAt" | "updatedAt"> &
Pick<HttpRequest, 'id'>, Pick<HttpRequest, "id">,
): Promise<HttpRequest>; ): Promise<HttpRequest>;
delete(args: { id: string }): Promise<HttpRequest>; delete(args: { id: string }): Promise<HttpRequest>;
}; };
folder: { folder: {
list(args?: ListFoldersRequest): Promise<ListFoldersResponse['folders']>; list(args?: ListFoldersRequest): Promise<ListFoldersResponse["folders"]>;
getById(args: { id: string }): Promise<Folder | null>; getById(args: { id: string }): Promise<Folder | null>;
create( create(
args: Omit<Partial<Folder>, 'id' | 'model' | 'createdAt' | 'updatedAt'> & args: Omit<Partial<Folder>, "id" | "model" | "createdAt" | "updatedAt"> &
Pick<Folder, 'workspaceId' | 'name'>, Pick<Folder, "workspaceId" | "name">,
): Promise<Folder>; ): Promise<Folder>;
update( update(
args: Omit<Partial<Folder>, 'model' | 'createdAt' | 'updatedAt'> & Pick<Folder, 'id'>, args: Omit<Partial<Folder>, "model" | "createdAt" | "updatedAt"> & Pick<Folder, "id">,
): Promise<Folder>; ): Promise<Folder>;
delete(args: { id: string }): Promise<Folder>; delete(args: { id: string }): Promise<Folder>;
}; };
httpResponse: { httpResponse: {
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>; find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse["httpResponses"]>;
}; };
templates: { templates: {
render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>; render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>;

View File

@@ -1,5 +1,5 @@
import type { FilterResponse } from '../bindings/gen_events'; import type { FilterResponse } from "../bindings/gen_events";
import type { Context } from './Context'; import type { Context } from "./Context";
export type FilterPlugin = { export type FilterPlugin = {
name: string; name: string;

View File

@@ -1,5 +1,5 @@
import type { CallFolderActionArgs, FolderAction } from '../bindings/gen_events'; import type { CallFolderActionArgs, FolderAction } from "../bindings/gen_events";
import type { Context } from './Context'; import type { Context } from "./Context";
export type FolderActionPlugin = FolderAction & { export type FolderActionPlugin = FolderAction & {
onSelect(ctx: Context, args: CallFolderActionArgs): Promise<void> | void; onSelect(ctx: Context, args: CallFolderActionArgs): Promise<void> | void;

View File

@@ -1,5 +1,5 @@
import type { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events'; import type { CallGrpcRequestActionArgs, GrpcRequestAction } from "../bindings/gen_events";
import type { Context } from './Context'; import type { Context } from "./Context";
export type GrpcRequestActionPlugin = GrpcRequestAction & { export type GrpcRequestActionPlugin = GrpcRequestAction & {
onSelect(ctx: Context, args: CallGrpcRequestActionArgs): Promise<void> | void; onSelect(ctx: Context, args: CallGrpcRequestActionArgs): Promise<void> | void;

View File

@@ -1,5 +1,5 @@
import type { CallHttpRequestActionArgs, HttpRequestAction } from '../bindings/gen_events'; import type { CallHttpRequestActionArgs, HttpRequestAction } from "../bindings/gen_events";
import type { Context } from './Context'; import type { Context } from "./Context";
export type HttpRequestActionPlugin = HttpRequestAction & { export type HttpRequestActionPlugin = HttpRequestAction & {
onSelect(ctx: Context, args: CallHttpRequestActionArgs): Promise<void> | void; onSelect(ctx: Context, args: CallHttpRequestActionArgs): Promise<void> | void;

View File

@@ -1,17 +1,17 @@
import type { ImportResources } from '../bindings/gen_events'; import type { ImportResources } from "../bindings/gen_events";
import type { AtLeast, MaybePromise } from '../helpers'; import type { AtLeast, MaybePromise } from "../helpers";
import type { Context } from './Context'; import type { Context } from "./Context";
type RootFields = 'name' | 'id' | 'model'; type RootFields = "name" | "id" | "model";
type CommonFields = RootFields | 'workspaceId'; type CommonFields = RootFields | "workspaceId";
export type PartialImportResources = { export type PartialImportResources = {
workspaces: Array<AtLeast<ImportResources['workspaces'][0], RootFields>>; workspaces: Array<AtLeast<ImportResources["workspaces"][0], RootFields>>;
environments: Array<AtLeast<ImportResources['environments'][0], CommonFields>>; environments: Array<AtLeast<ImportResources["environments"][0], CommonFields>>;
folders: Array<AtLeast<ImportResources['folders'][0], CommonFields>>; folders: Array<AtLeast<ImportResources["folders"][0], CommonFields>>;
httpRequests: Array<AtLeast<ImportResources['httpRequests'][0], CommonFields>>; httpRequests: Array<AtLeast<ImportResources["httpRequests"][0], CommonFields>>;
grpcRequests: Array<AtLeast<ImportResources['grpcRequests'][0], CommonFields>>; grpcRequests: Array<AtLeast<ImportResources["grpcRequests"][0], CommonFields>>;
websocketRequests: Array<AtLeast<ImportResources['websocketRequests'][0], CommonFields>>; websocketRequests: Array<AtLeast<ImportResources["websocketRequests"][0], CommonFields>>;
}; };
export type ImportPluginResponse = null | { export type ImportPluginResponse = null | {

View File

@@ -1,6 +1,6 @@
import type { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events'; import type { CallTemplateFunctionArgs, FormInput, TemplateFunction } from "../bindings/gen_events";
import type { MaybePromise } from '../helpers'; import type { MaybePromise } from "../helpers";
import type { Context } from './Context'; import type { Context } from "./Context";
type AddDynamicMethod<T> = { type AddDynamicMethod<T> = {
dynamic?: ( dynamic?: (
@@ -9,16 +9,16 @@ type AddDynamicMethod<T> = {
) => MaybePromise<Partial<T> | null | undefined>; ) => MaybePromise<Partial<T> | null | undefined>;
}; };
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern // oxlint-disable-next-line no-explicit-any -- distributive conditional type pattern
type AddDynamic<T> = T extends any type AddDynamic<T> = T extends any
? T extends { inputs?: FormInput[] } ? T extends { inputs?: FormInput[] }
? Omit<T, 'inputs'> & { ? Omit<T, "inputs"> & {
inputs: Array<AddDynamic<FormInput>>; inputs: Array<AddDynamic<FormInput>>;
dynamic?: ( dynamic?: (
ctx: Context, ctx: Context,
args: CallTemplateFunctionArgs, args: CallTemplateFunctionArgs,
) => MaybePromise< ) => MaybePromise<
Partial<Omit<T, 'inputs'> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined Partial<Omit<T, "inputs"> & { inputs: Array<AddDynamic<FormInput>> }> | null | undefined
>; >;
} }
: T & AddDynamicMethod<T> : T & AddDynamicMethod<T>
@@ -26,7 +26,7 @@ type AddDynamic<T> = T extends any
export type DynamicTemplateFunctionArg = AddDynamic<FormInput>; export type DynamicTemplateFunctionArg = AddDynamic<FormInput>;
export type TemplateFunctionPlugin = Omit<TemplateFunction, 'args'> & { export type TemplateFunctionPlugin = Omit<TemplateFunction, "args"> & {
args: DynamicTemplateFunctionArg[]; args: DynamicTemplateFunctionArg[];
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>; onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>;
}; };

View File

@@ -1,3 +1,3 @@
import type { Theme } from '../bindings/gen_events'; import type { Theme } from "../bindings/gen_events";
export type ThemePlugin = Theme; export type ThemePlugin = Theme;

Some files were not shown because too many files have changed in this diff Show More